File: compiled_parser.rb

package info (click to toggle)
ruby-treetop 1.6.14-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 956 kB
  • sloc: ruby: 8,918; makefile: 5
file content (139 lines) | stat: -rw-r--r-- 4,695 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
module Treetop
  module Runtime
    class CompiledParser
      include Treetop::Runtime

      attr_reader :input, :index, :max_terminal_failure_index
      attr_writer :root
      attr_accessor :consume_all_input
      alias :consume_all_input? :consume_all_input

      def initialize
        self.consume_all_input = true
      end

      def parse(input, options = {})
        prepare_to_parse(input)
        @index = options[:index] if options[:index]
        result = send("_nt_#{options[:root] || root}")
        should_consume_all = options.include?(:consume_all_input) ? options[:consume_all_input] : consume_all_input?
        if (should_consume_all && index != input.size)
          if index > max_terminal_failure_index # Otherwise the failure is already explained
            terminal_parse_failure('<END OF INPUT>', true)
          end
          return nil
        end
        return SyntaxNode.new(input, index...(index + 1)) if result == true
        return result
      end

      def failure_index
        max_terminal_failure_index
      end

      def failure_line
        @terminal_failures && input.line_of(failure_index)
      end

      def failure_column
        @terminal_failures && input.column_of(failure_index)
      end

      OtherThan = 'something other than '
      def failure_reason
        return nil unless (tf = terminal_failures) && tf.size > 0
        "Expected " +
          (tf.size == 1 ?
           (tf[0].unexpected ? OtherThan : '')+tf[0].expected_string :
                 "one of #{tf.map{|f| (f.unexpected ? OtherThan : '')+f.expected_string}.uniq*', '}"
          ) +
                " at line #{failure_line}, column #{failure_column} (byte #{failure_index+1})" +
                (failure_index > 0 ? " after #{input[index...failure_index]}" : '')
      end

      def terminal_failures
        if @terminal_failures.empty? || @terminal_failures[-1].is_a?(TerminalParseFailure)
          @terminal_failures
        else
          @terminal_failures.map! {|tf_ary| tf_ary.is_a?(TerminalParseFailure) ? tf_ary : TerminalParseFailure.new(*tf_ary) }
        end
      end


      protected

      attr_reader :node_cache, :input_length
      attr_writer :index

      def prepare_to_parse(input)
        @input = input
        @input_length = input.length
        reset_index
        @node_cache = Hash.new {|hash, key| hash[key] = Hash.new}
        @regexps = {}
        @terminal_failures = []
        @max_terminal_failure_index = 0
      end

      def forget_failures_to_here
        @terminal_failures = []
        @max_terminal_failure_index = -1
      end

      def reset_index
        @index = 0
      end

      def parse_anything(node_class = SyntaxNode, inline_module = nil)
        if index < input.length
          result = instantiate_node(node_class,input, index...(index + 1))
          result.extend(inline_module) if inline_module
          @index += 1
          result
        else
          terminal_parse_failure("any character")
        end
      end

      def instantiate_node(node_type,*args)
        if node_type.respond_to? :new
          node_type.new(*args)
        else
          SyntaxNode.new(*args).extend(node_type)
        end
      end

      def has_terminal?(terminal, mode, index)
        case mode
        when :regexp    # A Regexp has been passed in, either a character class or a literel regex 'foo'r
          (terminal =~ input[index..-1]) == 0 && $&.length
        when false      # The terminal is a string which must match exactly
          input[index, terminal.size] == terminal && terminal.size
        when :insens    # The terminal is a downcased string which must match input downcased
          input[index, terminal.size].downcase == terminal && terminal.size
        when true       # Only occurs with old compiled grammars, for character classes
          rx = @regexps[terminal] ||= Regexp.new(terminal)
          input.index(rx, index) == index && $&.length
        end
      end

      def terminal_parse_failure(expected_string, unexpected = false)
        if @max_terminal_failure_index == -1
          @max_terminal_failure_index = 0
          return nil
        end
        return nil if index < max_terminal_failure_index
        if index > max_terminal_failure_index
          @max_terminal_failure_index = index
          @terminal_failures = []
        end
        @terminal_failures << [index, expected_string, unexpected]
        # It's very slow, but this shows the last 5 nested rules:
        # caller.reject{|l| l =~ /`loop'|`block in /}[0..5].reverse.map{|l| l.sub(/[^`]*`_nt_/,'').sub(/'/,'')}

        terminal_failures
        return nil
      end
    end
  end
end