module Treetop

    module Compiler
      grammar Metagrammar
        rule treetop_file
          requires:(space? require_statement)* prefix:space? module_or_grammar suffix:space? {
            def compile
              requires.text_value + prefix.text_value + module_or_grammar.compile + suffix.text_value
            end
          }
        end

        rule require_statement
          prefix:space? "require" [ \t]+ [^\n\r]+ [\n\r]
        end

        rule module_or_grammar
          module_declaration / grammar
        end

        rule module_declaration
          prefix:('module' space name:([A-Z] alphanumeric_char* ('::' [A-Z] alphanumeric_char*)*) space) module_contents:(module_declaration / grammar) suffix:(space 'end') {
            def compile
              prefix.text_value + module_contents.compile + suffix.text_value
            end

            def parser_name
              prefix.name.text_value+'::'+module_contents.parser_name
            end
          }
        end

        rule grammar
          'grammar' space grammar_name space ('do' space)? declaration_sequence space? 'end' <Grammar>
        end

        rule grammar_name
          ([A-Z] alphanumeric_char*)
        end

        rule declaration_sequence
          head:declaration tail:(space declaration)* <DeclarationSequence> {
            def declarations
              [head] + tail
            end

            def tail
              super.elements.map { |elt| elt.declaration }
            end
          }
          /
          '' {
            def compile(builder)
            end
          }
        end

        rule declaration
          parsing_rule / include_declaration
        end

        rule include_declaration
          'include' space [A-Z] (alphanumeric_char / '::')* {
            def compile(builder)
              builder << text_value
            end
          }
        end

        rule parsing_rule
          'rule' space nonterminal space ('do' space)? parsing_expression space 'end' <ParsingRule>
        end

        rule parsing_expression
          choice / sequence / primary
        end

        rule choice
          head:alternative tail:(space? '/' space? alternative)+ <Choice> {
            def alternatives
              [head] + tail
            end

            def tail
              super.elements.map {|elt| elt.alternative}
            end

            def inline_modules
              (alternatives.map {|alt| alt.inline_modules }).flatten
            end
          }
        end

        rule sequence
          sequence_body node_class_declarations <Sequence> {
            def sequence_elements
              [sequence_body.head] + tail
            end

            def tail
              sequence_body.tail
            end

            def inline_modules
              (sequence_elements.map {|elt| elt.inline_modules}).flatten +
              [sequence_element_accessor_module] +
              node_class_declarations.inline_modules
            end

            def inline_module_name
              node_class_declarations.inline_module_name
            end
          }
        end

        rule sequence_body
          variable_length_sequence_body / labeled_expression_sequence_body
        end

        rule variable_length_sequence_body
          head:optionally_labeled_sequence_primary tail:(space optionally_labeled_sequence_primary)+ {
            def tail
              super.elements.map {|elt| elt.optionally_labeled_sequence_primary }
            end
          }
        end

        rule labeled_expression_sequence_body
          labeled_sequence_primary {
            def head
              self
            end

            def tail
              []
            end
          }
        end

        rule alternative
          sequence / primary
        end

        rule primary
          prefix atomic {
            def compile(address, builder, parent_expression=nil)
              prefix.compile(address, builder, self)
            end

            def prefixed_expression
              atomic
            end

            def inline_modules
              atomic.inline_modules
            end

            def inline_module_name
              nil
            end
          }
          /
          prefix space? predicate_block {
            def compile(address, builder, parent_expression=nil)
              prefix.compile(address, builder, self)
            end
            def prefixed_expression
              predicate_block
            end
            def inline_modules
              []
            end
          }
          /
          atomic suffix node_class_declarations {
            def compile(address, builder, parent_expression=nil)
              suffix.compile(address, builder, self)
            end

            def optional_expression
              atomic
            end

            def node_class_name
              node_class_declarations.node_class_name
            end

            def inline_modules
              atomic.inline_modules + node_class_declarations.inline_modules
            end

            def inline_module_name
              node_class_declarations.inline_module_name
            end
          }
          /
          atomic node_class_declarations {
            def compile(address, builder, parent_expression=nil)
              atomic.compile(address, builder, self)
            end

            def node_class_name
              node_class_declarations.node_class_name
            end

            def inline_modules
              atomic.inline_modules + node_class_declarations.inline_modules
            end

            def inline_module_name
              node_class_declarations.inline_module_name
            end
          }
        end

        rule optionally_labeled_sequence_primary
          labeled_sequence_primary / unlabeled_sequence_primary
        end

        rule labeled_sequence_primary
          named_label sequence_primary {
            def compile(lexical_address, builder)
              sequence_primary.compile(lexical_address, builder)
            end

            def inline_modules
              sequence_primary.inline_modules
            end

            def label_name
              named_label.name
            end
          }
        end

        rule unlabeled_sequence_primary
          null_label sequence_primary {
            def compile(lexical_address, builder)
              sequence_primary.compile(lexical_address, builder)
            end

            def inline_modules
              sequence_primary.inline_modules
            end

            def label_name
              if sequence_primary.instance_of?(Nonterminal)
                sequence_primary.text_value
              else
                nil
              end
            end
          }
        end

        rule label
          named_label / null_label
        end

        rule named_label
          (alpha_char alphanumeric_char*) ':' {
            def name
              elements[0].text_value
            end
          }
        end

        rule null_label
          '' {
            def name
              nil
            end
          }
        end

        rule sequence_primary
          prefix atomic {
            def compile(lexical_address, builder)
              prefix.compile(lexical_address, builder, self)
            end

            def prefixed_expression
              elements[1]
            end

            def inline_modules
              atomic.inline_modules
            end

            def inline_module_name
              nil
            end
          }
          /
          prefix space? predicate_block {
            def compile(address, builder, parent_expression=nil)
              prefix.compile(address, builder, self)
            end
            def prefixed_expression
              predicate_block
            end
            def inline_modules
              []
            end
          }
          /
          atomic suffix {
            def compile(lexical_address, builder)
              suffix.compile(lexical_address, builder, self)
            end

            def node_class_name
              nil
            end

            def inline_modules
              atomic.inline_modules
            end

            def inline_module_name
              nil
            end
          }
          /
          atomic
        end

        rule suffix
          repetition_suffix / optional_suffix
        end

        rule optional_suffix
          '?' <Optional>
        end

        rule node_class_declarations
          node_class_expression trailing_inline_module {
            def node_class_name
              node_class_expression.node_class_name
            end

            def inline_modules
              trailing_inline_module.inline_modules
            end

            def inline_module
              trailing_inline_module.inline_module
            end

            def inline_module_name
              inline_module.module_name if inline_module
            end
          }
        end

        rule repetition_suffix
          '+' <OneOrMore> / '*' <ZeroOrMore> / occurrence_range
        end

        rule occurrence_range
          space? min:([0-9])* '..' max:([0-9])* <OccurrenceRange>
        end

        rule prefix
          '&' <AndPredicate> / '!' <NotPredicate> / '~' <TransientPrefix>
        end

        rule atomic
          terminal
          /
          nonterminal
          /
          parenthesized_expression
        end

        rule parenthesized_expression
          '(' space? parsing_expression space? ')' <ParenthesizedExpression> {
            def inline_modules
              parsing_expression.inline_modules
            end
          }
        end

        rule nonterminal
          !keyword_inside_grammar (alpha_char alphanumeric_char*) <Nonterminal>
        end

        rule terminal
          quoted_string / character_class / anything_symbol
        end

        rule quoted_string
          (single_quoted_string / double_quoted_string) {
            def string
              super.text_value
            end
          }
        end

        rule double_quoted_string
          '"' string:(!'"' ("\\\\" / '\"' / .))* '"' <Terminal>
        end

        rule single_quoted_string
          "'" string:(!"'" ("\\\\" / "\\'" / .))* "'" <Terminal>
        end

        rule character_class
          '[' characters:(!']' ('\' . / bracket_expression / !'\' .))+ ']' <CharacterClass> {
            def characters
              super.text_value
            end
          }
        end

        rule bracket_expression
           '[:' '^'? (
             'alnum' / 'alpha' / 'blank' / 'cntrl' / 'digit' / 'graph' / 'lower' /
             'print' / 'punct' / 'space' / 'upper' / 'xdigit' / 'word'
           ) ':]'
        end

        rule anything_symbol
          '.' <AnythingSymbol>
        end

        rule node_class_expression
          space '<' (!'>' .)+ '>' {
            def node_class_name
              elements[2].text_value
            end
          }
          /
          '' {
            def node_class_name
              nil
            end
          }
        end

        rule trailing_inline_module
          space inline_module {
            def inline_modules
              [inline_module]
            end

            def inline_module_name
              inline_module.module_name
            end
          }
          /
          '' {
            def inline_modules
              []
            end

            def inline_module
              nil
            end

            def inline_module_name
              nil
            end
          }
        end

        rule predicate_block
          '' inline_module <PredicateBlock>
        end

        rule inline_module
          '{' (inline_module / ![{}] .)* '}' <InlineModule>
        end

        rule keyword_inside_grammar
          ('rule' / 'end') !non_space_char
        end

        rule non_space_char
          !space .
        end

        rule alpha_char
          [A-Za-z_]
        end

        rule alphanumeric_char
          alpha_char / [0-9]
        end

        rule space
          (white / comment_to_eol)+
        end

        rule comment_to_eol
          '#' (!"\n" .)*
        end

        rule white
          [ \t\n\r]
        end
      end
    end

end