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
|