# Copyright (C) 2007-2008, The Perl Foundation.
# $Id: /mirror/trunk/languages/PIR/src/pir.pg 32065 2008-10-19T17:43:05.033899Z kjs  $

=head1 NAME

pir.pg - A PIR grammar for PGE.

TODO:

 1. fix Heredocs parsing
 2. Test and fix things

=cut


grammar PIR::Grammar is PCT::Grammar;


token TOP {
    ^ <program>
    [ $ || <.syntax_error: end of file expected> ]
   {*}
}

rule program {
    <.nl>*
    <compilation_unit>
    [ <.nl> <compilation_unit> ]*
    <.nl>
    {*}
}

token compilation_unit {
    | <expansion>  {*} #= expansion
    | <sub_def>    {*} #= sub_def
    | <const_def>  {*} #= const_def
    | <pragma>     {*} #= pragma
}

rule sub_def {
    '.sub' <sub_id> <sub_pragma>* <.nl>
    <param_decl>*
    <labeled_pir_instr>*
    '.end'
    {*}
}

rule sub_id {
    | <id> {*}              #= id
    | <string_constant> {*} #= string_constant
}

rule sub_pragma {
    | <short_sub_pragma> {*} #= short_sub_pragma
    | <vtable_pragma>  {*} #= vtable_pragma
    | <multi_pragma>   {*} #= multi_pragma
    | <outer_pragma>   {*} #= outer_pragma
}

token short_sub_pragma {
    | ':load'
    | ':init'
    | ':immediate'
    | ':postcomp'
    | ':main'
    | ':anon'
    | ':lex'
}

rule vtable_pragma {
    ':vtable' <parenthesized_string>?
    {*}
}

rule parenthesized_string {
    '(' <string_constant> ')'
    {*}
}

rule multi_pragma {
    ':multi' '(' <multi_types>? ')'
    {*}
}

rule outer_pragma {
    ':outer' '(' <sub_id> ')'
    {*}
}

rule multi_types {
    <multi_type> [ ',' <multi_type> ]*
}

rule multi_type {
    | <pir_type> {*}        #= pir_type
    | '_' {*}               #= any_type
    | <keylist> {*}         #= keylist
    | <id> {*}              #= id
    | <string_constant> {*} #= string_constant
}

rule param_decl {
    '.param' <parameter> <parameter_flags> <.nl>
    {*}
}

rule parameter {
    <pir_type> [ <string_constant> '=>' ]? <id>
    {*}
}

rule parameter_flags {
    [ <get_flags> | ':unique_reg' ]*
}

rule labeled_pir_instr {
    [ <label> <instr>?
    | <instr>
    ]
    <.nl>
    {*}
}

rule labeled_pasm_instr {
    [ <label> <pasm_instr>?
    | <pasm_instr>
    ]
    <.nl>
    {*}
}

rule instr {
    | <pir_instr> {*}          #= pir_instr
    | <pasm_instr> {*}         #= pasm_instr
}

token pir_instr {
    | <local_decl>         {*} #= local_decl
    | <lexical_decl>       {*} #= lexical_decl
    | <const_def>          {*} #= const_def
    | <globalconst_def>    {*} #= globalconst_def
    | <conditional_stat>   {*} #= conditional_stat
    | <assignment_stat>    {*} #= assignment_stat
    | <return_stat>        {*} #= return_stat
    | <sub_invocation>     {*} #= sub_invocation
    | <macro_invocation>   {*} #= macro_invocation
    | <goto_stat>          {*} #= goto_stat
    | <source_info>        {*} #= source_info
}

rule macro_invocation {
    <macro_id> <parenthesized_args>?
}


=head2 Local declarations

Local declarations can be done using C<.local> in normal context.
TODO: how to declare locals in a macro? this needs to be fixed.

=cut

rule local_decl {
    '.local'
    <pir_type>
    <local_id> [ ',' <local_id> ]*
    {*}
}


rule local_id {
    <id> [$<unique>=':unique_reg']?
    {*}
}

rule lexical_decl {
    '.lex' <string_constant> ',' <target>
    {*}
}


=head2 Const definition

Const definitions, a check is done for the constant type and the type of the constant value.

=cut

rule const_def {
    '.const' <const_def_tail>

}

rule globalconst_def {
    '.globalconst' <const_def_tail>

}

rule const_def_tail {
    | 'int'    <id> '=' <int_constant>     {*} #= int_const_def
    | 'num'    <id> '=' <float_constant>   {*} #= num_const_def
    | 'pmc'    <id> '=' <string_constant>  {*} #= pmc_const_def
    | 'string' <id> '=' <string_constant>  {*} #= string_const_def
}

rule conditional_stat {
    $<sym>=[if|unless] <conditional_expr> 'goto' <label_identifier>
    {*}
}

token label_identifier {
    | <id>          {*} #= id
    | <pasm_instr>     {*} #= pasm_instr
}

rule conditional_expr {
    | 'null' <target>
    | <simple_expr> [ <relational_operator> <simple_expr> ]?
}

rule goto_stat {
    goto <label_identifier>
    {*}
}

token relational_operator {
    | '=='
    | '!='
    | '<='
    | '<'
    | '>='
    | '>'
}

token binary_operator {
    | '+'  | '-'  | '/'  | '**' | '*' | '%' | '<<' | '>>>'
    | '>>' | '&&' | '||' | '~~' | '|' | '&' | '~'  | '.'
}

token assign_operator {
    | '+=' | '-=' | '/=' | '%='  | '*='  | '.='
    | '&=' | '|=' | '~=' | '<<=' | '>>=' | '>>>='
}

token unary_operator {
    '!' | '-' | '~'
}

rule expression {
    #| <binary_expr> {*}  #= binary_expr
    #| <unary_expr>  {*}  #= unary_expr
    | <simple_expr> {*}  #= simple_expr
}

rule binary_expr {
    <simple_expr> <binary_operator> <simple_expr>
    {*}
}

rule unary_expr {
    <unary_operator> <simple_expr>
    {*}
}

token simple_expr {
    | <constant>  {*} #= constant
    | <target>    {*} #= target
}


rule keylist {
    '[' <key> [ <separator> <key> ]* ']'
}

token separator {
    | ','
    | ';'
}

rule key {
    <simple_expr>
    {*}
}


rule assignment_stat {
    | <simple_assignment> {*}        #= simple_assignment
    | <augment_assignment> {*}       #= augment_assignment
    | <keyed_assignment> {*}         #= keyed_assignment
    | <call_assignment> {*}          #= call_assignment
}

rule augment_assignment {
    <target> <assign_operator> <simple_expr>
    {*}
}

rule keyed_assignment {
    <target> <keylist> '=' <simple_expr>
    {*}
}

rule call_assignment {
    <result_var_list> '=' <short_sub_call>
    {*}
}

rule simple_assignment {
    <target> '=' <rhs>
    {*}
}

rule rhs {
    | <short_sub_call>                        {*} #= short_sub_call
    | <target> <keylist>                      {*} #= target_keylist
    | <expression>                            {*} #= expression
    | <pasm_instruction> \N*                  {*} #= pasm_instr
    | <heredoc>                               {*} #= heredoc
    | <instantiation>                         {*} #= instantiation
}

rule instantiation {
    'new'
    [ <string_constant> {*}         #= string_constant
    | <keylist> {*}                 #= keylist
    ]
}



rule heredoc {
    '<<' <string_constant> <.nl>
    <heredoc_string>
    [ <heredoc_label> | <syntax_error: heredoc label expected> ]
}

token heredoc_label {
    [ ^^ | <syntax_error: no whitespace allowed in front of a heredoc label> ]
    <id>
    [ $$ | <syntax_error: no whitespace allowed after a heredoc> ]
}

# fix this:
rule heredoc_string {
    [ \N | \n ]*
}

rule long_sub_call {
    '.begin_call' <.nl>
    <arguments>
    <long_call_invocation> <.nl>
    [ <local_decl> <.nl> ]*
    <result_values>
    '.end_call'
    {*}
}

rule long_call_invocation {
    | <method_invocation>        {*} #= method_invocation
    | <non_method_invocation>    {*} #= non_method_invocation
}

rule non_method_invocation {
    [ '.call' | '.nci_call' ] <target>
    {*}
}

rule method_invocation {
    '.invocant' <target> <.nl>
    '.meth_call' <method>
    {*}
}

#
# in case of a method call, these options are allowed:
# - foo.bar()
# - foo. bar()
# - foo .bar()
#
# where the method ('bar') can also be a quoted string.
# it's important to recognize that the dot can NOT be written as
# a separate token, it must be glued to either the invocant or the method.
# otherwise it's recognized as a concatentation.
#

rule short_sub_call {
    <short_call_invocant>
    <parenthesized_args>             # sub args
    <process_heredocs>               # process the list of heredoc labels, if any
    <clear_heredocs>
    {*}
}

rule short_call_invocant {
    | <target> [ \.<method> ]
    | [ <target>\. ]? <method>
    {*}
}

rule method {
    | <target>           {*} #= target
    | <string_constant>  {*} #= string_constant
}

rule sub_invocation {
    | <long_sub_call>    {*} #= long_sub_call
    | <short_sub_call>   {*} #= short_sub_call
}

rule result_var_list {
    '('[ <result_var> [',' <result_var> ]* ]? ')'
    {*}
}

rule result_var {
    <target> <get_flags>*
    {*}
}

rule parenthesized_args {
    '(' [ <arg> [ ',' <arg> ]* ]? ')'
    {*}
}

rule arg {
    [ <float_constant>   {*} #= float_constant
    | <int_constant>     {*} #= int_constant
    | <string_constant>  {*} #= string_constant
      [ '=>' <target> ]?
    | <target>           {*} #= target
    | <heredoc_id>       {*} #= heredoc_id
    ] <set_flags>*
    {*}
}

token heredoc_id {
    '<<' <string_constant> <store_heredoc_label: $1>
}

#rule heredoc_label {
#    .* ^^ <ident> $$
#}

rule arguments {
    [ '.set_arg' <simple_expr> <set_flags>* <.nl> ]*
    {*}
}

rule result_values {
    [ '.result' <target> <get_flags>* <.nl> ]*
    {*}
}

rule set_flags {
    | ':flat'
    | <named_flag>
}

rule get_flags {
    | ':slurpy'
    | ':optional'
    | ':opt_flag'
    | <named_flag>
}

rule named_flag {
    ':named' <parenthesized_string>?
    {*}
}

token return_stat {
    | <long_return_stat>   {*} #= long_return_stat
    | <short_return_stat>  {*} #= short_return_stat
    | <long_yield_stat>    {*} #= long_yield_stat
    | <short_yield_stat>   {*} #= short_yield_stat
    | <tail_call>          {*} #= tail_call
}

rule long_return_stat {
    '.begin_return' <.nl>
    [ '.return' <return_value> ]*
    '.end_return'
    {*}
}

rule short_return_stat {
    '.return' <parenthesized_args>
    {*}
}

rule long_yield_stat {
    '.begin_yield' <.nl>
    [ '.yield' <return_value> ]*
    '.end_yield'
    {*}
}


rule return_value {
    <simple_expr> <set_flags>* <.nl>
}

rule short_yield_stat {
    '.yield' <parenthesized_args>
    {*}
}

rule tail_call {
    '.return' <short_sub_call>
    {*}
}

rule macro_def {
    '.macro' <id> <macro_parameters>? <.nl>
    <macro_body>
    {*}
}

rule macro_parameters {
    '(' <id_list>? ')'
    {*}
}

=head2 Macro body

In order to be able to parse macro identifiers, before
the macro body is parsed, some rules are redefined.
After parsing the macro body, they are restored.

=cut

regex macro_body {
    <init_macro_rules>
    <labeled_pir_instr>*
    '.endm'
    <close_macro_rules>
    {*}
}

token expansion {
    | <macro_def>     {*} #= macro_def
    | <include>       {*} #= include
    | <pasm_constant> {*} #= pasm_constant
}

rule pasm_constant {
    '.macro_const' <id>
    [ <pasm_register> | <constant> ]
    {*}
}

token constant {
    | <float_constant>  {*} #= float_constant
    | <int_constant>    {*} #= int_constant
    | <string_constant> {*} #= string_constant
}


# only a PASM register is allowed for pasm_constant
#
token pasm_register {
    [P|I|N|S]\d\d?
}

token pragma {
    | <new_operators>   {*} #= new_operators
    | <loadlib>         {*} #= loadlib
    | <namespace>       {*} #= namespace
    | <hll_mapping>     {*} #= hll_mapping
    | <hll_specifier>   {*} #= hll_specifier
    | <source_info>     {*} #= source_info
}

rule include {
    '.include' <string_constant>
    {*}
}

rule new_operators {
    '.pragma' 'n_operators' <int_constant>
    {*}
}

rule loadlib {
    '.loadlib' <string_constant>
    {*}
}

rule namespace {
    '.namespace'
    [ '[' <namespace_id> ']' ]?
    {*}
}

rule hll_specifier {
    '.HLL' <string_constant> <.comma> <string_constant>
    {*}
}

rule hll_mapping {
    '.HLL_map' <string_constant> <.comma> <string_constant>
    {*}
}

rule namespace_id {
    <string_constant> [ ';' <string_constant> ]*
}

rule source_info {
    '.line' <int_constant> [ ',' <string_constant> ]?
    {*}
}

rule id_list {
    <id> [ ',' <id> ]*
}

token normal_label {
    <id> ':'
}

token macro_label {
    '$' <id> ':'
}

rule macro_label_decl {
    | '.label' <macro_label>
    | <normal_label>
}

rule int_constant {
    [
    | '0b' \d+
    | '0x' \d+
    | ['-']? \d+
    ]
    {*}
}

rule float_constant {
    ['-']? \d+\.\d+
    {*}
}

rule string_constant {
    [ <encoding_specifier>? <charset_specifier> ]?
    [ <stringdouble> | <stringsingle> ]
    {*}
}

token encoding_specifier {
    'utf8:'
}

token charset_specifier {
    | 'ascii:'
    | 'binary:'
    | 'unicode:'
    | 'iso-8859-1:'
}

token stringsingle {
    \' <string_literal: '\''> \'
}

token stringdouble {
    '"' <string_literal: '"'> '"'
}

token type {
    | <pir_type>
    | <macro_id>
}

token pir_type {
    $<type>=['int'|'num'|'pmc'|'string']
}

token target {
    <normal_target>
    {*}
}

token normal_target {
    | <id>         {*} #= id
    | <register>   {*} #= register
}

=head2 Macro_target

In a macro, a target can also be a macro_id

=cut

token macro_target {
    | <id>
    | <register>
    | <macro_id>
}

token id {
    <!keyword> \w+
    {*}
}

token macro_id {
    <!pir_directive> \.\w+
}

token register {
    | <ireg>
    | <nreg>
    | <preg>
    | <sreg>
}

token ireg {
    | I\d<[\d]>?  # match I0 to I99
    | \$I\d+      # match $I0 to infinity
}

token sreg {
    | S\d<[\d]>?
    | \$S\d+
}

token preg {
    | P\d<[\d]>?
    | \$P\d+
}

token nreg {
    | N\d<[\d]>?
    | \$N\d+
}

token ws {
    [ [ \s & \N ]+
    | \# \N*
    | ^^ <.pod_comment>
    ]*
}


=head2 Newlines

match the set of a newline and possible indention space 1 or more times
this is necessary to be able to skip fully white lines (with some spaces/tabs in it).
However, therefore it is also necessary to put the comments skipping here as well.

=cut



token nl {
    [ \n <.ws> ]+
}


regex pod_comment {
    ^^ '=' [ [ cut \h*: | end [\h\N*]? ]
         | for [ \h\N+: ] \n [ \N+\n ]*:
         | \w\N*: \n .*? \n '=' [ cut \h*: | end [\h\N*:]? ]
         ]
         [\n|$]
}

=head2 Keywords

PIR directives, PASM instructions and
the Parrot datatypes (int, string, num, pmc)
are keywords, and must not be used as
identifiers.

=cut

token keyword {
    [ <pir_type>
    | <pir_keyword>
    | <pir_directive>
    | <pasm_instruction> # pasm instructions may be identifiers in IMCC
    ]
}


# TODO: find out if we need to do this: I think so..
token pir_keyword {
    'goto'
}

=head2 PIR directives

TODO: put this in <%pir_directive> PIR directives

=cut

token pir_directive {
    [ '.set_arg'
    | '.const'
    | '.endm'
    | '.end'
    | '.get_results'
    | '.globalconst'
    | '.HLL_map'
    | '.HLL'
    | '.include'
    | '.invocant'
    | '.label'
    | '.lex'
    | '.line'
    | '.loadlib'
    | '.local'
    | '.macro_const'
    | '.meth_call'
    | '.namespace'
    | '.nci_call'
    | '.param'
    | '.begin_return'
    | '.begin_yield'
    | '.begin_call'
    | '.call'
    | '.end_return'
    | '.end_yield'
    | '.end_call'
    | '.pcc_sub'
    | '.pragma'
    | '.return'
    | '.sub'
    | '.result'
    | '.yield'
    ] >>
}
