packrat_parser 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3329c290118a8d488a68b9d83f645eee1095730ee11d51446bd1e76ff232b95e
4
- data.tar.gz: d39e34dd833b9bb48079b6e342a5b36162396a257b239697bef76d5f796b0679
3
+ metadata.gz: 5600ad17276add802741ba1c0bda893d7757dcdab4a22dc8ae10e2b18a928fd9
4
+ data.tar.gz: ac53b87a45954f9d319f0b3298222217c2636fb478281c7596c4d440d81742b2
5
5
  SHA512:
6
- metadata.gz: 9d51853503af9f290bbb062f4a0b3e8543ddb032aeb69d3f3d1ec555c02101975780e0cad5e4ab364a267cfd6fd361259b0cd1ca4136dbae00ce81dea954a753
7
- data.tar.gz: afd0c1223b0bc3d392b947a319cedc9696280c6cf6d5e10cfbc638aaa1fdf7669a5c88c532542bc9744b645312cab8b88fb559d3fa90be4f1c77d50145f23abd
6
+ metadata.gz: 8ca4a20c8f1f3e4c3063db70c39441b88401bd144c1ac1cc50b29a03ec58b845029d93c3a5879176b454e7b79b0fd47b35193943a327b928a50552c055d46d29
7
+ data.tar.gz: 882cb9d1845f4c06f360f2fd933ff6f84d80c03e749dff3ba5aa6e687fed0b98cdd75a247043913ffbb5f8da89349a4fa6b4dfe0c6d3f5fae4b84a2b669f357c
data/README.md CHANGED
@@ -49,9 +49,14 @@ The default Prism parser rejects `for ... then`. The library itself
49
49
 
50
50
  ## API
51
51
 
52
- Subclass `PackratParser`. **Every method you define in the subclass is a grammar
53
- rule** and must return a parser; rule methods are automatically made lazy and
54
- memoized so they can reference one another (and themselves) recursively.
52
+ Subclass `PackratParser`. **Every public method you define in the subclass is a
53
+ grammar rule** and must return a parser; rule methods are automatically made lazy
54
+ and memoized so they can reference one another (and themselves) recursively.
55
+
56
+ **Private methods are left as ordinary helpers** (not rules), so you can factor
57
+ out plain Ruby logic. Use the `private` section form (`private` on its own line,
58
+ then the defs); `private def foo` is not detected, because the method is still
59
+ public when it is defined.
55
60
 
56
61
  ### Building blocks (available inside rule methods)
57
62
 
@@ -66,18 +71,26 @@ memoized so they can reference one another (and themselves) recursively.
66
71
  - `map { |v| new_value }` — transform the result.
67
72
  - `filter { |v| bool }` — succeed only when the predicate holds.
68
73
  - `a | b` — ordered choice: try `a`, and if it fails try `b`.
69
- - `a + b` — sequence, keep **both** results (Scala's `~`): run `a` then `b`,
70
- yield the pair `[a, b]`. Left-associative and nesting, so `a + b + c` yields
71
- `[[a, b], c]`; Ruby's block-parameter destructuring takes them apart the way
72
- Scala's `case a ~ b ~ c` does: `(a + b + c).map { |(x, y), z| ... }`.
74
+ - `a * b` — sequence, keep **both** results (Scala's `~`): run `a` then `b`,
75
+ yield the pair `[a, b]`. `*` reads as a product: the result is the product of
76
+ the two values, just as `|` (choice) is the algebraic sum. Left-associative and
77
+ nesting, so `a * b * c` yields `[[a, b], c]`; Ruby's block-parameter
78
+ destructuring takes them apart the way Scala's `case a ~ b ~ c` does:
79
+ `(a * b * c).map { |(x, y), z| ... }`.
73
80
  - `a << b` — sequence, keep the **left** result (Scala's `<~`): run `a` then
74
81
  `b`, yield `a`'s value and discard `b`'s.
75
82
  - `a >> b` — sequence, keep the **right** result (Scala's `~>`): run `a` then
76
83
  `b`, yield `b`'s value and discard `a`'s.
84
+ - `p.rep` — zero or more (Scala's `rep`): yields an array of results (empty when
85
+ there are no matches). Always succeeds.
86
+ - `p.rep1` — one or more (Scala's `rep1`): like `rep` but fails unless `p`
87
+ matches at least once; yields a non-empty array.
88
+ - `p.opt` — optional (Scala's `opt`): yields `p`'s value, or `nil` (consuming
89
+ nothing) when `p` does not match.
77
90
 
78
91
  The arrow direction is a useful mnemonic: `<<`/`>>` keeps whichever side it
79
92
  points to. They are handy for discarding punctuation, e.g. `( expr )` is
80
- `term("(") >> expr << term(")")`. Ruby's precedence (`+` over `<<`/`>>` over
93
+ `term("(") >> expr << term(")")`. Ruby's precedence (`*` over `<<`/`>>` over
81
94
  `|`) means sequencing binds tighter than ordered choice, as you'd want.
82
95
 
83
96
  `flat_map`, `map`, and `filter` are exactly what the `for ... then`
@@ -114,6 +127,10 @@ The setting is inherited by subclasses, so a base parser can enable it once.
114
127
  If omitted, the first defined method is used as the start symbol.
115
128
  - `Klass.parse(input)` / `Klass.new.parse(input)` — parse, returning the value.
116
129
  Raises `PackratParser::ParseError` on failure or leftover input.
130
+ - Parse from any rule, not just the start symbol:
131
+ `Klass.new.number.parse("123")` (call `parse` on the rule) or
132
+ `Klass.new.parse("123", :number)` (pass the start rule). Both apply the same
133
+ full-consumption check and whitespace handling.
117
134
 
118
135
  ## Notes / limitations
119
136
 
@@ -123,11 +140,29 @@ The setting is inherited by subclasses, so a base parser can enable it once.
123
140
  - **No implicit whitespace by default.** `term` matches exactly. Enable implicit
124
141
  whitespace skipping with `skip_whitespace` (see above) when your grammar needs
125
142
  it.
143
+ - **Byte-oriented positions.** The parser tracks byte offsets and matches with
144
+ `byteslice` / `byteindex`, so it stays efficient on UTF-8 input (indexing a
145
+ multibyte string by character is O(n)). Positions are byte offsets throughout,
146
+ including the `pos` reported on a raised `ParseError`.
126
147
 
127
148
  ## Running the tests
128
149
 
129
150
  ```sh
151
+ rake # or: rake test
152
+ # or directly:
130
153
  bin/test
131
154
  # or:
132
- ruby --parser=parse.y -Ilib -Itest test/test_packrat_parser.rb
155
+ ruby --parser=parse.y -Ilib test/test_packrat_parser.rb
133
156
  ```
157
+
158
+ The `rake test` task runs the suite under `--parser=parse.y` for you (the test
159
+ grammars need the fork's legacy parser).
160
+
161
+ ## Benchmark
162
+
163
+ `benchmark/` compares this library against [racc](https://github.com/ruby/racc)
164
+ (LALR) and [parslet](https://kschiess.github.io/parslet/) (PEG combinators) on
165
+ the same calculator grammar. Run it with `rake bench` (needs `parslet`). Roughly,
166
+ all three are linear on this grammar, with **racc ≫ packrat_parser > parslet** on
167
+ constant factors (packrat_parser ~3× racc, parslet ~8× packrat_parser). See
168
+ [benchmark/README.md](benchmark/README.md) for numbers and analysis.
@@ -53,10 +53,17 @@ class PackratParser
53
53
  # Rule. Guards against rewriting the base class's own infrastructure and
54
54
  # against re-entering while we install the replacement (define_method itself
55
55
  # fires method_added).
56
+ #
57
+ # Private methods are left alone, so they can be used as ordinary helpers
58
+ # rather than grammar rules. Note this only sees the visibility in effect when
59
+ # the method is defined, so it recognises the `private` section form but not
60
+ # `private def foo ... end` (which is still public at this point and only made
61
+ # private afterwards).
56
62
  def self.method_added(name)
57
63
  return if self == PackratParser
58
64
  return if name == :initialize
59
65
  return if @__defining_rule
66
+ return if private_method_defined?(name)
60
67
 
61
68
  @__defining_rule = true
62
69
  @start_symbol ||= name
@@ -70,15 +77,27 @@ class PackratParser
70
77
  end
71
78
  end
72
79
 
73
- # Per-input packrat memo table, keyed by [rule_name, pos].
80
+ # Per-input packrat memo table: a two-level hash, rule_name => (pos => result).
74
81
  def __memo
75
82
  @__memo ||= {}
76
83
  end
77
84
 
85
+ # Cache of built combinators, keyed by rule name. The combinator graph for a
86
+ # rule is stable, so it is built once and reused (loop variables are
87
+ # block-local, so reusing a closure across recursive activations is safe).
88
+ def __built
89
+ @__built ||= {}
90
+ end
91
+
78
92
  # A terminal parser. A String matches that exact literal at the current
79
93
  # position; a Regexp is matched anchored at the current position. The matched
80
94
  # substring is the parser's value.
81
95
  #
96
+ # Positions are *byte* offsets, not character offsets: indexing a UTF-8 string
97
+ # by character is O(n), so matching by byte (+byteslice+ for literals,
98
+ # +byteindex+ with a `\G` anchor for regexps) keeps each step O(match length)
99
+ # regardless of how far into the input we are.
100
+ #
82
101
  # When the class enables +skip_whitespace+, leading whitespace is consumed
83
102
  # before the match is attempted, mirroring Scala's RegexParsers.
84
103
  def term(pattern)
@@ -86,22 +105,30 @@ class PackratParser
86
105
  ws = /\G(?:#{ws})/ if ws
87
106
  case pattern
88
107
  when String
108
+ bytes = pattern.bytesize
109
+ # The failure message is constant for this terminal, and failures are
110
+ # common (ordered choice discards them) while the message is only read if
111
+ # the whole parse fails. Build it once and share the frozen string rather
112
+ # than interpolating on every failed match.
113
+ msg = "expected #{pattern.inspect}".freeze
89
114
  Parser.new do |input, pos|
90
115
  pos = __skip_ws(ws, input, pos)
91
- if input[pos, pattern.length] == pattern
92
- Success.new(pattern, pos + pattern.length)
116
+ if input.byteslice(pos, bytes) == pattern
117
+ Success.new(pattern, pos + bytes)
93
118
  else
94
- Failure.new(pos, "expected #{pattern.inspect}")
119
+ Failure.new(pos, msg)
95
120
  end
96
121
  end
97
122
  when Regexp
98
123
  anchored = /\G(?:#{pattern})/
124
+ msg = "expected #{pattern.inspect}".freeze
99
125
  Parser.new do |input, pos|
100
126
  pos = __skip_ws(ws, input, pos)
101
- if (m = anchored.match(input, pos))
102
- Success.new(m[0], pos + m[0].length)
127
+ if input.byteindex(anchored, pos)
128
+ s = Regexp.last_match[0]
129
+ Success.new(s, pos + s.bytesize)
103
130
  else
104
- Failure.new(pos, "expected #{pattern.inspect}")
131
+ Failure.new(pos, msg)
105
132
  end
106
133
  end
107
134
  else
@@ -109,11 +136,11 @@ class PackratParser
109
136
  end
110
137
  end
111
138
 
112
- # Advance +pos+ past whitespace matched by the anchored regexp +ws+ (nil when
113
- # skipping is disabled). Returns the new position.
139
+ # Advance the byte offset +pos+ past whitespace matched by the anchored regexp
140
+ # +ws+ (nil when skipping is disabled). Returns the new byte offset.
114
141
  def __skip_ws(ws, input, pos)
115
142
  return pos unless ws
116
- (m = ws.match(input, pos)) ? pos + m[0].length : pos
143
+ input.byteindex(ws, pos) ? pos + Regexp.last_match[0].bytesize : pos
117
144
  end
118
145
 
119
146
  # A parser that succeeds with +value+ without consuming any input (monadic
@@ -122,11 +149,16 @@ class PackratParser
122
149
  Parser.new { |_input, pos| Success.new(value, pos) }
123
150
  end
124
151
 
125
- # Parse +input+ starting from the configured start symbol. Returns the parsed
126
- # value on success; raises ParseError on failure or on leftover input.
127
- def parse(input)
152
+ # Parse +input+, starting from rule +start+ (defaults to the configured start
153
+ # symbol). Returns the parsed value on success; raises ParseError on failure or
154
+ # on leftover input. Pass +start+ to parse from any rule, e.g.
155
+ # +parser.parse("123", :number)+ or, equivalently, +parser.number.parse("123")+.
156
+ #
157
+ # Positions are byte offsets throughout, including the +pos+ reported on a
158
+ # ParseError (see +term+ for why matching is byte-oriented).
159
+ def parse(input, start = nil)
128
160
  @__memo = {}
129
- name = self.class.start_symbol
161
+ name = start || self.class.start_symbol
130
162
  raise ParseError.new("no start symbol defined", 0) unless name
131
163
 
132
164
  result = send(name).call(input, 0)
@@ -138,7 +170,7 @@ class PackratParser
138
170
  # all input was used.
139
171
  ws = self.class.whitespace
140
172
  end_pos = __skip_ws(ws && /\G(?:#{ws})/, input, result.pos)
141
- if end_pos < input.length
173
+ if end_pos < input.bytesize
142
174
  raise ParseError.new("unexpected trailing input", end_pos)
143
175
  end
144
176
  result.value
@@ -76,7 +76,18 @@ class PackratParser
76
76
  #
77
77
  # number << term(";") # parse a number followed by ";", yield the number
78
78
  def <<(other)
79
- flat_map { |x| other.map { |_| x } }
79
+ # Equivalent to flat_map { |x| other.map { |_| x } }, but written directly
80
+ # so the combinator graph is built once (at bind time) instead of
81
+ # allocating a fresh `map` parser on every successful match.
82
+ Parser.new do |input, pos|
83
+ a = call(input, pos)
84
+ if a.success?
85
+ b = other.call(input, a.pos)
86
+ b.success? ? Success.new(a.value, b.pos) : b
87
+ else
88
+ a
89
+ end
90
+ end
80
91
  end
81
92
 
82
93
  # Sequence, keeping the *right* result (Scala's `~>`). Run this parser, then
@@ -84,18 +95,71 @@ class PackratParser
84
95
  #
85
96
  # term("(") >> additive # skip "(", yield whatever additive produces
86
97
  def >>(other)
87
- flat_map { |_| other }
98
+ # Equivalent to flat_map { |_| other }, written directly to avoid the
99
+ # per-call block dispatch.
100
+ Parser.new do |input, pos|
101
+ a = call(input, pos)
102
+ a.success? ? other.call(input, a.pos) : a
103
+ end
88
104
  end
89
105
 
90
106
  # Sequence, keeping *both* results (Scala's `~`). Run this parser, then
91
- # +other+, and on success return the pair +[left, right]+. Like Scala's `~`
92
- # this is left-associative and nests, so `p + q + r` yields `[[a, b], c]`;
93
- # Ruby's block-parameter destructuring takes them apart the way Scala's
94
- # `case a ~ b ~ c` does:
107
+ # +other+, and on success return the pair +[left, right]+. The result type is
108
+ # the product of the operands' types, so `*` (product) is the natural
109
+ # spelling -- and it dovetails with `|` for choice, mirroring how a regular
110
+ # language is a semiring with choice as the sum and sequence as the product.
111
+ #
112
+ # Like Scala's `~` this is left-associative and nests, so `p * q * r` yields
113
+ # `[[a, b], c]`; Ruby's block-parameter destructuring takes them apart the
114
+ # way Scala's `case a ~ b ~ c` does:
95
115
  #
96
- # (p + q + r).map { |(a, b), c| ... }
97
- def +(other)
98
- flat_map { |x| other.map { |y| [x, y] } }
116
+ # (p * q * r).map { |(a, b), c| ... }
117
+ def *(other)
118
+ # Equivalent to flat_map { |x| other.map { |y| [x, y] } }, written directly
119
+ # so only the result pair (and its Success) is allocated per match, not a
120
+ # fresh intermediate `map` parser.
121
+ Parser.new do |input, pos|
122
+ a = call(input, pos)
123
+ if a.success?
124
+ b = other.call(input, a.pos)
125
+ b.success? ? Success.new([a.value, b.value], b.pos) : b
126
+ else
127
+ a
128
+ end
129
+ end
130
+ end
131
+
132
+ # Zero or more repetitions (Scala's `rep` / `p.*`). Always succeeds, yielding
133
+ # an array of the collected values (empty when there are no matches). A match
134
+ # that consumes no input stops the loop, so a nullable parser can't spin
135
+ # forever.
136
+ def rep
137
+ Parser.new do |input, pos|
138
+ values = []
139
+ cur = pos
140
+ loop do
141
+ result = call(input, cur)
142
+ break if !result.success? || result.pos == cur
143
+ values << result.value
144
+ cur = result.pos
145
+ end
146
+ Success.new(values, cur)
147
+ end
148
+ end
149
+
150
+ # One or more repetitions (Scala's `rep1` / `p.+`). Fails if the first match
151
+ # fails; otherwise yields a non-empty array of values.
152
+ def rep1
153
+ flat_map { |first| rep.map { |rest| [first, *rest] } }
154
+ end
155
+
156
+ # Optional (Scala's `opt` / `p.?`). Yields the parsed value, or nil (consuming
157
+ # nothing) when this parser does not match.
158
+ def opt
159
+ Parser.new do |input, pos|
160
+ result = call(input, pos)
161
+ result.success? ? result : Success.new(nil, pos)
162
+ end
99
163
  end
100
164
  end
101
165
 
@@ -110,20 +174,14 @@ class PackratParser
110
174
  # Memoizing the *result* per (rule, pos) gives the packrat property: each rule
111
175
  # is evaluated at most once per input position, so parsing stays linear.
112
176
  #
113
- # The combinator graph is rebuilt on every (memo-missed) entry rather than
114
- # cached, and that is deliberate. The `for ... then` comprehension's loop
115
- # variables currently leak into the enclosing rule-method scope instead of
116
- # being block-local, so a single built closure shares those slots. If the same
117
- # closure were reused for a recursive activation (e.g. additive calling
118
- # additive), the inner activation would clobber the outer's leaked variables.
119
- # Building fresh gives each activation its own scope. The rebuild is bounded:
120
- # result memoization means a build happens at most once per (rule, pos).
121
- #
122
- # NOTE: this rebuild is a workaround for the loop-variable leak in the fork's
123
- # comprehension (parse.y: new_for_comp_gen leaves the loop var in the
124
- # surrounding scope, like the legacy `for`). If the comprehension is changed to
125
- # make loop variables block-local, this clobbering goes away and `built` can be
126
- # cached once per (owner, name) again -- e.g. `@owner.__built[@name] ||= ...`.
177
+ # The combinator graph is built once per rule and cached on the owner. This
178
+ # relies on the comprehension's loop variables being block-local: each closure
179
+ # built from the rule body owns its loop-variable bindings, so reusing one
180
+ # cached closure across recursive activations (e.g. additive calling additive)
181
+ # is safe. (An earlier fork leaked the loop variables into the rule-method
182
+ # scope, which forced a rebuild per entry so activations would not clobber each
183
+ # other's bindings; that workaround is no longer needed now that the
184
+ # comprehension scopes them.)
127
185
  class Rule < Parser
128
186
  def initialize(owner, name, body)
129
187
  @owner = owner
@@ -132,11 +190,20 @@ class PackratParser
132
190
  end
133
191
 
134
192
  def call(input, pos)
135
- memo = @owner.__memo
136
- key = [@name, pos]
137
- return memo[key] if memo.key?(key)
138
- combinator = @body.bind(@owner).call
139
- memo[key] = combinator.call(input, pos)
193
+ # Two-level memo table (rule -> pos -> result). Keying on a single [name,
194
+ # pos] Array would allocate one Array per rule invocation and hash it;
195
+ # nesting keeps the per-call key a bare Integer.
196
+ memo = (@owner.__memo[@name] ||= {})
197
+ return memo[pos] if memo.key?(pos)
198
+ combinator = (@owner.__built[@name] ||= @body.bind(@owner).call)
199
+ memo[pos] = combinator.call(input, pos)
200
+ end
201
+
202
+ # Parse +input+ starting from this rule, e.g. +parser.number.parse("123")+.
203
+ # Delegates to the owner so memo reset, whitespace handling, and the
204
+ # full-consumption check are applied exactly as for the start symbol.
205
+ def parse(input)
206
+ @owner.parse(input, @name)
140
207
  end
141
208
  end
142
209
  end
@@ -1,3 +1,3 @@
1
1
  class PackratParser
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,52 +1,118 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: packrat_parser
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
- - Shugo Maeda
7
+ - Shugo Maeda
8
8
  bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
- dependencies: []
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rake
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ -
17
+ - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: "13.0"
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ -
25
+ - ~>
26
+ - !ruby/object:Gem::Version
27
+ version: "13.0"
28
+ - !ruby/object:Gem::Dependency
29
+ name: test-unit
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ -
33
+ - ~>
34
+ - !ruby/object:Gem::Version
35
+ version: "3.0"
36
+ type: :development
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ -
41
+ - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: "3.0"
44
+ - !ruby/object:Gem::Dependency
45
+ name: racc
46
+ requirement: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ -
49
+ - ~>
50
+ - !ruby/object:Gem::Version
51
+ version: "1.8"
52
+ type: :development
53
+ prerelease: false
54
+ version_requirements: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ -
57
+ - ~>
58
+ - !ruby/object:Gem::Version
59
+ version: "1.8"
60
+ - !ruby/object:Gem::Dependency
61
+ name: parslet
62
+ requirement: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ -
65
+ - ~>
66
+ - !ruby/object:Gem::Version
67
+ version: "2.0"
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ -
73
+ - ~>
74
+ - !ruby/object:Gem::Version
75
+ version: "2.0"
12
76
  description: |
13
- packrat_parser is a small PEG/packrat parser-combinator library. Grammar
14
- rules are plain methods that return parsers and can be written using the
15
- `for ... then` comprehension (flat_map/map/filter), giving a Scala-style
16
- for-comprehension feel.
77
+ packrat_parser is a small PEG/packrat parser-combinator library. Grammar
78
+ rules are plain methods that return parsers and can be written using the
79
+ `for ... then` comprehension (flat_map/map/filter), giving a Scala-style
80
+ for-comprehension feel.
17
81
  email:
18
- - shugo.maeda@gmail.com
82
+ - shugo.maeda@gmail.com
19
83
  executables: []
20
84
  extensions: []
21
85
  extra_rdoc_files: []
22
86
  files:
23
- - LICENSE
24
- - README.md
25
- - examples/simple_calc.rb
26
- - lib/packrat_parser.rb
27
- - lib/packrat_parser/base.rb
28
- - lib/packrat_parser/parser.rb
29
- - lib/packrat_parser/result.rb
30
- - lib/packrat_parser/version.rb
31
- homepage: https://github.com/shugo/packrat_parser
87
+ - LICENSE
88
+ - README.md
89
+ - examples/simple_calc.rb
90
+ - lib/packrat_parser.rb
91
+ - lib/packrat_parser/base.rb
92
+ - lib/packrat_parser/parser.rb
93
+ - lib/packrat_parser/result.rb
94
+ - lib/packrat_parser/version.rb
95
+ homepage: "https://github.com/shugo/packrat_parser"
32
96
  licenses:
33
- - MIT
97
+ - MIT
34
98
  metadata: {}
35
99
  rdoc_options: []
36
100
  require_paths:
37
- - lib
101
+ - lib
38
102
  required_ruby_version: !ruby/object:Gem::Requirement
39
103
  requirements:
40
- - - ">="
41
- - !ruby/object:Gem::Version
42
- version: 3.0.0
104
+ -
105
+ - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: 3.0.0
43
108
  required_rubygems_version: !ruby/object:Gem::Requirement
44
109
  requirements:
45
- - - ">="
46
- - !ruby/object:Gem::Version
47
- version: '0'
110
+ -
111
+ - ">="
112
+ - !ruby/object:Gem::Version
113
+ version: "0"
48
114
  requirements: []
49
- rubygems_version: 4.0.10
115
+ rubygems_version: 4.1.0.dev
50
116
  specification_version: 4
51
117
  summary: A packrat / PEG parser-combinator library with a Scala-inspired API.
52
118
  test_files: []