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 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193
|
require 'kpeg/position'
module KPeg
class Parser < StringScanner
def initialize(str, grammar, log=false)
super str
@grammar = grammar
# A 2 level hash.
@memoizations = Hash.new { |h,k| h[k] = {} }
@failing_offset = nil
@failing_op = nil
@log = log
end
attr_reader :grammar, :memoizations, :failing_offset
attr_accessor :failing_op
include Position
def switch_grammar(gram)
begin
old = @grammar
@grammar = gram
yield
ensure
@grammar = old
end
end
def fail(op)
@failing_offset = pos
@failing_op = op
return nil
end
def expected_string
case @failing_op
when Choice
return Range.new(@failing_op.start, @failing_op.fin)
when Dot
return nil
else
@failing_op.string
end
end
class LeftRecursive
def initialize(detected=false)
@detected = detected
end
attr_accessor :detected
end
class MemoEntry
def initialize(ans, pos)
@ans = ans
@pos = pos
@uses = 1
end
attr_reader :ans, :pos, :uses
def inc!
@uses += 1
end
def move!(ans, pos)
@ans = ans
@pos = pos
end
end
# Call a rule without memoization
def invoke(rule)
rule.op.match(self)
end
def apply(rule)
ans = nil
if m = @memoizations[rule][pos]
m.inc!
self.pos = m.pos
if m.ans.kind_of? LeftRecursive
m.ans.detected = true
if @log
puts "LR #{rule.name} @ #{self.inspect}"
end
return nil
end
ans = m.ans
else
lr = LeftRecursive.new(false)
m = MemoEntry.new(lr, pos)
@memoizations[rule][pos] = m
start_pos = pos
if @log
puts "START #{rule.name} @ #{self.inspect}"
end
ans = rule.op.match(self)
m.move! ans, pos
# Don't bother trying to grow the left recursion
# if it's failing straight away (thus there is no seed)
if ans and lr.detected
ans = grow_lr(rule, start_pos, m)
end
end
if @log
if ans
puts " OK #{rule.name} @ #{self.inspect}"
else
puts " FAIL #{rule.name} @ #{self.inspect}"
end
end
return ans
end
def grow_lr(rule, start_pos, m)
while true
self.pos = start_pos
ans = rule.op.match(self)
return nil unless ans
break if pos <= m.pos
m.move! ans, pos
end
self.pos = m.pos
return m.ans
end
def failed?
!!@failing_op
end
def parse(name=nil)
if name
rule = @grammar.find(name)
unless rule
raise "Unknown rule - #{name}"
end
else
rule = @grammar.root
end
match = apply rule
if pos == string.size
@failing_op = nil
end
return match
end
def expectation
error_pos = @failing_offset
line_no = current_line(error_pos)
col_no = current_column(error_pos)
expected = expected_string()
prefix = nil
case expected
when String
prefix = expected.inspect
when Range
prefix = "to be between #{expected.begin} and #{expected.end}"
when Array
prefix = "to be one of #{expected.inspect}"
when nil
prefix = "anything (no more input)"
else
prefix = "unknown"
end
return "Expected #{prefix} at line #{line_no}, column #{col_no} (offset #{error_pos})"
end
end
end
|