Class: PackratParser::Rule

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

Overview

A lazy, memoizing reference to a named grammar rule.

Rule methods on a PackratParser subclass return a Rule instead of building their combinator immediately. This is essential: a comprehension evaluates its generator receivers eagerly (to call flat_map/map on them), so a self-referential rule like additive would recurse forever at build time if the body ran on every reference. Returning a lazy Rule breaks that cycle.

Memoizing the result per (rule, pos) gives the packrat property: each rule is evaluated at most once per input position, so parsing stays linear.

The combinator graph is built once per rule and cached on the owner. This relies on the comprehension's loop variables being block-local: each closure built from the rule body owns its loop-variable bindings, so reusing one cached closure across recursive activations (e.g. additive calling additive) is safe. (An earlier fork leaked the loop variables into the rule-method scope, which forced a rebuild per entry so activations would not clobber each other's bindings; that workaround is no longer needed now that the comprehension scopes them.)

Instance Method Summary collapse

Methods inherited from Parser

#*, #<<, #>>, #filter, #flat_map, #map, #opt, #rep, #rep1, #|

Constructor Details

#initialize(owner, name, body) ⇒ Rule

Returns a new instance of Rule.



186
187
188
189
190
# File 'lib/packrat_parser/parser.rb', line 186

def initialize(owner, name, body)
  @owner = owner
  @name = name
  @body = body
end

Instance Method Details

#call(input, pos) ⇒ Object



192
193
194
195
196
197
198
199
200
# File 'lib/packrat_parser/parser.rb', line 192

def call(input, pos)
  # Two-level memo table (rule -> pos -> result). Keying on a single [name,
  # pos] Array would allocate one Array per rule invocation and hash it;
  # nesting keeps the per-call key a bare Integer.
  memo = (@owner.__memo[@name] ||= {})
  return memo[pos] if memo.key?(pos)
  combinator = (@owner.__built[@name] ||= @body.bind(@owner).call)
  memo[pos] = combinator.call(input, pos)
end

#parse(input) ⇒ Object

Parse input starting from this rule, e.g. parser.number.parse("123"). Delegates to the owner so memo reset, whitespace handling, and the full-consumption check are applied exactly as for the start symbol.



205
206
207
# File 'lib/packrat_parser/parser.rb', line 205

def parse(input)
  @owner.parse(input, @name)
end