# Copyright (C) 2007, The Perl Foundation.
# $Id: /mirror/trunk/compilers/nqp/src/Grammar.pg 35863 2009-01-20T18:52:17.052822Z pmichaud  $


=begin overview

The following is the grammar for nqp written as a sequence of
Perl 6 rules.  In each of the rules, the special notation C<{*}>
marks a point in the rule where the corresponding action in
NQP::Grammar::Actions is to be invoked (see F<src/Grammar/Actions.pir>).
These actions are then used to construct the AST nodes for
the program.

The C<#=> markers at the ends of lines are used to distinguish
among multiple C<{*}> actions within a rule, by passing the value
after the marker as a 'key' argument to the action method.

=end overview

grammar NQP::Grammar is PCT::Grammar;


token TOP {
  <statement_block>
  [ $ || <panic: 'Syntax error'> ]
  {*}
}

##  The <ws> and <pod_comment> rules handle whitespace and comments.
##  These are taken from an earlier draft of Perl 6, so they do not
##  yet handle all valid forms of Perl 6 whitespace.
token ws {
    {{  $P0 = match.'to'()
        $P1 = get_hll_global [ 'NQP';'Grammar' ], '$!ws'
        if null $P1 goto set_new_ws
        $P2 = $P1.'to'()
        if $P0 != $P2 goto set_new_ws
        .return (1)
      set_new_ws:
        set_hll_global [ 'NQP';'Grammar' ], '$!ws', match
    }}
    [ \s+
    | ^^ [ \# \N*
         | <.pod_comment>
         ]
    | \# \N*
    ]*
}


token afterws {
    ##  <?{ $¢ == $!ws_to != $!ws_from }>
    {{  $P0 = match.'to'()
        $P1 = get_hll_global [ 'NQP';'Grammar' ], '$!ws'
        $P2 = $P1.'to'()
        if $P0 != $P2 goto end
        $P2 = $P1.'from'()
        if $P0 == $P1 goto end
        .return (1)
      end:
    }}
    <fail>
}


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


#### Blocks and Statements ####

##  A <statement_block> is a statement list that is returned as
##  a PAST::Block node.
rule statement_block {
    {*}                                                    #= open
    <statement_list>
    {*}                                                    #= close
}

##  Parse a list of statements.
rule statement_list {
    [ <statement>
        [  <.statement_end> || <panic: 'Statement not terminated properly'> ]
    ]*
    {*}
}


##  Parse a single statement, which may be either a bare block
##  or an expression.  Any statement termination is handled by
##  the calling rule.
rule statement {
    | <if_statement> {*}                         #= if_statement
    | <unless_statement> {*}                     #= unless_statement
    | <while_statement> {*}                      #= while_statement
    | <repeat_statement> {*}                     #= repeat_statement
    | <for_statement> {*}                        #= for_statement
    | <return_statement> {*}                     #= return_statement
    | <make_statement> {*}                       #= make_statement
    | <inline_pir_statement> {*}                 #= inline_pir_statement
    | <block> {*}                                #= block
    | <EXPR> {*}                                 #= EXPR
}


##  The statement_end rule detects when we're at valid statement
##  termination point.  A semicolon always acts as a valid
##  statement ending point, as does the presence of any expression
##  terminator.  The MARK_STATEMENT_END subrule is used by other
##  rules to indicate a valid statement end even when a terminator
##  isn't present (e.g., the closing '}' at the end of a line for
##  a <block> ).

token terminator { <[ } \] ) ]> | $ }

token statement_end {
     || ';'
     || <?before <terminator>>
     || {{  $P0 = get_hll_global ['NQP';'Grammar'], '$!endstmt'
            $P1 = get_hll_global ['NQP';'Grammar'], '$!ws'
            $P2 = $P1.'from'()
            if $P0 != $P2 goto end
            .return (1)
            end:
        }} <fail>
}

token MARK_STATEMENT_END {
    {{  $P0 = match.'to'()
        $P0 = clone $P0
        set_hll_global ['NQP';'Grammar'], '$!endstmt', $P0
    }}
    <.ws>
}


##  The if_statement handles conditional statements such as 'if' and
##  'unless'.

rule if_statement {
    $<sym>=['if']
    <EXPR> <block>
    [ 'elsif' <EXPR> <block> ]*
    [ 'else' $<else>=<block> ]?
    {*}
}

rule unless_statement {
    $<sym>=['unless']
    <EXPR> <block>
    {*}
}

rule while_statement {
    $<sym>=['while'|'until'] <EXPR> <block>
    {*}
}

rule repeat_statement {
    'repeat' <block> $<sym>=['while'|'until'] <EXPR>
    {*}
}

rule for_statement {
    $<sym>=['for'] <EXPR> <block>
    {*}
}


rule return_statement {
    $<sym>=['return']
    [ <EXPR> || <panic: 'Unable to parse argument to "return"'> ]
    {*}
}

rule make_statement {
    $<sym>=['make']
    [ <EXPR> || <panic: 'Unable to parse argument to "make"'> ]
    {*}
}

rule inline_pir_statement {
    'PIR'
    [ <quote> {*}                              #= quote
    | 'q:to:' [ \' (<-['\n]>+) \' | \" (<-["\n]>+) \" | \< (<-[\>\n]>+) \> ]
      [:sigspace(0):ratchet(0)
        ';' \h*: \n
        $<text>=[ [\N*:\n]*? ]
        ^^ \h*: $0 \h*: \n
        <.MARK_STATEMENT_END>
      ] {*}                                    #= heredoc
    ]
}


##  Parse a block.  If the closing brace is at the end of a line,
##  then we call <.MARK_STATEMENT_END> so that any following
##  <statement_end> rule can succeed.

token block {
    '{'
    <statement_block>
    [ '}' || <panic: 'Missing "}"'> ]
    [ \h* [ \# \N* ]? \n  <.MARK_STATEMENT_END> ]?
    {*}
}


#### Subroutine and method definitions ####

rule routine_def {
    $<declarator>=['sub'|'method']
    <ident>
    '(' <signature> ')'
    <block>
    {*}
}

rule signature {
    ( <parameter> ( ',' | <?before ')'> ) )*
    {*}
}

token parameter {
    | [
        $<named>=[':'?]
        <param_var>
        $<quant>=[ <[ ? ! ]>? ]
      ] {*}                                                #= non-slurp
    | $<quant>=['*'?] <param_var> {*}                      #= slurp
}

token param_var {
    [
    | '$/'
    | <sigil> <ident>
    ]
    {*}
}


#### Terms ####

token term {
    <noun>
    <postfix>*
    {*}                                                    #= end
}

token postfix {
    <!afterws>
    [ <methodop> {*}                                       #= methodop
    | <postcircumfix> {*}                                  #= postcircumfix
    ]
}

token methodop {
    '.' <ident>
    '(' <arglist>
    [ <.ws> ')' || <panic: "Missing ')'"> ]
    {*}                                                    #= methodop
}

token postcircumfix {
    | '(' <arglist> ')' {*}                                #= ( )
    | '[' <EXPR> <.ws> ']' {*}                             #= [ ]
    | '{' <EXPR> <.ws> '}' {*}                             #= { }
    | '<' <string_literal: '>' > '>' {*}                     #= < >
}

rule arglist {
    <EXPR>?
    {*}
}

token noun {
    | <colonpair> {*}                                      #= colonpair
    | <package_declarator> {*}                             #= package_declarator
    | <scope_declarator> {*}                               #= scope_declarator
    | <routine_def> {*}                                    #= routine_def
    | <circumfix> {*}                                      #= circumfix
    | <variable> {*}                                       #= variable
    | <subcall> {*}                                        #= subcall
    | <value> {*}                                          #= value
    | <name> {*}                                           #= name
}


token colonpair {
    | ':' <ident> [ '(' <EXPR> <.ws> ')' ] {*}
}


rule package_declarator {
    $<sym>=[module|class]
    <name>
    [
    || ';' <statement_block> {*}                           #= statement_block
    || <block> {*}                                         #= block
    ]
}


rule scope_declarator {
    $<declarator>=[my|our]
    <variable>
    {*}
}


token circumfix {
    | '(' <EXPR>? <.ws> ')' {*}                            #= ( )
    | '$(' <EXPR>? <.ws> ')' {*}                           #= $( )
    | '@(' <EXPR>? <.ws> ')' {*}                           #= @( )
}


token variable {
    | '$<' ( <-[\>]>* ) '>' {*}                            #= $< >
    | '$/' {*}                                             #= $/
    | <sigil> <twigil>? <ident> {*}                        #= $var
}

token sigil { <[$@%&]> }

token twigil { <[.!^*+?=]> }

token value {
    | <quote> {*}                                          #= quote
    | <number> {*}                                         #= number
    | <typename> {*}                                       #= typename
}

token quote {
    [ \'  <string_literal: '\''> \'
    | '"' <string_literal: '"'> '"'
    | 'q' <.ws> '<' <string_literal: '>'> '>'
    ]
    {*}
}


token typename {
    <name> {*}                                             #= name
}


token name {
    <ident> [ '::' <ident> ]*
    {*}
}


token number { \d+ {*} }


token subcall {
    <ident> '(' <arglist> ')'
    {*}
}



#### Expressions and operators ####

rule EXPR is optable { ... }

## Terms
proto 'term:' is precedence('z=')
    is parsed(&term)
    { ... }

## Postfix operators
proto postfix:<++> is looser('term:')
    is pasttype('inline')
    { ... }
proto postfix:<--> is equiv(postfix:<++>)
    is pasttype('inline')
    { ... }

## Unary prefix operators
proto prefix:<+> is looser(postfix:<++>)
    is pirop('set N*')
    { ... }
proto prefix:<~> is equiv(prefix:<+>)
    is pirop('set S*')
    { ... }
proto prefix:<-> is equiv(prefix:<+>)
    is pirop('neg')
    { ... }
proto prefix:<!> is equiv(prefix:<+>)
    is pirop('isfalse')
    { ... }
proto prefix:<?> is equiv(prefix:<+>)
    is pirop('istrue')
    { ... }

## Multiplicative operators
proto infix:<*> is looser(prefix:<+>)
    is pirop('mul')
    { ... }
proto infix:</> is equiv(infix:<*>)
    is pirop('div')
    { ... }
proto infix:<%> is equiv(infix:<*>)
    is pirop('mod')
    { ... }


## Additive operators
proto infix:<+> is looser(infix:<*>)
    is pirop('add')
    { ... }
proto infix:<-> is equiv(infix:<+>)
    is pirop('sub')
    { ... }

## Concatenation operators
proto infix:<~> is looser(infix:<+>)
    is pirop('concat')
    { ... }

## Comparison operators
proto infix:<==> is looser(infix:<+>)
    is pirop('iseq INn')
    { ... }
proto infix:<!=> is equiv(infix:<==>)
    is pirop('isne INn')
    { ... }
proto infix:«>=» is equiv(infix:<==>)
    is pirop('isge INn')
    { ... }
proto infix:«<=» is equiv(infix:<==>)
    is pirop('isle INn')
    { ... }
proto infix:«>» is equiv(infix:<==>)
    is pirop('isgt INn')
    { ... }
proto infix:«<» is equiv(infix:<==>)
    is pirop('islt INn')
    { ... }

proto infix:<eq> is equiv(infix:<==>)
    is pirop('iseq ISs')
    { ... }
proto infix:<ne> is equiv(infix:<==>)
    is pirop('isne ISs')
    { ... }
proto infix:<lt> is equiv(infix:<==>)
    is pirop('islt ISs')
    { ... }
proto infix:<le> is equiv(infix:<==>)
    is pirop('isle ISs')
    { ... }
proto infix:<gt> is equiv(infix:<==>)
    is pirop('isgt ISs')
    { ... }
proto infix:<ge> is equiv(infix:<==>)
    is pirop('isge ISs')
    { ... }
proto infix:<=:=> is equiv(infix:<==>)
    is pirop('issame')
    { ... }

## Tight logical operators
proto infix:<&&> is looser(infix:<==>)
    is pasttype('if')
    { ... }
proto infix:<||> is looser(infix:<&&>)
    is pasttype('unless')
    { ... }

## Conditional
proto ternary:<?? !!> is looser(infix:<||>)
    is pasttype('if')
    { ... }

## Binding
proto infix:<:=> is looser(ternary:<?? !!>)
    is lvalue(1)
    is pasttype('bind')
    { ... }

## Comma
proto infix:<,> is looser(infix:<:=>)
    is assoc('list')
    is pasttype('list')
    is nullterm
    { ... }

## loose logical operators
proto infix:<and> is looser(infix:<,>)
    is pasttype('if')
    { .... }

proto infix:<or> is looser(infix:<and>)
    is pasttype('unless')
    { ... }
