# Copyright (C) 2006-2009, The Perl Foundation.
# $Id: /mirror/trunk/languages/pipp/src/pct/grammar.pg 35842 2009-01-20T15:34:45.244775Z bernhard  $

# PHP grammar for Pipp PCT
grammar Pipp::Grammar is PCT::Grammar;

# the starting rule
token TOP {
    {*}                                     #= open
    <sea_or_code>*
    [ $ || <.panic: 'Syntax error'> ]
    {*}                                     #= close
}

# Whitespace and comments

token ws_char           { \h | \v }

token singlelinecomment { [ '#' || '//' ] \N* }    # end of line comment like Unix shell and C++

token multilinecomment  { '/*' .*?  '*/' }         # C-like multiline comment

token ws_all {
    | <.ws_char>
    | <singlelinecomment>
    | <multilinecomment>
}

# whitespace rule used implicity by rules
token ws {
    | <!ww> <ws_all>+
    | <ws_all>*
}

# text or PHP-code
token sea_or_code {
    | <code_echo_tag>   {*}    #= code_echo_tag
    | <code_short_tag>  {*}    #= code_short_tag
    | <code_script_tag> {*}    #= code_script_tag
    | <SEA>             {*}    #= SEA
}

# text, in most cases broken HTML,
# when an '<' is encountered check whether this is the start of PHP-code
regex SEA {
    '<'? .+? ( <before '<'> | $ )
    {*}
}

rule code_echo_tag {
    <.code_start_echo_tag> <argument_list> <.statement_delimiter>
        <statement_list>
    <.close_short_tag>?
    {*}
}

rule code_script_tag {
    <.open_script_tag>
        <statement_list>
    <.close_script_tag>?
    {*}
}

rule code_short_tag {
    <.open_short_tag>
        <statement_list>
    <.close_short_tag>?
    {*}
}

regex open_script_tag {
    '<script' <ws_char>+: 'language' <ws_char>*: '=' <ws_char>*: <quoted_lang_name> <ws_char>*: '>'
    {{ $P0 = new 'String'
       $P0 = "script"
       set_global "tag_type", $P0
    }}
}

regex close_script_tag {
    '</script' <ws_char>*: '>'
    {{  $S0 = "script"
        $P0 = get_global "tag_type"
        $S1 = $P0
        if $S0 != $S1 goto fail
        .return(1)
    fail:
    }} <.fail>
}

token quoted_lang_name { '"php"' | '\'php\'' }

token code_start_echo_tag {
    '<?='
    {{ $P0 = new 'String'
       $P0 = "short"
       set_global "tag_type", $P0
    }}
}

token open_short_tag {
    '<?' 'php'?
    {{ $P0 = new 'String'
       $P0 = "short"
       set_global "tag_type", $P0
    }}
}

token close_short_tag {
    '?>' \n?
    {{  $S0 = "short"
        $P0 = get_global "tag_type"
        $S1 = $P0
        if $S0 != $S1 goto fail
        .return(1)
    fail:
    }} <.fail>
}

# statements

rule statement {
    | <namespace_definition>           {*}  #= namespace_definition
    | <return_statement>               {*}  #= return_statement
    | <require_once_statement>         {*}  #= require_once_statement
    | <echo_statement>                 {*}  #= echo_statement
    | <expression_statement>           {*}  #= expression_statement
    | <constant_definition>            {*}  #= constant_definition
    | <if_statement>                   {*}  #= if_statement
    | <while_statement>                {*}  #= while_statement
    | <do_while_statement>             {*}  #= do_while_statement
    | <for_statement>                  {*}  #= for_statement
    | <inline_sea_short_tag>           {*}  #= inline_sea_short_tag
    | <inline_sea_script_tag>          {*}  #= inline_sea_script_tag
    | [ <var_assign> ';' ]             {*}  #= var_assign
    | <function_definition>            {*}  #= function_definition
    | <class_definition>               {*}  #= class_definition
}

rule statement_list {
    <statement>* {*}
}

rule statement_delimiter {
    | ';'
    | <before '?'>
    | $
}

rule namespace_definition {
    'namespace' <namespace_name>?  {*}       #= open
    '{' <statement_list> '}'       {*}       #= close
}

# return can appear inside and outside functions
token return_statement {
    'return' <.ws_char> <expression> <.statement_delimiter>
    {*}
}

token require_once_statement {
    'require_once' <.ws_char> <quote> <ws> <.statement_delimiter>
    {*}
}

token echo_statement {
    'echo' <.ws_char> <argument_list> <ws> <.statement_delimiter>
    {*}
}

rule expression_statement {
    <expression> <.statement_delimiter>
    {*}
}

rule argument_list {
    [ <expression> [',' <expression>]* ]?
    {*}
}

rule conditional_expression {
    '(' <expression> ')' '{' <statement_list> '}'
    {*}
}

rule if_statement {
    'if' <conditional_expression>
    <elseif_clause>*
    <else_clause>?
    {*}
}

rule else_clause {
    'else' '{' <statement_list> '}'
}

rule elseif_clause {
    'elseif' <conditional_expression>
    {*}
}

rule while_statement {
    'while' <conditional_expression>
    {*}
}

rule do_while_statement {
    'do' '{' <statement_list> '}' 'while' '(' <expression> ')' <.statement_delimiter>
    {*}
}

rule for_statement {
    'for' '(' <var_assign> ';' <expression> ';' <expression> ')' '{' <statement_list> '}'
    {*}
}

token inline_sea_short_tag {
    <.close_short_tag>
        <SEA_empty_allowed>
    <.open_short_tag>
    {*}
}

token inline_sea_script_tag {
    <.close_script_tag>
        <SEA_empty_allowed>
    <.open_script_tag>
    {*}
}

regex SEA_empty_allowed {
    <-[<]>*? <before '<'>
}

rule var_assign {
    <var> '=' <expression>
    {*}
}


# identifiers

token class_name {
    <ident>
}

token function_name            { <ident> }

token interface_name           { <ident> }

token member_name              { <ident> }

token method_name              { <ident> }

token namespace_name           { <ns_path> <ident> }

token name {
    <leading_backslash=ns_sep>?           # a leading backslash is optional, but significant
    <ns_path>                             # namespace
    <ident>                               # identifier is required
}

token ns_sep                   { '\\' }   # yes, PHP 5.3 uses backslash as the namespace separator

token ns_path                  {
    [ <ident> <.ns_sep> ]*
} 

token var_name                 { '$' <ident> }

# terms
rule method_call {
    <var> '->' <method_name> '(' <argument_list> ')'
    {*}
}

rule closure_call {
    <var> '(' <argument_list> ')'
    {*}
}

rule function_call {
    <function_name> '(' <argument_list> ')'
    {*}
}

# a trailing ',' is allowed
rule instantiate_array {
    'array' '(' [ <array_argument> [ ',' <array_argument> ]* ]? ','? ')'
    {*}
}

rule array_argument {
    | <key_value_pair>  {*}    #= key_value_pair
    | <expression>      {*}    #= expression
}

rule key_value_pair {
    <key=expression> '=>' <value=expression>
    {*}
}

rule constructor_call {
    'new' <class_name> [ '(' <argument_list> ')' ]?
    {*}
}

token true {
    [:i 'TRUE' ]
    {*}
}

token false {
    [:i 'FALSE']
    {*}
}

token null {
    'NULL'
    {*}
}

token number {
    ( \d+ )? '.' \d+
    {*}
}

token integer {
    \d+
    {*}
}

token quote {
    [ <.before '"' > <quote_expression: :qq>
    | <.before \'  > <quote_expression: :q>
    ]
    {*}
}

token constant {
    <name>
    {*}
}

token class_constant {
    <class_name> '::' <name>
    {*}
}

token static_member {
    'self' '::' '$' <ident>
    {*}
}

rule this {
    '$this'
    {*}
}

rule member {
    <this> '->' <member_name>
    {*}
}

rule array_elem {
    <var_name> '[' <expression> ']'
    {*}
}

token simple_var {
    <var_name>
    {*}
}

token var {
    | <this>        {*}   #= this
    | <array_elem>  {*}   #= array_elem
    | <simple_var>  {*}   #= simple_var
}

rule literal {
    | <true>    {*}    #= true
    | <false>   {*}    #= false
    | <null>    {*}    #= null
    | <number>  {*}    #= number
    | <integer> {*}    #= integer
    | <quote>   {*}    #= quote
}

rule term {
    | <method_call>            {*}   #= method_call
    | <closure>                {*}   #= closure
    | <closure_call>           {*}   #= closure_call
    | <function_call>          {*}   #= function_call
    | <instantiate_array>      {*}   #= instantiate_array
    | <constructor_call>       {*}   #= constructor_call
    | '(' <var_assign> ')'     {*}   #= var_assign
    | '(' <expression> ')'     {*}   #= expression
    | <literal>                {*}   #= literal
    | <static_member>          {*}   #= static_member
    | <class_constant>         {*}   #= class_constant
    | <constant>               {*}   #= constant
    | <member>                 {*}   #= member
    | <var>                    {*}   #= var
}


# declarations
rule closure {
    'function' <param_list> <bind_list>?  {*}   #= open
    '{' <statement_list> '}'              {*}   #= close
}

rule bind_list {
    'use' '(' [ '&'? <var_name> [',' '&'? <var_name>]* ]? ')'
}

rule function_definition {
    'function' <function_name> <param_list> {*}  #= open
    '{' <statement_list> '}'                {*}  #= close
}

rule param_list {
    '(' [ '&'? <var_name> [',' '&'? <var_name>]* ]? ')'
    {*}
}

# OO

rule interface_definition {
    'interface' <interface_name> [ 'implements' <interface_name> ]? '{'
        <method_declaration>*
    '}'
    {*}
}

rule method_declaration {
    'function' <method_name> <param_list> ';'
    {*}
}

rule class_definition {
    'class' <class_name> [ 'implements' <interface_name> ]?  {*}  #= open
    '{' <class_member_or_method_definition>* '}'             {*}  #= close
}

rule class_member_or_method_definition {
    | <constant_definition> {*}                  #= constant_definition
    | <class_member_definition> {*}              #= class_member_definition
    | <class_static_member_definition> {*}       #= class_static_member_definition
    | <class_method_definition> {*}              #= class_method_definition
}

# In PHP 5.3 a constant definition outside a declared namespace or class is forbidden
# In Pipp this is not checked
rule constant_definition {
    'const' <ident> '=' <literal> <.statement_delimiter>
    {*}
}

rule class_member_definition {
    'public' <var_name> '=' <literal> <.statement_delimiter> {*}
}

rule class_static_member_definition {
    'public' 'static' <var_name> '=' <literal> <.statement_delimiter> {*}
}

rule class_method_definition {
    'function' <method_name> <param_list> {*}  #= open
    '{' <statement_list> '}'              {*}  #= close
}

token curly_interpolation {
    '{' <var> '}'
    {*}
}


# operator precedence table
rule expression is optable                                    { ... }

proto postfix:'++' is precedence('1')                         { ... }
proto postfix:'--' is equiv(postfix:<++>)                     { ... }
proto prefix:'++'  is equiv(postfix:<++>)                     { ... }
proto prefix:'--'  is equiv(postfix:<++>)                     { ... }

proto infix:'|'    is looser('postfix:++')                    { ... }
proto infix:'&'    is equiv('infix:|')                        { ... }
proto infix:'^'    is equiv('infix:|')                        { ... }

proto infix:'<'    is tighter('infix:|')                      { ... }
proto infix:'<='   is equiv('infix:<')                        { ... }
proto infix:'>'    is equiv('infix:<')                        { ... }
proto infix:'>='   is equiv('infix:<')                        { ... }
proto infix:'=='   is equiv('infix:<')                        { ... }
proto infix:'!='   is equiv('infix:<')                        { ... }

proto infix:'+'    is tighter('infix:<')                      { ... }
proto infix:'-'    is equiv('infix:+')                        { ... }
proto infix:'.'    is equiv('infix:+')    is pirop('concat')  { ... }

proto infix:'*'    is tighter('infix:+')                      { ... }
proto infix:'%'    is equiv('infix:*')                        { ... }
proto infix:'/'    is equiv('infix:*')                        { ... }

proto prefix:'-'   is tighter('infix:*')                      { ... }
proto prefix:'+'   is equiv('prefix:-')                       { ... }
proto prefix:'!'   is equiv('prefix:-')                       { ... }

proto term:''      is tighter('prefix:-') is parsed(&term)    { ... }
