plurimath-parslet 3.0.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 +7 -0
- data/HISTORY.txt +284 -0
- data/LICENSE +23 -0
- data/README.adoc +454 -0
- data/Rakefile +71 -0
- data/lib/parslet/accelerator/application.rb +62 -0
- data/lib/parslet/accelerator/engine.rb +112 -0
- data/lib/parslet/accelerator.rb +162 -0
- data/lib/parslet/atoms/alternative.rb +53 -0
- data/lib/parslet/atoms/base.rb +157 -0
- data/lib/parslet/atoms/can_flatten.rb +137 -0
- data/lib/parslet/atoms/capture.rb +38 -0
- data/lib/parslet/atoms/context.rb +103 -0
- data/lib/parslet/atoms/dsl.rb +112 -0
- data/lib/parslet/atoms/dynamic.rb +32 -0
- data/lib/parslet/atoms/entity.rb +45 -0
- data/lib/parslet/atoms/ignored.rb +26 -0
- data/lib/parslet/atoms/infix.rb +115 -0
- data/lib/parslet/atoms/lookahead.rb +52 -0
- data/lib/parslet/atoms/named.rb +32 -0
- data/lib/parslet/atoms/re.rb +41 -0
- data/lib/parslet/atoms/repetition.rb +87 -0
- data/lib/parslet/atoms/scope.rb +26 -0
- data/lib/parslet/atoms/sequence.rb +48 -0
- data/lib/parslet/atoms/str.rb +42 -0
- data/lib/parslet/atoms/visitor.rb +89 -0
- data/lib/parslet/atoms.rb +34 -0
- data/lib/parslet/cause.rb +101 -0
- data/lib/parslet/context.rb +21 -0
- data/lib/parslet/convenience.rb +33 -0
- data/lib/parslet/error_reporter/contextual.rb +120 -0
- data/lib/parslet/error_reporter/deepest.rb +100 -0
- data/lib/parslet/error_reporter/tree.rb +63 -0
- data/lib/parslet/error_reporter.rb +8 -0
- data/lib/parslet/export.rb +163 -0
- data/lib/parslet/expression/treetop.rb +92 -0
- data/lib/parslet/expression.rb +51 -0
- data/lib/parslet/graphviz.rb +97 -0
- data/lib/parslet/parser.rb +68 -0
- data/lib/parslet/pattern/binding.rb +49 -0
- data/lib/parslet/pattern.rb +113 -0
- data/lib/parslet/position.rb +21 -0
- data/lib/parslet/rig/rspec.rb +52 -0
- data/lib/parslet/scope.rb +42 -0
- data/lib/parslet/slice.rb +105 -0
- data/lib/parslet/source/line_cache.rb +99 -0
- data/lib/parslet/source.rb +96 -0
- data/lib/parslet/transform.rb +265 -0
- data/lib/parslet/version.rb +5 -0
- data/lib/parslet.rb +314 -0
- data/plurimath-parslet.gemspec +42 -0
- data/spec/acceptance/infix_parser_spec.rb +145 -0
- data/spec/acceptance/mixing_parsers_spec.rb +74 -0
- data/spec/acceptance/regression_spec.rb +329 -0
- data/spec/acceptance/repetition_and_maybe_spec.rb +44 -0
- data/spec/acceptance/unconsumed_input_spec.rb +21 -0
- data/spec/examples/boolean_algebra_spec.rb +257 -0
- data/spec/examples/calc_spec.rb +278 -0
- data/spec/examples/capture_spec.rb +137 -0
- data/spec/examples/comments_spec.rb +186 -0
- data/spec/examples/deepest_errors_spec.rb +420 -0
- data/spec/examples/documentation_spec.rb +205 -0
- data/spec/examples/email_parser_spec.rb +275 -0
- data/spec/examples/empty_spec.rb +37 -0
- data/spec/examples/erb_spec.rb +482 -0
- data/spec/examples/ip_address_spec.rb +153 -0
- data/spec/examples/json_spec.rb +413 -0
- data/spec/examples/local_spec.rb +302 -0
- data/spec/examples/mathn_spec.rb +151 -0
- data/spec/examples/minilisp_spec.rb +492 -0
- data/spec/examples/modularity_spec.rb +340 -0
- data/spec/examples/nested_errors_spec.rb +322 -0
- data/spec/examples/optimized_erb_spec.rb +299 -0
- data/spec/examples/parens_spec.rb +239 -0
- data/spec/examples/prec_calc_spec.rb +525 -0
- data/spec/examples/readme_spec.rb +228 -0
- data/spec/examples/scopes_spec.rb +187 -0
- data/spec/examples/seasons_spec.rb +196 -0
- data/spec/examples/sentence_spec.rb +119 -0
- data/spec/examples/simple_xml_spec.rb +250 -0
- data/spec/examples/string_parser_spec.rb +407 -0
- data/spec/fixtures/examples/boolean_algebra.rb +62 -0
- data/spec/fixtures/examples/calc.rb +86 -0
- data/spec/fixtures/examples/capture.rb +36 -0
- data/spec/fixtures/examples/comments.rb +22 -0
- data/spec/fixtures/examples/deepest_errors.rb +99 -0
- data/spec/fixtures/examples/documentation.rb +32 -0
- data/spec/fixtures/examples/email_parser.rb +42 -0
- data/spec/fixtures/examples/empty.rb +10 -0
- data/spec/fixtures/examples/erb.rb +39 -0
- data/spec/fixtures/examples/ip_address.rb +103 -0
- data/spec/fixtures/examples/json.rb +107 -0
- data/spec/fixtures/examples/local.rb +60 -0
- data/spec/fixtures/examples/mathn.rb +47 -0
- data/spec/fixtures/examples/minilisp.rb +75 -0
- data/spec/fixtures/examples/modularity.rb +60 -0
- data/spec/fixtures/examples/nested_errors.rb +95 -0
- data/spec/fixtures/examples/optimized_erb.rb +105 -0
- data/spec/fixtures/examples/parens.rb +25 -0
- data/spec/fixtures/examples/prec_calc.rb +71 -0
- data/spec/fixtures/examples/readme.rb +59 -0
- data/spec/fixtures/examples/scopes.rb +43 -0
- data/spec/fixtures/examples/seasons.rb +40 -0
- data/spec/fixtures/examples/sentence.rb +18 -0
- data/spec/fixtures/examples/simple_xml.rb +51 -0
- data/spec/fixtures/examples/string_parser.rb +77 -0
- data/spec/parslet/atom_results_spec.rb +39 -0
- data/spec/parslet/atoms/alternative_spec.rb +26 -0
- data/spec/parslet/atoms/base_spec.rb +127 -0
- data/spec/parslet/atoms/capture_spec.rb +21 -0
- data/spec/parslet/atoms/combinations_spec.rb +5 -0
- data/spec/parslet/atoms/dsl_spec.rb +7 -0
- data/spec/parslet/atoms/entity_spec.rb +77 -0
- data/spec/parslet/atoms/ignored_spec.rb +15 -0
- data/spec/parslet/atoms/infix_spec.rb +5 -0
- data/spec/parslet/atoms/lookahead_spec.rb +22 -0
- data/spec/parslet/atoms/named_spec.rb +4 -0
- data/spec/parslet/atoms/re_spec.rb +14 -0
- data/spec/parslet/atoms/repetition_spec.rb +24 -0
- data/spec/parslet/atoms/scope_spec.rb +26 -0
- data/spec/parslet/atoms/sequence_spec.rb +28 -0
- data/spec/parslet/atoms/str_spec.rb +15 -0
- data/spec/parslet/atoms/visitor_spec.rb +101 -0
- data/spec/parslet/atoms_spec.rb +488 -0
- data/spec/parslet/convenience_spec.rb +54 -0
- data/spec/parslet/error_reporter/contextual_spec.rb +118 -0
- data/spec/parslet/error_reporter/deepest_spec.rb +82 -0
- data/spec/parslet/error_reporter/tree_spec.rb +7 -0
- data/spec/parslet/export_spec.rb +40 -0
- data/spec/parslet/expression/treetop_spec.rb +74 -0
- data/spec/parslet/minilisp.citrus +29 -0
- data/spec/parslet/minilisp.tt +29 -0
- data/spec/parslet/parser_spec.rb +36 -0
- data/spec/parslet/parslet_spec.rb +38 -0
- data/spec/parslet/pattern_spec.rb +272 -0
- data/spec/parslet/position_spec.rb +14 -0
- data/spec/parslet/rig/rspec_spec.rb +54 -0
- data/spec/parslet/scope_spec.rb +45 -0
- data/spec/parslet/slice_spec.rb +186 -0
- data/spec/parslet/source/line_cache_spec.rb +74 -0
- data/spec/parslet/source_spec.rb +210 -0
- data/spec/parslet/transform/context_spec.rb +56 -0
- data/spec/parslet/transform_spec.rb +183 -0
- data/spec/spec_helper.rb +74 -0
- data/spec/support/opal.rb +8 -0
- data/spec/support/opal.rb.erb +14 -0
- data/spec/support/parslet_matchers.rb +96 -0
- metadata +240 -0
data/README.adoc
ADDED
@@ -0,0 +1,454 @@
|
|
1
|
+
= Plurimath Parslet
|
2
|
+
|
3
|
+
This is a fork of the original Parslet gem, which is a parser library for Ruby.
|
4
|
+
|
5
|
+
This differs from the original Parslet gem, such that it is Opal (JS based Ruby)
|
6
|
+
compatible.
|
7
|
+
|
8
|
+
== Introduction
|
9
|
+
|
10
|
+
Parslet makes developing complex parsers easy. It does so by:
|
11
|
+
|
12
|
+
* providing the best error reporting possible
|
13
|
+
* not generating reams of code for you to debug
|
14
|
+
|
15
|
+
Parslet takes the long way around to make your job easier. It allows for
|
16
|
+
incremental language construction. Often, you start out small, implementing
|
17
|
+
the atoms of your language first; _parslet_ takes pride in making this
|
18
|
+
possible.
|
19
|
+
|
20
|
+
Eager to try this out? Please see the associated web site:
|
21
|
+
https://kschiess.github.io/parslet/
|
22
|
+
|
23
|
+
== Synopsis
|
24
|
+
|
25
|
+
[source,ruby]
|
26
|
+
----
|
27
|
+
require 'parslet'
|
28
|
+
include Parslet
|
29
|
+
|
30
|
+
# parslet parses strings
|
31
|
+
str('foo').
|
32
|
+
parse('foo') # => "foo"@0
|
33
|
+
|
34
|
+
# it matches character sets
|
35
|
+
match['abc'].parse('a') # => "a"@0
|
36
|
+
match['abc'].parse('b') # => "b"@0
|
37
|
+
match['abc'].parse('c') # => "c"@0
|
38
|
+
|
39
|
+
# and it annotates its output
|
40
|
+
str('foo').as(:important_bit).
|
41
|
+
parse('foo') # => {:important_bit=>"foo"@0}
|
42
|
+
|
43
|
+
# you can construct parsers with just a few lines
|
44
|
+
quote = str('"')
|
45
|
+
simple_string = quote >> (quote.absent? >> any).repeat >> quote
|
46
|
+
|
47
|
+
simple_string.
|
48
|
+
parse('"Simple Simple Simple"') # => "\"Simple Simple Simple\""@0
|
49
|
+
|
50
|
+
# or by making a fuss about it
|
51
|
+
class Smalltalk < Parslet::Parser
|
52
|
+
root :smalltalk
|
53
|
+
|
54
|
+
rule(:smalltalk) { statements }
|
55
|
+
rule(:statements) {
|
56
|
+
# insert smalltalk parser here (outside of the scope of this readme)
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
# and then
|
61
|
+
Smalltalk.new.parse('smalltalk')
|
62
|
+
----
|
63
|
+
|
64
|
+
== Features
|
65
|
+
|
66
|
+
* Tools for every part of the parser chain
|
67
|
+
* Transformers generate Abstract Syntax Trees
|
68
|
+
* Accelerators transform parsers, making them quite a bit faster
|
69
|
+
* Pluggable error reporters
|
70
|
+
* Graphviz export for your parser
|
71
|
+
* Rspec testing support rig
|
72
|
+
* Simply Ruby, composable and hackable
|
73
|
+
|
74
|
+
== Compatibility
|
75
|
+
|
76
|
+
This library is intended to work with Ruby variants >= 2.7. It has been tested on:
|
77
|
+
|
78
|
+
* MRI Ruby 2.7+
|
79
|
+
* JRuby
|
80
|
+
* Opal (works as Ruby 3.2, with the limitation of `StringScanner#charpos` not being
|
81
|
+
available, which is used in some examples)
|
82
|
+
|
83
|
+
Please report as a bug if you encounter issues.
|
84
|
+
|
85
|
+
== Status
|
86
|
+
|
87
|
+
Production worthy.
|
88
|
+
|
89
|
+
== Installation
|
90
|
+
|
91
|
+
Add this line to your application's Gemfile:
|
92
|
+
|
93
|
+
[source,ruby]
|
94
|
+
----
|
95
|
+
gem 'plurimath-parslet'
|
96
|
+
----
|
97
|
+
|
98
|
+
And then execute:
|
99
|
+
|
100
|
+
[source,bash]
|
101
|
+
----
|
102
|
+
$ bundle install
|
103
|
+
----
|
104
|
+
|
105
|
+
Or install it yourself as:
|
106
|
+
|
107
|
+
[source,bash]
|
108
|
+
----
|
109
|
+
$ gem install plurimath-parslet
|
110
|
+
----
|
111
|
+
|
112
|
+
== Usage
|
113
|
+
|
114
|
+
[source,ruby]
|
115
|
+
----
|
116
|
+
require 'parslet'
|
117
|
+
----
|
118
|
+
|
119
|
+
The gem maintains the original `parslet` namespace for compatibility.
|
120
|
+
|
121
|
+
== Examples
|
122
|
+
|
123
|
+
The `examples/` directory contains 25 interactive examples that demonstrate various parsing scenarios and techniques. These examples are designed to help you learn parslet by showing real-world parsing problems and their solutions.
|
124
|
+
|
125
|
+
=== How to run examples
|
126
|
+
|
127
|
+
Each example can be run directly from the command line:
|
128
|
+
|
129
|
+
[source,bash]
|
130
|
+
----
|
131
|
+
$ ruby examples/run_boolean_algebra.rb
|
132
|
+
$ ruby examples/run_json.rb
|
133
|
+
$ ruby examples/run_calc.rb
|
134
|
+
----
|
135
|
+
|
136
|
+
=== Available examples
|
137
|
+
|
138
|
+
[cols="1,3,2", options="header"]
|
139
|
+
|===
|
140
|
+
| Example | Description | Key Features
|
141
|
+
|
142
|
+
| `run_boolean_algebra.rb`
|
143
|
+
| Boolean expression parser with operator precedence
|
144
|
+
| AND/OR operators, parentheses, DNF transformation
|
145
|
+
|
146
|
+
| `run_calc.rb`
|
147
|
+
| Basic calculator with arithmetic operations
|
148
|
+
| Operator precedence, expression evaluation
|
149
|
+
|
150
|
+
| `run_capture.rb`
|
151
|
+
| Named capture groups and result extraction
|
152
|
+
| Capture syntax, result processing
|
153
|
+
|
154
|
+
| `run_comments.rb`
|
155
|
+
| Comment parsing (single-line and multi-line)
|
156
|
+
| Comment syntax, nested structures
|
157
|
+
|
158
|
+
| `run_deepest_errors.rb`
|
159
|
+
| Advanced error reporting and debugging
|
160
|
+
| Error handling, parse failure analysis
|
161
|
+
|
162
|
+
| `run_documentation.rb`
|
163
|
+
| Markdown-style documentation parser
|
164
|
+
| Headers, lists, formatting, code blocks
|
165
|
+
|
166
|
+
| `run_email_parser.rb`
|
167
|
+
| Email address validation and parsing
|
168
|
+
| Email format validation, domain parsing
|
169
|
+
|
170
|
+
| `run_empty.rb`
|
171
|
+
| Empty rule behavior and edge cases
|
172
|
+
| Empty matches, optional content
|
173
|
+
|
174
|
+
| `run_erb.rb`
|
175
|
+
| ERB template parsing
|
176
|
+
| Template syntax, embedded Ruby code
|
177
|
+
|
178
|
+
| `run_ip_address.rb`
|
179
|
+
| IPv4 address parsing and validation
|
180
|
+
| IP format validation, octet parsing
|
181
|
+
|
182
|
+
| `run_json.rb`
|
183
|
+
| Complete JSON parser with all data types
|
184
|
+
| Objects, arrays, strings, numbers, booleans, null
|
185
|
+
|
186
|
+
| `run_local.rb`
|
187
|
+
| Local variable scoping demonstration
|
188
|
+
| Variable declarations, scope management
|
189
|
+
|
190
|
+
| `run_mathn.rb`
|
191
|
+
| Mathematical expression parsing
|
192
|
+
| Math operations, Ruby mathn compatibility
|
193
|
+
|
194
|
+
| `run_minilisp.rb`
|
195
|
+
| Minimal Lisp interpreter
|
196
|
+
| S-expressions, nested structures, symbols
|
197
|
+
|
198
|
+
| `run_modularity.rb`
|
199
|
+
| Modular parser design patterns
|
200
|
+
| Parser composition, reusable components
|
201
|
+
|
202
|
+
| `run_nested_errors.rb`
|
203
|
+
| Nested error handling strategies
|
204
|
+
| Error propagation, context preservation
|
205
|
+
|
206
|
+
| `run_optimized_erb.rb`
|
207
|
+
| Performance-optimized ERB parsing
|
208
|
+
| Greedy parsing, performance comparison
|
209
|
+
|
210
|
+
| `run_parens.rb`
|
211
|
+
| Parentheses matching and balancing
|
212
|
+
| Balanced expressions, nesting validation
|
213
|
+
|
214
|
+
| `run_prec_calc.rb`
|
215
|
+
| Calculator with full operator precedence
|
216
|
+
| Complex precedence rules, associativity
|
217
|
+
|
218
|
+
| `run_readme.rb`
|
219
|
+
| README-style documentation parsing
|
220
|
+
| Document structure, sections, formatting
|
221
|
+
|
222
|
+
| `run_scopes.rb`
|
223
|
+
| Variable scope handling in parsers
|
224
|
+
| Block scoping, variable shadowing
|
225
|
+
|
226
|
+
| `run_seasons.rb`
|
227
|
+
| Transform chains and data processing
|
228
|
+
| Multi-stage transformations, data flow
|
229
|
+
|
230
|
+
| `run_sentence.rb`
|
231
|
+
| Natural language sentence parsing
|
232
|
+
| Grammar rules, sentence structure
|
233
|
+
|
234
|
+
| `run_simple_xml.rb`
|
235
|
+
| Basic XML parsing
|
236
|
+
| Tags, attributes, nested elements
|
237
|
+
|
238
|
+
| `run_string_parser.rb`
|
239
|
+
| String literal parsing with escaping
|
240
|
+
| Quote handling, escape sequences
|
241
|
+
|===
|
242
|
+
|
243
|
+
=== Example structure
|
244
|
+
|
245
|
+
Each example follows a consistent structure:
|
246
|
+
|
247
|
+
* Educational comments explaining the parsing problem
|
248
|
+
* Sample input data demonstrating various test cases
|
249
|
+
* Parser demonstration showing both successful parsing and error handling
|
250
|
+
* Output explanation describing what the parser supports and how it works
|
251
|
+
|
252
|
+
=== Learning path
|
253
|
+
|
254
|
+
For beginners, we recommend starting with these examples in order:
|
255
|
+
|
256
|
+
. `run_simple_xml.rb` - Basic parsing concepts
|
257
|
+
. `run_calc.rb` - Operator precedence and evaluation
|
258
|
+
. `run_json.rb` - Complex data structures
|
259
|
+
. `run_boolean_algebra.rb` - Transformations and logic
|
260
|
+
. `run_minilisp.rb` - Advanced parsing techniques
|
261
|
+
|
262
|
+
== Development
|
263
|
+
|
264
|
+
After checking out the repo, run:
|
265
|
+
|
266
|
+
[source,bash]
|
267
|
+
----
|
268
|
+
$ bundle install
|
269
|
+
----
|
270
|
+
|
271
|
+
=== Available rake tasks
|
272
|
+
|
273
|
+
==== Testing
|
274
|
+
|
275
|
+
* `rake spec` - Run all tests (438 examples covering all functionality)
|
276
|
+
* `rake spec:unit` - Run unit tests only
|
277
|
+
* `rake spec:opal` - Run Opal (JavaScript) tests (437 examples)
|
278
|
+
|
279
|
+
===== Running Opal tests
|
280
|
+
|
281
|
+
The Opal test suite runs the same specs as the Ruby test suite but in a
|
282
|
+
JavaScript environment via Node.js. This ensures parslet works correctly when
|
283
|
+
compiled to JavaScript with Opal.
|
284
|
+
|
285
|
+
To run all Opal tests:
|
286
|
+
|
287
|
+
[source,bash]
|
288
|
+
----
|
289
|
+
$ bundle exec rake spec:opal
|
290
|
+
----
|
291
|
+
|
292
|
+
The Opal specs are located in the `spec-opal/` directory and mirror the
|
293
|
+
structure of the main `spec/` directory.
|
294
|
+
|
295
|
+
NOTE: Some Opal tests may fail due to environment differences between Ruby and
|
296
|
+
JavaScript execution, but the core parsing functionality is fully supported.
|
297
|
+
|
298
|
+
==== Benchmarking
|
299
|
+
|
300
|
+
* `rake benchmark` - Run quick benchmarks (alias for benchmark:quick)
|
301
|
+
* `rake benchmark:quick` - Run example-focused benchmarks only
|
302
|
+
* `rake benchmark:examples` - Run example-focused benchmarks
|
303
|
+
* `rake benchmark:all` - Run comprehensive benchmark suite (all categories)
|
304
|
+
* `rake benchmark:export` - Run benchmarks and export results to JSON/YAML files
|
305
|
+
|
306
|
+
===== What gets benchmarked
|
307
|
+
|
308
|
+
The benchmark suite measures parsing performance across different scenarios:
|
309
|
+
|
310
|
+
**Basic Parsing Operations**
|
311
|
+
|
312
|
+
* `str('hello')` - Simple string matching performance
|
313
|
+
* `match('[a-z]').repeat(1)` - Character class matching with repetition
|
314
|
+
* Email-like pattern matching - Complex regex-style parsing (`user@example.com`)
|
315
|
+
|
316
|
+
**Calculator Parser** (from `example/calc.rb`)
|
317
|
+
|
318
|
+
* Simple expressions: `1+2`
|
319
|
+
* Medium complexity: `1+2*3-4/2`
|
320
|
+
* Complex expressions: `123*456+789-321/3*2+1`
|
321
|
+
* Full pipeline (parse + transform + evaluate)
|
322
|
+
|
323
|
+
**JSON Parser** (from `example/json.rb`)
|
324
|
+
|
325
|
+
* Simple objects: `{"key": "value"}`
|
326
|
+
* Arrays: `[1, 2, 3, 4, 5]`
|
327
|
+
* Complex nested structures with multiple data types
|
328
|
+
* Parse vs. transform performance comparison
|
329
|
+
|
330
|
+
**String Parsing**
|
331
|
+
|
332
|
+
* Simple quoted strings: `"hello world"`
|
333
|
+
* Long strings (1000+ characters)
|
334
|
+
* Escaped strings with backslash sequences: `"hello \"world\" with escapes"`
|
335
|
+
|
336
|
+
**Repetition Patterns**
|
337
|
+
|
338
|
+
* `repeat(1)` with varying input lengths (short/medium/long)
|
339
|
+
* Bounded repetition `repeat(3,6)`
|
340
|
+
* Optional repetition `repeat` (zero or more)
|
341
|
+
* Performance scaling with input size
|
342
|
+
|
343
|
+
**Transform Operations**
|
344
|
+
|
345
|
+
* Simple AST transformations (number/string conversion)
|
346
|
+
* Medium complexity (multiple rules, arrays)
|
347
|
+
* Complex nested transformations with multiple rule types
|
348
|
+
|
349
|
+
===== Sample benchmark output
|
350
|
+
|
351
|
+
[example]
|
352
|
+
====
|
353
|
+
[source]
|
354
|
+
----
|
355
|
+
Plurimath Parslet Performance Benchmarks
|
356
|
+
==================================================
|
357
|
+
|
358
|
+
Basic Parsing Operations
|
359
|
+
------------------------------
|
360
|
+
ruby 3.3.2 (2024-05-30 revision e5a195edf6) [arm64-darwin23]
|
361
|
+
Warming up --------------------------------------
|
362
|
+
str('hello') 17.235k i/100ms
|
363
|
+
match('[a-z]').repeat(1)
|
364
|
+
3.502k i/100ms
|
365
|
+
email-like pattern 2.780k i/100ms
|
366
|
+
Calculating -------------------------------------
|
367
|
+
str('hello') 174.636k (± 2.1%) i/s (5.73 μs/i)
|
368
|
+
match('[a-z]').repeat(1)
|
369
|
+
35.182k (± 2.6%) i/s (28.42 μs/i)
|
370
|
+
email-like pattern 27.874k (± 8.5%) i/s (35.88 μs/i)
|
371
|
+
|
372
|
+
Comparison:
|
373
|
+
str('hello'): 174636.1 i/s
|
374
|
+
match('[a-z]').repeat(1): 35182.1 i/s - 4.96x slower
|
375
|
+
email-like pattern: 27873.8 i/s - 6.27x slower
|
376
|
+
|
377
|
+
Calculator Parser Benchmarks
|
378
|
+
------------------------------
|
379
|
+
parse simple: '1+2' 18.791k (± 3.2%) i/s (53.22 μs/i)
|
380
|
+
parse medium: '1+2*3-4/2'
|
381
|
+
8.871k (± 6.4%) i/s (112.73 μs/i)
|
382
|
+
parse complex: '123*456+789-321/3*2+1'
|
383
|
+
5.872k (± 4.3%) i/s (170.30 μs/i)
|
384
|
+
full calc simple 7.516k (± 8.5%) i/s (133.06 μs/i)
|
385
|
+
full calc complex 3.018k (± 1.9%) i/s (331.34 μs/i)
|
386
|
+
----
|
387
|
+
====
|
388
|
+
|
389
|
+
===== Benchmark results export
|
390
|
+
|
391
|
+
Results are exported to multiple formats for analysis:
|
392
|
+
|
393
|
+
* `benchmark/results.json` - Detailed benchmark data with iterations/second, standard deviation, and microseconds per iteration
|
394
|
+
* `benchmark/results.yaml` - YAML format results for easy reading
|
395
|
+
* `benchmark/summary.json` - Performance summary with fastest/slowest operations and insights
|
396
|
+
* `benchmark/summary.yaml` - YAML format summary
|
397
|
+
|
398
|
+
The exported data includes:
|
399
|
+
|
400
|
+
* Ruby version and platform information
|
401
|
+
* Parslet version and benchmark tool versions
|
402
|
+
* Detailed performance metrics for each test case
|
403
|
+
* Statistical analysis (standard deviation, error percentages)
|
404
|
+
* Performance comparisons and insights
|
405
|
+
* Identification of performance bottlenecks and optimization opportunities
|
406
|
+
|
407
|
+
==== Building and distribution
|
408
|
+
|
409
|
+
* `rake build` - Build plurimath-parslet-3.0.0.gem into the pkg directory
|
410
|
+
* `rake build:checksum` - Generate SHA512 checksum of the gem
|
411
|
+
* `rake install` - Build and install gem into system gems
|
412
|
+
* `rake install:local` - Build and install gem without network access
|
413
|
+
* `rake release[remote]` - Create tag and push gem to rubygems.org
|
414
|
+
|
415
|
+
==== Documentation
|
416
|
+
|
417
|
+
* `rake rdoc` - Build RDoc HTML files
|
418
|
+
* `rake rdoc:coverage` - Print RDoc coverage report
|
419
|
+
* `rake rerdoc` - Rebuild RDoc HTML files
|
420
|
+
|
421
|
+
==== Maintenance
|
422
|
+
|
423
|
+
* `rake clean` - Remove temporary products
|
424
|
+
* `rake clobber` - Remove generated files
|
425
|
+
* `rake clobber_rdoc` - Remove RDoc HTML files
|
426
|
+
* `rake stat` - Print lines of code statistics
|
427
|
+
|
428
|
+
=== Example coverage
|
429
|
+
|
430
|
+
All 25 examples in the `examples/` directory are covered by specs and tested automatically:
|
431
|
+
|
432
|
+
* boolean_algebra.rb, calc.rb, capture.rb, comments.rb, deepest_errors.rb
|
433
|
+
* documentation.rb, email_parser.rb, empty.rb, erb.rb, ip_address.rb
|
434
|
+
* json.rb, local.rb, mathn.rb, minilisp.rb, modularity.rb
|
435
|
+
* nested_errors.rb, optimized_erb.rb, parens.rb, prec_calc.rb, readme.rb
|
436
|
+
* scopes.rb, seasons.rb, sentence.rb, simple_xml.rb, string_parser.rb
|
437
|
+
|
438
|
+
== Contributing
|
439
|
+
|
440
|
+
. Fork it
|
441
|
+
. Create your feature branch (`git checkout -b my-new-feature`)
|
442
|
+
. Commit your changes (`git commit -am 'Add some feature'`)
|
443
|
+
. Push to the branch (`git push origin my-new-feature`)
|
444
|
+
. Create a new Pull Request
|
445
|
+
|
446
|
+
== License
|
447
|
+
|
448
|
+
The gem is available as open source under the terms of the MIT License.
|
449
|
+
|
450
|
+
== Copyright
|
451
|
+
|
452
|
+
(c) 2010-2018 Kaspar Schiess.
|
453
|
+
|
454
|
+
2025 Augmented by Ribose Inc.
|
data/Rakefile
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/gem_tasks'
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
require 'rdoc/task'
|
6
|
+
|
7
|
+
begin
|
8
|
+
require 'opal/rspec/rake_task'
|
9
|
+
rescue LoadError, NoMethodError
|
10
|
+
# Opal not available or incompatible with current Ruby version
|
11
|
+
end
|
12
|
+
|
13
|
+
desc 'Run all tests'
|
14
|
+
RSpec::Core::RakeTask.new(:spec)
|
15
|
+
|
16
|
+
namespace :spec do
|
17
|
+
desc 'Run unit tests only'
|
18
|
+
RSpec::Core::RakeTask.new(:unit) do |task|
|
19
|
+
task.pattern = 'spec/parslet/**/*_spec.rb'
|
20
|
+
end
|
21
|
+
|
22
|
+
if defined?(Opal::RSpec::RakeTask)
|
23
|
+
desc 'Run Opal (JavaScript) tests'
|
24
|
+
Opal::RSpec::RakeTask.new(:opal) do |task|
|
25
|
+
task.append_path 'lib'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
RDoc::Task.new do |rdoc|
|
31
|
+
rdoc.rdoc_dir = 'rdoc'
|
32
|
+
rdoc.title = 'Plurimath Parslet'
|
33
|
+
rdoc.options << '--line-numbers'
|
34
|
+
rdoc.rdoc_files.include('README.adoc')
|
35
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
36
|
+
end
|
37
|
+
|
38
|
+
desc 'Print LOC statistics'
|
39
|
+
task :stat do
|
40
|
+
%w[lib spec example].each do |dir|
|
41
|
+
next unless Dir.exist?(dir)
|
42
|
+
|
43
|
+
loc = `find #{dir} -name "*.rb" | xargs wc -l | grep 'total'`.split.first.to_i
|
44
|
+
printf("%20s %d\n", dir, loc)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
namespace :benchmark do
|
49
|
+
desc 'Run comprehensive benchmark suite'
|
50
|
+
task :all do
|
51
|
+
ruby 'benchmark/benchmark_suite.rb'
|
52
|
+
end
|
53
|
+
|
54
|
+
desc 'Run example-focused benchmarks'
|
55
|
+
task :examples do
|
56
|
+
ruby 'benchmark/example_benchmarks.rb'
|
57
|
+
end
|
58
|
+
|
59
|
+
desc 'Run benchmarks and export results to JSON/YAML'
|
60
|
+
task :export do
|
61
|
+
ruby 'benchmark/benchmark_runner.rb'
|
62
|
+
end
|
63
|
+
|
64
|
+
desc 'Run quick benchmark (examples only)'
|
65
|
+
task quick: :examples
|
66
|
+
end
|
67
|
+
|
68
|
+
desc 'Run quick benchmarks'
|
69
|
+
task benchmark: 'benchmark:quick'
|
70
|
+
|
71
|
+
task default: :spec
|
@@ -0,0 +1,62 @@
|
|
1
|
+
|
2
|
+
# @api private
|
3
|
+
module Parslet::Accelerator
|
4
|
+
class Application
|
5
|
+
def initialize atom, rules
|
6
|
+
@atom = atom
|
7
|
+
@rules = rules
|
8
|
+
end
|
9
|
+
|
10
|
+
def call
|
11
|
+
@atom.accept(self)
|
12
|
+
end
|
13
|
+
|
14
|
+
def visit_parser(root)
|
15
|
+
transform root.accept(self)
|
16
|
+
end
|
17
|
+
def visit_entity(name, block)
|
18
|
+
transform Parslet::Atoms::Entity.new(name) { block.call.accept(self) }
|
19
|
+
end
|
20
|
+
def visit_named(name, atom)
|
21
|
+
transform Parslet::Atoms::Named.new(atom.accept(self), name)
|
22
|
+
end
|
23
|
+
def visit_repetition(tag, min, max, atom)
|
24
|
+
transform Parslet::Atoms::Repetition.new(atom.accept(self), min, max, tag)
|
25
|
+
end
|
26
|
+
def visit_alternative(alternatives)
|
27
|
+
transform Parslet::Atoms::Alternative.new(
|
28
|
+
*alternatives.map { |atom| atom.accept(self) })
|
29
|
+
end
|
30
|
+
def visit_sequence(sequence)
|
31
|
+
transform Parslet::Atoms::Sequence.new(
|
32
|
+
*sequence.map { |atom| atom.accept(self) })
|
33
|
+
end
|
34
|
+
def visit_lookahead(positive, atom)
|
35
|
+
transform Parslet::Atoms::Lookahead.new(atom, positive)
|
36
|
+
end
|
37
|
+
def visit_re(regexp)
|
38
|
+
transform Parslet::Atoms::Re.new(regexp)
|
39
|
+
end
|
40
|
+
def visit_str(str)
|
41
|
+
transform Parslet::Atoms::Str.new(str)
|
42
|
+
end
|
43
|
+
|
44
|
+
def transform atom
|
45
|
+
@rules.each do |expr, action|
|
46
|
+
# Try and match each rule in turn
|
47
|
+
binding = Parslet::Accelerator.match(atom, expr)
|
48
|
+
if binding
|
49
|
+
# On a successful match, allow the rule action to transform the
|
50
|
+
# parslet into something new.
|
51
|
+
ctx = Parslet::Context.new(binding)
|
52
|
+
return ctx.instance_eval(&action)
|
53
|
+
end
|
54
|
+
end # rules.each
|
55
|
+
|
56
|
+
# If no rule matches, this is the fallback - a clean new parslet atom.
|
57
|
+
return atom
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
require 'parslet/context'
|
@@ -0,0 +1,112 @@
|
|
1
|
+
|
2
|
+
require 'parslet/atoms/visitor'
|
3
|
+
|
4
|
+
module Parslet::Accelerator
|
5
|
+
# @api private
|
6
|
+
class Apply
|
7
|
+
def initialize(engine, expr)
|
8
|
+
@engine = engine
|
9
|
+
@expr = expr
|
10
|
+
end
|
11
|
+
|
12
|
+
def visit_parser(root)
|
13
|
+
false
|
14
|
+
end
|
15
|
+
def visit_entity(name, block)
|
16
|
+
false
|
17
|
+
end
|
18
|
+
def visit_named(name, atom)
|
19
|
+
match(:as) do |key|
|
20
|
+
@engine.try_bind(key, name)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
def visit_repetition(tag, min, max, atom)
|
24
|
+
match(:rep) do |e_min, e_max, expr|
|
25
|
+
e_min == min && e_max == max && @engine.match(atom, expr)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
def visit_alternative(alternatives)
|
29
|
+
match(:alt) do |*expressions|
|
30
|
+
return false if alternatives.size != expressions.size
|
31
|
+
|
32
|
+
alternatives.zip(expressions).all? do |atom, expr|
|
33
|
+
@engine.match(atom, expr)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
def visit_sequence(sequence)
|
38
|
+
match(:seq) do |*expressions|
|
39
|
+
return false if sequence.size != expressions.size
|
40
|
+
|
41
|
+
sequence.zip(expressions).all? do |atom, expr|
|
42
|
+
@engine.match(atom, expr)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
def visit_lookahead(positive, atom)
|
47
|
+
match(:absent) do |expr|
|
48
|
+
return positive == false && @engine.match(atom, expr)
|
49
|
+
end
|
50
|
+
match(:present) do |expr|
|
51
|
+
return positive == true && @engine.match(atom, expr)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
def visit_re(regexp)
|
55
|
+
match(:re) do |*bind_conditions|
|
56
|
+
bind_conditions.all? { |bind_cond|
|
57
|
+
@engine.try_bind(bind_cond, regexp) }
|
58
|
+
end
|
59
|
+
end
|
60
|
+
def visit_str(str)
|
61
|
+
match(:str) do |*bind_conditions|
|
62
|
+
bind_conditions.all? { |bind_cond|
|
63
|
+
@engine.try_bind(bind_cond, str) }
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def match(type_tag)
|
68
|
+
expr_tag = @expr.type
|
69
|
+
if expr_tag == type_tag
|
70
|
+
yield *@expr.args
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# @api private
|
76
|
+
class Engine
|
77
|
+
attr_reader :bindings
|
78
|
+
|
79
|
+
def initialize
|
80
|
+
@bindings = {}
|
81
|
+
end
|
82
|
+
|
83
|
+
def match(atom, expr)
|
84
|
+
atom.accept(
|
85
|
+
Apply.new(self, expr))
|
86
|
+
end
|
87
|
+
|
88
|
+
def try_bind(variable, value)
|
89
|
+
if bound? variable
|
90
|
+
return value == lookup(variable)
|
91
|
+
else
|
92
|
+
case variable
|
93
|
+
when Symbol
|
94
|
+
bind(variable, value)
|
95
|
+
else
|
96
|
+
# This does not look like a variable - let's try matching it against
|
97
|
+
# the value:
|
98
|
+
variable === value
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
def bound? var
|
103
|
+
@bindings.has_key? var
|
104
|
+
end
|
105
|
+
def lookup var
|
106
|
+
@bindings[var]
|
107
|
+
end
|
108
|
+
def bind var, val
|
109
|
+
@bindings[var] = val
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|