program = line+ <program>
        | eof <program>

line = comment
     | opt_space exp:expression opt_space comment? (eof | eol | eob) <simple_exp>
     | empty_line

empty_line = opt_space eol

expression = regex | binary_operation
           | index_set | index_get | assignment
           | paren_exp !(/\.|\(|->/) <simple_exp>
           | method_access | method_invocation | number | string
           | function_definition | array | hash
           | unary_operation

paren_exp  = "(" opt_space exp:expression opt_space ")" <simple_exp>

assignment = field_access space "=" spaceorbreak (function_definition !"(" | method_access) <field_assign>
           | field_access space "=" spaceorbreak expression <field_assign>
           | var:identifier space "=" spaceorbreak expression <var_assign>

field_access = method_chain (identifier | operator) <field_access>

index_set = indexed_expression sindexes:("[" expression "]")+ opt_space "=" spaceorbreak expression <index_set>

index_get = indexed_expression gindexes:("[" index_inner_arg_list "]")+ !"." iargs:simple_arg_list? <index_get>

indexed_expression = array | hash | paren_exp | method_invocation | string | unary_operation

method_access = "->" meth:(identifier) !"." <simple_meth_access>
              | target:method_invocation "->" meth:(identifier | operator) <meth_access>
              | paren_exp "->" meth:(identifier | operator) <paren_meth_access>
              | "->" paren_exp <simple_paren_meth_access>

#-- Basic literals

number  = /-?[0-9]+(\.[0-9]+)?/ <bnumber>

array   = "[" spaceorbreak inner:array_inner spaceorbreak "]" <barray>
        | "[" spaceorbreak "]" <empty_array>

array_inner = first:expression rest:(((spaceorbreak "," spaceorbreak) | (space | eol | comment)+) expression)* <array_inner>

hash    = "[" opt_space ":" opt_space "]" <empty_hash>
        | "[" spaceorbreak inner:hash_inner spaceorbreak "]" <bhash>

hash_inner = first:hash_argument rest:(((spaceorbreak "," spaceorbreak) | ((space | eol | comment)+)) hash_argument)* <array_inner>

hash_argument = key:((identifier | operator) (identifier | operator | number)*) ":" spaceorbreak value:expression <hash_key_arg>
              | key:expression spaceorbreak ":" spaceorbreak value:expression <hash_arg>

regex   = "/" body:("\\/" | !"/" . )* "/" opts:/[mix]*/ <bregex>

string  = ("''" | "\"\"") <empty_string>
        | "\"" values:(/[^#"\\]+/ | string_interpolation | "\\\"" | "\\\\" | !"\"" .)* ("\"" | missing_end_quote) <string_interp>
        | "'" svalue:("\\'" | "\\\\" | !"'" .)+ ("'" | missing_end_quote) <simple_string>
        | symbol

symbol = ":" svalue:(identifier | operator | number)+ <simple_symbol>
       | (":''" | ":\"\"") <empty_symbol>
       | ":'" svalue:("\\'" | "\\\\" | !"'" .)+ ("'" | missing_end_quote) <simple_symbol>
       | ":\"" svalue:("\\\"" | "\\\\" | !"\"" .)+ ("\"" | missing_end_quote) <double_symbol>

string_interpolation = "#" "{" opt_space first:expression? opt_space rest:(eol opt_space expression space?)* spaceorbreak "}" <interp_value>

block_comment = opt_space "#*" (!"*#" (block_comment | .))* ("*#" | eof)

comment = block_comment
        | opt_space "#" (!("\n" | eof) .)*

#-- Function definitions

function_definition = "{" opt_space args:formal_args? opt_space body:line* opt_space ("}" | missing_bracket_function) <bfunction>

formal_args = opt_space plain_formals opt_space "|" !"|"
            | opt_space default_args opt_space "|" !"|"
            | opt_space variable_args opt_space "|" !"|"
            | opt_space plain_formals opt_space "," opt_space variable_args opt_space "|" !"|"
            | opt_space default_args opt_space "," opt_space variable_args opt_space "|" !"|"
            | opt_space plain_formals opt_space "," opt_space default_args opt_space "," opt_space variable_args opt_space "|" !"|"
            | opt_space plain_formals opt_space "," opt_space default_args opt_space "|" !"|"
            | "|"

plain_formals = plain_arg rest:rest_formals

rest_formals = (space? "," opt_space plain_arg !(space "="))*

plain_arg = identifier <plain_arg>

default_args = default_arg (space? "," opt_space default_args)*

default_arg = arg_var:identifier space "=" space arg_val:default_arg_rhs_expression <default_arg>

default_arg_rhs_expression = index_get | paren_exp | method_invocation | number | string | function_definition | array | hash

variable_args = "*" arg_var:identifier <variable_args>

#-- Unary and binary operations

unary_operation = !("-" number) operator !space unary_rhs_expression <unary_op>

unary_rhs_expression = index_get | paren_exp | number | string | array | hash | method_invocation | regex

binary_operation = binary_operation_chain expression <binary_op>

binary_operation_chain = (binary_lhs_expression space operator ((space? eol space?) | space))+ <binary_op_chain>

binary_lhs_expression = simple_method_chain | index_get | paren_exp | string | array | hash | regex | number | method_invocation | unary_operation

#-- Basic building blocks

identifier = /[a-zA-Z](?:(?!->)[a-zA-Z0-9_!?\-*+^&@~\/\\><$%])*/

operator = !("->" !operator) !("=" !(operator | "=")) ("!=" | ">=" | "<=" | /[!?\-*+^@~\/\\><$_%\=]/ | "||" | "|" | "&&" | "&")+

#-- Calling methods and functions

method_invocation = method_chain (identifier | operator) arg_list <chain_call>
                  | simple_method_invocation
                  | m_name:identifier alist:arg_list <simple_call>
                  | paren_exp simple_arg_list <paren_call>
                  | function_literal_invocation

simple_method_invocation =  m_name:identifier alist:simple_arg_list <simpler_call>

simple_method_chain = method_chain (identifier | operator) simple_arg_list <chain_call>
                    | method_chain (identifier | operator) &space <chain_call>
                    | simple_method_invocation
                    | identifier &space <simplest_call>

function_literal_invocation = func:function_definition alist:simple_arg_list <func_lit_call>

method_chain = (method_lhs ".")+ <method_chain>

method_lhs = var:identifier !space args:arg_list ("[" opt_space index_args:inner_arg_list opt_space "]")* <simple_meth_lhs>
           | target:array indexes:("[" opt_space index_args:inner_arg_list opt_space "]")+ <array_index_lhs>
           | function_definition !space simple_arg_list
           | function_definition &"."
           | "->" var:identifier <access_meth_lhs>
           | method_target_expression

method_target_expression = array | hash | paren_exp | number | string | regex | unary_operation

#-- Argument lists

arg_list = "(" opt_space inner_arg_list opt_space  (")" | missing_end_parenthesis)
         | "(" opt_space (")" | missing_end_parenthesis)
         | space inner_arg_list
         | !"(" &(space?)

simple_arg_list = "(" opt_space inner_arg_list opt_space (")" | missing_end_parenthesis)
                | "(" opt_space (")" | missing_end_parenthesis)

inner_arg_list = arg_first arg_next* <inner_arg_list>

arg_first = named_argument | expression

arg_space = (space | eol_not_semicolon)

arg_next = arg_space* "," arg_space* arg_first
         | arg_space+ function_definition
         | arg_space+ named_argument
         | opt_space expression

named_argument = key:identifier ":" spaceorbreak value:expression <named_argument>
               | key:string ":" spaceorbreak value:expression <named_argument>
               | key:(method_target_expression | simple_method_invocation) space ":" space spaceorbreak value:expression <named_argument>

index_inner_arg_list = arg_first inner_arg_next* <inner_arg_list>

inner_arg_next = arg_next
               | arg_space+ arg_first

#-- Errors

missing_end_quote = %Missing end quote mark for string%

missing_end_slash = %Regular expression missing end `/`%

missing_bracket_function = %Missing end bracket for function definition%

missing_end_parenthesis = %Missing closing parenthesis for function arguments%

#-- Spaces

spaceorbreak = opt_space eol? (comment eol)* space?

eol_not_semicolon = ("\n" | "\r\n")+ | comment

opt_space = /(?: |\t)*/

space   = /(?: |\t)+/

eol     = /(?:\n|;|\r\n)+/

eob     = opt_space &"}" space?

eof     = (eol | space)? !.