Class: FuPeg::Parser

Inherits:
Object
  • Object
show all
Defined in:
lib/fupeg/parser.rb

Defined Under Namespace

Classes: CutPoint, Fail, Position

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(str, pos = 0) ⇒ Parser

Returns a new instance of Parser.



12
13
14
# File 'lib/fupeg/parser.rb', line 12

def initialize(str, pos = 0)
  reset!(str, pos)
end

Instance Attribute Details

#debugObject

Returns the value of attribute debug.



7
8
9
# File 'lib/fupeg/parser.rb', line 7

def debug
  @debug
end

#failedObject (readonly)

Returns the value of attribute failed.



9
10
11
# File 'lib/fupeg/parser.rb', line 9

def failed
  @failed
end

#fileObject

Returns the value of attribute file.



8
9
10
# File 'lib/fupeg/parser.rb', line 8

def file
  @file
end

#strObject (readonly)

Returns the value of attribute str.



10
11
12
# File 'lib/fupeg/parser.rb', line 10

def str
  @str
end

Instance Method Details

#backtrackObject



204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'lib/fupeg/parser.rb', line 204

def backtrack
  pos = @scan.pos
  res = yield
  if res
    @failed = nil if @failed && @failed.bytepos <= @scan.pos
    res
  else
    @scan.pos = pos
    nil
  end
rescue
  @scan.pos = pos
  raise
end

#bounds(lit = nil, &block) ⇒ Object



173
174
175
176
# File 'lib/fupeg/parser.rb', line 173

def bounds(lit = nil, &block)
  pos = @scan.pos
  match(lit, &block) && pos...@scan.pos
end

#byteposObject



31
32
33
# File 'lib/fupeg/parser.rb', line 31

def bytepos
  @scan.pos
end

#charpos(pos = @scan.pos) ⇒ Object



35
36
37
# File 'lib/fupeg/parser.rb', line 35

def charpos(pos = @scan.pos)
  @str_size - @str.byteslice(pos..).size
end

#current_cutpointObject



113
114
115
# File 'lib/fupeg/parser.rb', line 113

def current_cutpoint
  @cut
end

#dotObject



196
197
198
# File 'lib/fupeg/parser.rb', line 196

def dot
  match(/./m)
end

#eof?Boolean

Returns:

  • (Boolean)


200
201
202
# File 'lib/fupeg/parser.rb', line 200

def eof?
  @scan.eos?
end

#fail!(pat: nil, skip: 2) ⇒ Object



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/fupeg/parser.rb', line 41

def fail!(*, pat: nil, skip: 2)
  if debug || !@failed || bytepos > @failed.bytepos
    stack = caller_locations(skip)
    stack.delete_if do |loc|
      path = loc.path
      if path == __FILE__
        true
      elsif path.start_with?(__dir__)
        loc.label =~ /\b(backtrack|each|block)\b/
      end
    end
    @failed = Fail.new(stack, bytepos, pat)
    report_failed($stderr) if debug
  end
  nil
end

#failed_positionObject



58
59
60
# File 'lib/fupeg/parser.rb', line 58

def failed_position
  position(bytepos: @failed.bytepos)
end

#init_line_endsObject



121
122
123
124
125
126
127
128
# File 'lib/fupeg/parser.rb', line 121

def init_line_ends
  @line_ends = [-1]
  scan = StringScanner.new(@str)
  while scan.skip_until(/\n|\r\n?/)
    @line_ends << scan.pos - 1
  end
  @line_ends << @str.bytesize
end

#look_ahead(positive, lit = nil, &block) ⇒ Object



219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# File 'lib/fupeg/parser.rb', line 219

def look_ahead(positive, lit = nil, &block)
  if block
    p, f = @scan.pos, @failed
    r = yield
    @scan.pos = p
    if positive ? r : !r
      @failed = f
      true
    else
      fail!
    end
  else
    m = @scan.match?(lit)
    if positive ? m : !m
      true
    else
      fail!(pat: lit)
    end
  end
end

#position(bytepos: @scan.pos) ⇒ Object



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/fupeg/parser.rb', line 130

def position(bytepos: @scan.pos)
  lineno = @line_ends.bsearch_index { |x| x >= bytepos }
  case lineno
  when nil
    raise "Position #{bytepos} is larger than string byte size #{@str.bytesize}"
  else
    prev_end = @line_ends[lineno - 1]
    line_start = prev_end + 1
    column = @str.byteslice(line_start, bytepos - prev_end).size
  end
  if bytepos == @str.bytesize
    if @str[-1] == "\n"
      lineno, column = lineno + 1, 1
    else
      column += 1
    end
  end
  line = @str.byteslice(line_start..@line_ends[lineno])
  Position.new(lineno, column, line, charpos(bytepos))
end

#repetition(range = 0, lit = nil, &block) ⇒ Object



178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/fupeg/parser.rb', line 178

def repetition(range = 0.., lit = nil, &block)
  range = range..range if Integer === range
  range = 0..range.max if range.begin.nil?
  unless Integer === range.min && (range.end.nil? || Integer === range.max)
    raise "Range malformed #{range}"
  end
  backtrack do
    max = range.end && range.max
    ar = []
    (1..max).each do |i|
      res = backtrack { yield i == 1 }
      break unless res
      ar << res
    end
    (ar.size >= range.min) ? ar : fail!
  end
end

#report_failed(out) ⇒ Object



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/fupeg/parser.rb', line 62

def report_failed(out)
  pos = position(bytepos: @failed.bytepos)
  out << if @failed.pattern
    "Failed #{failed.pattern.inspect} at #{pos.lineno}:#{pos.colno}"
  else
    "Failed at #{pos.lineno}:#{pos.colno}"
  end
  if @file
    out << " of #{@file}"
  end
  out << ":\n"
  out << pos.line.chomp + "\n"
  curpos = pos.line[...pos.colno].gsub("\t", " " * 8).size
  curpos = 1 if curpos == 0 && @failed.bytepos == @str.bytesize
  out << (" " * (curpos - 1) + "^\n")
  out << "Call stack:\n"
  @failed.stack.each do |loc|
    out << "#{loc.path}:#{loc.lineno} in #{loc.label}\n"
  end
  out
end

#reset!(str = nil, pos = nil) ⇒ Object



16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/fupeg/parser.rb', line 16

def reset!(str = nil, pos = nil)
  if str
    @str = str.dup
    @str_size = str.size
    init_line_ends
    @scan = StringScanner.new(str)
  end
  if pos
    @scan.pos = pos
  end
  @failed = nil
  @debug = false
  @cut = CutPoint.new
end

#text(lit = nil, &block) ⇒ Object



168
169
170
171
# File 'lib/fupeg/parser.rb', line 168

def text(lit = nil, &block)
  pos = @scan.pos
  match(lit, &block) && @str.byteslice(pos, @scan.pos - pos)
end

#with_cut_pointObject

for use with cut! and cont?



103
104
105
106
107
108
109
110
111
# File 'lib/fupeg/parser.rb', line 103

def with_cut_point
  prev_cut = @cut
  @cut = CutPoint.new
  prev_cut.next = @cut
  yield @cut
ensure
  prev_cut.next = nil
  @cut = prev_cut
end