=head1 NAME

pir.pg - A PIR grammar for PGE.

TODO:

 1. fix Heredocs parsing
 2. Test and fix things

=cut


grammar PIR::Grammar;


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

rule program {
  <?nl>*
  <compilation_unit>
  [ <?nl> <compilation_unit> ]*
  [ <?nl> | <warning: no newline at end of file> ]
}

rule compilation_unit {   
    <global_def>
  | <sub_def>
  | <const_def>
  | <macro_def>
  | <pragma>
  | <emit>
}


=head2 Sub defs, sub pragmas, sub body and parameters

=cut

rule sub_def {
  [ <'.sub'> | <'.pcc_sub'> ]
  [ <sub_id> | <syntax_error: sub identifier (id or string constant) expected> ]
  <sub_pragmas>?
  [ <?nl>    | <syntax_error: newline expected> ]
  <body>
}

rule sub_id {
  [ <id> | <string_constant> ]
}

=head3 sub_pragmas

The comma to separate the sub pragmas is optional.

=cut

rule sub_pragmas {
  <sub_pragma>
  [
    [ <','> ]?
    <sub_pragma>
  ]*
}

rule sub_pragma {
    <':load'>
  | <':init'>
  | <':immediate'>
  | <':main'>
  | <':anon'>
  | <':lex'>
  | <wrap_pragma>
  | <vtable_pragma>
  | <multi_pragma>
  | <outer_pragma>
}

=head3 wrap_pragma

This one is not sure yet

=cut

rule wrap_pragma {
  <':wrap'> <parenthesized_string>  
}

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

rule parenthesized_string {
  <'('>
  [ <string_constant> | <syntax_error: string constant expected> ]
  [ <')'>             | <syntax_error: ')' expected> ]
}

rule multi_pragma {
  <':multi'>
  [ <'('>    | <syntax_error: '(' expected after ':multi' flag> ]
  <multi_types>?
  [ <')'>    | <syntax_error: ')' expected to close ':multi' flag> ]
}

rule outer_pragma {
  <':outer'>
  [ <'('>    | <syntax_error: '(' expected after ':outer' flag> ]
  [ <string_constant>
  | <id>
  | <syntax_error: identifier for outer sub expected>
  ]
  [ <')'>    | <syntax_error: ')' expected to close ':outer' flag> ]
}

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

rule multi_type {
    <type>
  | <'_'>
  | <keylist>
  | <id>
  | <string_constant>
}

rule body {
  <param_decl>*
  <labeled_pir_instr>*
  [ <'.end'> | <syntax_error: '.end' expected to close method/sub body> ]
}

rule param_decl {
  <'.param'>
  [ [ <type> <id> ]
  | <reg>
  | <syntax_error: parameter type or register expected>
  ]
  [ <get_flags> | <':unique_reg'> ]*
  [ <?nl>   | <syntax_error: newline expected after parameter declaration> ]
}





=head2 PIR instructions

=cut

rule labeled_pir_instr {
  [ <label> <instr>?
  | <instr>
  ] 
  [ <?nl> | <syntax_error: newline expected after instruction> ]
}

rule labeled_pasm_instr {
  [ <label> <pasm_instr>?
  | <pasm_instr>
  ] 
  [ <?nl> | <syntax_error: newline expected after instruction> ]
}

rule instr {
  [ <pir_instr> | <pasm_instr> ]
  
}



rule pir_instr {
    <local_decl>
  | <sym_decl>
  | <lexical_decl>
  | <const_def>
  | <globalconst_def>
  | <conditional_stat>
  | <assignment_stat>
  | <open_namespace>
  | <close_namespace>
  | <return_stat>
  | <sub_invocation>
  | <macro_invocation>
  | <jump_stat>  
  | <source_info>  
}

rule macro_invocation {
  <macro_id> <parenthesized_args>?
}


=head2 Local declarations

Local declarations can be done using C<.sym> or C<.local> in normal context.
In macro context however, only C<.sym> is allowed, and the C<.local> keyword
indicates a label declaration. 

=cut

rule local_decl {
  <'.local'>
  [ <type> | <syntax_error: type for local symbol expected> ]
  <local_id_list>
}

rule sym_decl {
	<'.sym'>
	[ <type> | <syntax_error: type for local symbol expected> ]
  <local_id_list>
}


rule local_id_list {
  <local_id> [ <','> <local_id> ]*
}

rule local_id {
  <id> <local_flag>?
}

rule local_flag {  
  <':unique_reg'>
}

rule lexical_decl {
  <'.lex'>
  [ <string_constant> | <syntax_error: string constant expected> ]
  [ <','>             | <syntax_error: ',' expected> ]
  [ <target>          | <syntax_error: register expected> ]
}


rule global_def {
  <'.global'> [ <id> | <syntax_error: identifier expected> ]
}

=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'> <const_assign>
      [ <int_constant>    | <syntax_error: integer constant expected> ]
    ]
  | [ <'num'> <const_assign>
      [ <float_constant>  | <syntax_error: floating point constant expected> ]
    ]
  | [ [ <'pmc'> | <macro_id> | <id> ] <const_assign>
      [ <string_constant> | <syntax_error: string constant expected> ]
    ]
  | [ <'string'> <const_assign>
      [ <string_constant> | <syntax_error: string constant expected> ]
    ]
  | <syntax_error: type expected for constant>
  ]
}

rule const_assign {
  [ <id>  | <syntax_error: constant identifier expected> ]
  [ <'='> | <syntax_error: '=' expected after constant identifier> ]
}


rule conditional_stat {
  [ <'if'> | <'unless'> ]
  <conditional_expr>
  [ <'goto'> | <syntax_error: 'goto' expected> ]
  [ <id> | <pasm_instr> | <syntax_error: label identifier expected> ]
}

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

rule jump_stat {
  <'goto'> [ <id> | <syntax_error: label expected after 'goto'> ]
}


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

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

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

rule unary_operator {
    <'!'>  
  | <'-'>
  | <'~'>
}


rule expression {
    [ <simple_expr> [ <binary_operator> <simple_expr> ]? ]
  | [ <unary_operator> <simple_expr> ]
}


rule simple_expr {
    <float_constant>
  | <int_constant>
  | <string_constant>
  | <target>
}


rule keylist {
  <'['>
  <key> [ [ <','> | <';'> ] <key> ]*
  [ <']'> | <syntax_error: ']' expected> ]
}

rule key {
    [ <simple_expr> [ <'..'> [ <simple_expr> ]? ]? ]
  | [ <'..'> <simple_expr> ]
}

rule assignment_stat {
    <target> <'='> <short_sub_call>
  | <target> <'='> <target> <keylist>
  | <target> <'='> <expression>
  | <target> <'='> <pasm_instruction> \N*
  #| <target> <'='> <pasm_op_1> <simple_expr>
  #| <target> <'='> <pasm_op_2> <simple_expr> \, <simple_expr>
  | <target> <'='> <'new'> [ <int_constant> | <string_constant> | <macro_id> ]
  | <target> <'='> <'new'> <keylist>
  | <target> <'='> <'find_type'> [ <string_constant> | <string_reg> | <id> ]
  | <target> <'='> <heredoc>
  | <target> <'='> <'global'> <string_constant> # deprecated?  
  | <target> <assign_operator> <simple_expr>
  | <target> <keylist> <'='> <simple_expr>  
  | <'global'> <string_constant> <'='> <target> # deprecated?
  | <result_var_list> [ <'='> | <syntax_error: '=' expected> ] <short_sub_call>
}



# pasm ops that take 1 argument
#
#rule pasm_op_1 {
#    clone
#  | compreg
#  | defined
#  | assign
#  | addr
#  | istrue
#  | isfalse
#  | isnull
#  #| others
#}
#
## pasm ops that take 2 arguments
##
#rule pasm_op_2 {
#    issame
#  | isntsame
#}
#

rule heredoc {
  <'<<'>
  [ <string_constant> | <syntax_error: heredoc label identifier expected> ]
  [ <?nl> | <syntax_error: newline expected after heredoc label> ]
  <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 {
  <'.pcc_begin'>
  [ <?nl>  | <syntax_error: newline after '.pcc_begin' expected> ]
  <arguments>
  [ [ <'.pcc_call'> | <'.nci_call'> ]
  | [ <invocant> <'.meth_call'> ]
  ]
  [ <target> | <syntax_error: id or register expected that holds the sub object> ]
  [ <?nl>    | <syntax_error: newline after '.pcc_call sub' expected> ]
  [ <local_decl> <?nl> ]*
  <result_values>
  [ <'.pcc_end'>  | <syntax_error: '.pcc_end' expected> ]  
}

rule invocant {
  <'.invocant'> 
  <target> 
  <?nl>          
}

rule short_sub_call {
  [ [ <target>\. ]                  
  | [ <target> <'->'> ]
  ]?                               # optional invocant
  [ <target> | <string_constant> ] # method or sub name/id
  <parenthesized_args>             # sub args  
  <process_heredocs>							 # process the list of heredoc labels, if any
  <clear_heredocs>		
}

rule sub_invocation {
  [ <long_sub_call> | <short_sub_call> ]
}

rule result_var_list {
  <'('>
  <result_var>
  [
    <','> [ <result_var> | <syntax_error: target to store result expected after ','> ]
  ]*
  [ <')'> | <syntax_error: ')' expected to close result target list> ]
}

rule result_var {
  <target> <get_flags>?
}

rule parenthesized_args {
  <'('>
  [
    <arg>
    [ <','>
      [ <arg> | <syntax_error: argument expected after ','> ]
    ]*
  ]?
  [ <')'> | <syntax_error: ')' expected to close argument list> ]
}

rule arg {
  [ <float_constant>
  | <int_constant>
  | <string_constant>
    [ <'=>'> 
      [ <target> | <syntax_error: target for named argument expected> ]
    ]?
  | <target>
  | <heredoc_id>
  ] <set_flags>?
}

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

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

rule arguments {
  [ <'.arg'>
    [ <simple_expr> | <syntax_error: argument expression expected> ]
    <set_flags>?
    [ <?nl> | <syntax_error: newline expected after .arg statement> ]
  ]*
}

rule result_values {
  [ <'.result'>
    [ <target> | <syntax_error: target expected to hold the result> ]
    <get_flags>?
    [ <?nl>  | <syntax_error: newline expected after .result statement> ]
  ]*
}

rule set_flags {
  [ <':flat'>
  | <named_flag>
  ]+
}

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

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

rule return_stat {  
    <long_return_stat>
  | <short_return_stat>
  | <long_yield_stat> 
  | <short_yield_stat>
  | <tail_call>
}

rule long_return_stat {
  <'.pcc_begin_return'>
  [ <?nl> | <syntax_error: newline expected after '.pcc_begin_return'> ]
  [ <return_directive> ]*
  [ <'.pcc_end_return'> | <syntax_error: '.pcc_end_return' expected> ]
}

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

rule long_yield_stat {
  <'.pcc_begin_yield'>
  [ <?nl> | <syntax_error: newline expected after '.pcc_begin_yield'> ]
  [ <return_directive> ]*
  [ <'.pcc_end_yield'> | <syntax_error: '.pcc_end_yield' expected> ]
}

rule return_directive {
  <'.return'>
  [ <simple_expr> | <syntax_error: simple expression expected> ]
  <set_flags>?
  [ <?nl> | <syntax_error: newline expected after return directive> ]
}

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

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

rule open_namespace {
  <'.namespace'> [ <id> | <syntax_error: namespace identifier expected> ]
}

rule close_namespace {
  <'.endnamespace'> [ <id> | <syntax_error: namespace identifier expected> ]
}


rule emit {
  <'.emit'>
  [ <?nl>        | <syntax_error: newline expected> ]
  <labeled_pasm_instr>*
  [ <'.eom'>     | <syntax_error: '.eom' expected> ]
}


rule macro_def {
  <'.macro'>
  [ <id>  | <syntax_error: macro identifier expected> ]
  <macro_parameters>?
  [ <?nl> | <syntax_error: newline expected after macro parameter list> ]
  <macro_body>

}

rule macro_parameters {
  <'('>
  <id_list>?
  [ <')'>   | <syntax_error: ')' expected> ]
}

=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>
}



=head2 Pragmas

=cut

rule pragma {
    <include>
  | <new_operators>
  | <loadlib>
  | <namespace>
  | <hll_mapping>
  | <hll_specifier>  
  | <source_info>
}

rule include {
  <'.include'> [ <string_constant> | <syntax_error: string constant expected> ]
}

rule new_operators {
  <'.pragma'> <'n_operators'> [ <int_constant> | <syntax_error: integer constant expected> ]
}

rule loadlib {
  <'.loadlib'> [ <string_constant> | <syntax_error: library name expected> ]
}

rule namespace {
  <'.namespace'> 
  [ <'['>        
    [ <namespace_id>    | <syntax_error: namespace identifier expected> ]
    [ <']'>             | <syntax_error: ']' expected> ]
  ]?
}

rule hll_specifier {
  <'.HLL'>
  [ <string_constant> | <syntax_error: HLL name expected> ]
  [ <','>             | <syntax_error: ',' expected> ]
  [ <string_constant> | <syntax_error: HLL type library expected> ]
}

rule hll_mapping {
  <'.HLL_map'>
  [ <int_constant>    | <syntax_error: Parrot Core type expected> ]
  [ <','>             | <syntax_error: ',' expected> ]
  [ <int_constant>    | <syntax_error: HLL type expected> ]
}

rule namespace_id {
  <string_constant>
  [ <';'>
    [ <string_constant> | <syntax_error: string constant expected> ]
  ]*
}

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



rule id_list {
  <id>
  [
    <','>
    [ <id> | <syntax_error: identifier expected> ]
  ]*
}



=head2 Tokens

=head3 normal_label

this is a token, because no spaces are allowed between the id and the colon.

=cut

token normal_label {
 	<id> <':'>
}


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

rule macro_label_decl {
		<'.local'> [ <macro_label> | <syntax_error: $LABEL: expected> ]
	|	<normal_label>
}

token int_constant {
    <binary_constant>
  | <hex_constant>
  | [-]? \d+
}

token hex_constant {
  0x\d+
}

token binary_constant {
  0b\d+
}

token float_constant {
  [-]? \d+\.\d+
}


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

rule encoding_specifier {
	<'utf8:'>
}

rule charset_specifier {
  [ <'ascii:'> | <'binary:'> | <'unicode:'> | <'iso-8859-1:'> ]  
}

regex stringdouble { <PGE::Text::bracketed: "> }
regex stringsingle { <PGE::Text::bracketed: '> }

## TODO: add other built-in types
rule type {
  [ <'int'> 
  | <'num'> 
  | <'pmc'> 
  | <'object'>
  | <'string'>   
  | <macro_id>
  ]
  #| <'Array'> 
  #| <'Hash'> 
  
}

token normal_target {
	<id> | <reg> 
}

=head2 Macro_target

In a macro, a target can also be a macro_id

=cut

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

token id {
  <!keyword> \w+
}

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

token reg {
  [ <int_reg> | <num_reg> | <pmc_reg> | <string_reg> ]
}

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

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

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

token num_reg {
    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_directive>
  | <pasm_instruction> # pasm instructions may be identifiers in IMCC
  | <reserved>
}

token reserved {
		int
	| num
	| pmc
	| string
	| self
}
	

=head2 PIR directives

TODO: put this in <%pir_directive> PIR directives

=cut

token pir_directive {
    <'.arg'>    
  | <'.const'>
  | <'.emit'>
  | <'.end'>
  | <'.endnamespace'>
  | <'.eom'>  
  | <'.get_results'>
  | <'.globalconst'>
  | <'.global'>
  | <'.HLL_map'>
  | <'.HLL'>  
  | <'.include'>
  | <'.invocant'>
  | <'.lex'>
  | <'.line'>
  | <'.loadlib'>
  | <'.local'>
  | <'.meth_call'>
  | <'.namespace'>
  | <'.nci_call'>
  | <'.param'>
  | <'.pcc_begin_return'>
  | <'.pcc_begin_yield'>
  | <'.pcc_begin'>
  | <'.pcc_call'>
  | <'.pcc_end_return'>
  | <'.pcc_end_yield'>
  | <'.pcc_end'>  
  | <'.pcc_sub'>
  | <'.pragma'>
  | <'.return'>
  | <'.sub'>
  | <'.sym'>  
  | <'.result'>  
  | <'.yield'>
}
