parsanol 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.
Potentially problematic release.
This version of parsanol might be problematic. Click here for more details.
- checksums.yaml +7 -0
- data/HISTORY.txt +25 -0
- data/LICENSE +23 -0
- data/README.adoc +643 -0
- data/Rakefile +189 -0
- data/example/balanced-parens/basic.rb +42 -0
- data/example/balanced-parens/basic.rb.md +86 -0
- data/example/balanced-parens/parens.rb +42 -0
- data/example/balanced-parens/ruby_transform.rb +162 -0
- data/example/big.erb +73 -0
- data/example/boolean-algebra/basic.rb +70 -0
- data/example/boolean-algebra/basic.rb.md +108 -0
- data/example/boolean-algebra/ruby_transform.rb +263 -0
- data/example/calculator/basic.rb +153 -0
- data/example/calculator/basic.rb.md +120 -0
- data/example/calculator/pattern.rb +153 -0
- data/example/calculator/ruby_transform.rb +156 -0
- data/example/calculator/ruby_transform.rb.md +32 -0
- data/example/calculator/serialized.rb +257 -0
- data/example/calculator/serialized.rb.md +32 -0
- data/example/calculator/transform.rb +153 -0
- data/example/calculator/zero_copy.rb +269 -0
- data/example/calculator/zero_copy.rb.md +36 -0
- data/example/capture/basic.rb +49 -0
- data/example/capture/basic.rb.md +106 -0
- data/example/capture/example.json +39 -0
- data/example/comments/basic.rb +35 -0
- data/example/comments/basic.rb.md +110 -0
- data/example/csv/ruby_transform.rb +148 -0
- data/example/csv/ruby_transform.rb.md +131 -0
- data/example/csv/serialized.rb +201 -0
- data/example/csv/serialized.rb.md +31 -0
- data/example/csv/zero_copy.rb +276 -0
- data/example/csv/zero_copy.rb.md +36 -0
- data/example/custom_atoms/indent_atom.rb +79 -0
- data/example/deepest-errors/basic.rb +131 -0
- data/example/deepest-errors/basic.rb.md +152 -0
- data/example/documentation/basic.rb +18 -0
- data/example/documentation/basic.rb.md +97 -0
- data/example/email/basic.rb +55 -0
- data/example/email/basic.rb.md +102 -0
- data/example/email/ruby_transform.rb +106 -0
- data/example/empty/basic.rb +13 -0
- data/example/empty/basic.rb.md +73 -0
- data/example/empty/example.json +38 -0
- data/example/erb/basic.rb +47 -0
- data/example/erb/basic.rb.md +103 -0
- data/example/erb/optimized.rb +42 -0
- data/example/error-reporting/basic.rb +132 -0
- data/example/error-reporting/basic.rb.md +122 -0
- data/example/expression-evaluator/basic.rb +284 -0
- data/example/expression-evaluator/basic.rb.md +138 -0
- data/example/ini/basic.rb +154 -0
- data/example/ini/basic.rb.md +129 -0
- data/example/ini/ruby_transform.rb +154 -0
- data/example/ip-address/basic.rb +125 -0
- data/example/ip-address/basic.rb.md +139 -0
- data/example/iso-6709/basic.rb +231 -0
- data/example/iso-6709/basic.rb.md +143 -0
- data/example/iso-8601/basic.rb +275 -0
- data/example/iso-8601/basic.rb.md +149 -0
- data/example/json/basic.rb +128 -0
- data/example/json/basic.rb.md +121 -0
- data/example/json/pattern.rb +128 -0
- data/example/json/ruby_transform.rb +200 -0
- data/example/json/ruby_transform.rb.md +32 -0
- data/example/json/serialized.rb +233 -0
- data/example/json/serialized.rb.md +31 -0
- data/example/json/transform.rb +128 -0
- data/example/json/zero_copy.rb +316 -0
- data/example/json/zero_copy.rb.md +36 -0
- data/example/local/basic.rb +34 -0
- data/example/local/basic.rb.md +91 -0
- data/example/local/example.json +38 -0
- data/example/markdown/basic.rb +287 -0
- data/example/markdown/basic.rb.md +160 -0
- data/example/markup/basic.rb +173 -0
- data/example/markup/basic.rb.md +118 -0
- data/example/mathn/basic.rb +47 -0
- data/example/mathn/basic.rb.md +96 -0
- data/example/mathn/example.json +39 -0
- data/example/minilisp/basic.rb +94 -0
- data/example/minilisp/basic.rb.md +133 -0
- data/example/modularity/basic.rb +47 -0
- data/example/modularity/basic.rb.md +152 -0
- data/example/nested-errors/basic.rb +132 -0
- data/example/nested-errors/basic.rb.md +157 -0
- data/example/output/boolean_algebra.out +4 -0
- data/example/output/calc.out +1 -0
- data/example/output/capture.out +3 -0
- data/example/output/comments.out +8 -0
- data/example/output/deepest_errors.out +54 -0
- data/example/output/documentation.err +4 -0
- data/example/output/documentation.out +1 -0
- data/example/output/email_parser.out +2 -0
- data/example/output/empty.err +1 -0
- data/example/output/erb.out +7 -0
- data/example/output/ignore.out +1 -0
- data/example/output/ignore_whitespace.out +1 -0
- data/example/output/ip_address.out +9 -0
- data/example/output/json.out +5 -0
- data/example/output/local.out +3 -0
- data/example/output/mathn.out +4 -0
- data/example/output/minilisp.out +5 -0
- data/example/output/modularity.out +0 -0
- data/example/output/nested_errors.out +54 -0
- data/example/output/optimized_erb.out +1 -0
- data/example/output/parens.out +8 -0
- data/example/output/prec_calc.out +5 -0
- data/example/output/readme.out +1 -0
- data/example/output/scopes.out +1 -0
- data/example/output/seasons.out +28 -0
- data/example/output/sentence.out +1 -0
- data/example/output/simple_xml.out +2 -0
- data/example/output/string_parser.out +3 -0
- data/example/prec-calc/basic.rb +71 -0
- data/example/prec-calc/basic.rb.md +114 -0
- data/example/readme/basic.rb +30 -0
- data/example/readme/basic.rb.md +80 -0
- data/example/scopes/basic.rb +15 -0
- data/example/scopes/basic.rb.md +73 -0
- data/example/scopes/example.json +38 -0
- data/example/seasons/basic.rb +46 -0
- data/example/seasons/basic.rb.md +117 -0
- data/example/seasons/example.json +40 -0
- data/example/sentence/basic.rb +36 -0
- data/example/sentence/basic.rb.md +81 -0
- data/example/sexp/ruby_transform.rb +180 -0
- data/example/sexp/ruby_transform.rb.md +143 -0
- data/example/simple-xml/basic.rb +54 -0
- data/example/simple-xml/basic.rb.md +125 -0
- data/example/simple.lit +3 -0
- data/example/string-literal/basic.rb +77 -0
- data/example/string-literal/basic.rb.md +128 -0
- data/example/test.lit +4 -0
- data/example/toml/basic.rb +226 -0
- data/example/toml/basic.rb.md +173 -0
- data/example/url/basic.rb +219 -0
- data/example/url/basic.rb.md +142 -0
- data/example/url/ruby_transform.rb +219 -0
- data/example/yaml/basic.rb +216 -0
- data/example/yaml/basic.rb.md +148 -0
- data/ext/parsanol_native/extconf.rb +4 -0
- data/lib/parsanol/accelerator/application.rb +62 -0
- data/lib/parsanol/accelerator/engine.rb +112 -0
- data/lib/parsanol/accelerator.rb +162 -0
- data/lib/parsanol/ast_visitor.rb +122 -0
- data/lib/parsanol/atoms/alternative.rb +97 -0
- data/lib/parsanol/atoms/base.rb +214 -0
- data/lib/parsanol/atoms/can_flatten.rb +192 -0
- data/lib/parsanol/atoms/capture.rb +41 -0
- data/lib/parsanol/atoms/context.rb +351 -0
- data/lib/parsanol/atoms/context_optimized.rb +42 -0
- data/lib/parsanol/atoms/custom.rb +110 -0
- data/lib/parsanol/atoms/cut.rb +62 -0
- data/lib/parsanol/atoms/dsl.rb +130 -0
- data/lib/parsanol/atoms/dynamic.rb +33 -0
- data/lib/parsanol/atoms/entity.rb +55 -0
- data/lib/parsanol/atoms/ignored.rb +28 -0
- data/lib/parsanol/atoms/infix.rb +121 -0
- data/lib/parsanol/atoms/lookahead.rb +64 -0
- data/lib/parsanol/atoms/named.rb +50 -0
- data/lib/parsanol/atoms/re.rb +61 -0
- data/lib/parsanol/atoms/repetition.rb +241 -0
- data/lib/parsanol/atoms/scope.rb +28 -0
- data/lib/parsanol/atoms/sequence.rb +157 -0
- data/lib/parsanol/atoms/str.rb +90 -0
- data/lib/parsanol/atoms/visitor.rb +91 -0
- data/lib/parsanol/atoms.rb +36 -0
- data/lib/parsanol/buffer.rb +130 -0
- data/lib/parsanol/builder_callbacks.rb +353 -0
- data/lib/parsanol/cause.rb +101 -0
- data/lib/parsanol/context.rb +23 -0
- data/lib/parsanol/convenience.rb +35 -0
- data/lib/parsanol/edit_tracker.rb +107 -0
- data/lib/parsanol/error_reporter/contextual.rb +122 -0
- data/lib/parsanol/error_reporter/deepest.rb +106 -0
- data/lib/parsanol/error_reporter/tree.rb +68 -0
- data/lib/parsanol/error_reporter.rb +98 -0
- data/lib/parsanol/export.rb +163 -0
- data/lib/parsanol/expression/treetop.rb +94 -0
- data/lib/parsanol/expression.rb +51 -0
- data/lib/parsanol/fast_mode.rb +145 -0
- data/lib/parsanol/first_set.rb +75 -0
- data/lib/parsanol/grammar_builder.rb +177 -0
- data/lib/parsanol/graphviz.rb +97 -0
- data/lib/parsanol/incremental_parser.rb +179 -0
- data/lib/parsanol/interval_tree.rb +215 -0
- data/lib/parsanol/lazy_result.rb +178 -0
- data/lib/parsanol/lexer.rb +146 -0
- data/lib/parsanol/native/parser.rb +630 -0
- data/lib/parsanol/native/serializer.rb +245 -0
- data/lib/parsanol/native/transformer.rb +438 -0
- data/lib/parsanol/native/types.rb +41 -0
- data/lib/parsanol/native.rb +217 -0
- data/lib/parsanol/optimizer.rb +86 -0
- data/lib/parsanol/optimizers/choice_optimizer.rb +78 -0
- data/lib/parsanol/optimizers/cut_inserter.rb +175 -0
- data/lib/parsanol/optimizers/lookahead_optimizer.rb +58 -0
- data/lib/parsanol/optimizers/quantifier_optimizer.rb +62 -0
- data/lib/parsanol/optimizers/sequence_optimizer.rb +97 -0
- data/lib/parsanol/options/ruby_transform.rb +109 -0
- data/lib/parsanol/options/serialized.rb +94 -0
- data/lib/parsanol/options/zero_copy.rb +130 -0
- data/lib/parsanol/options.rb +20 -0
- data/lib/parsanol/parallel.rb +133 -0
- data/lib/parsanol/parsanol_native.bundle +0 -0
- data/lib/parsanol/parser.rb +151 -0
- data/lib/parsanol/parslet.rb +148 -0
- data/lib/parsanol/parslet_native.bundle +0 -0
- data/lib/parsanol/pattern/binding.rb +49 -0
- data/lib/parsanol/pattern.rb +115 -0
- data/lib/parsanol/pool.rb +220 -0
- data/lib/parsanol/pools/array_pool.rb +75 -0
- data/lib/parsanol/pools/buffer_pool.rb +173 -0
- data/lib/parsanol/pools/position_pool.rb +92 -0
- data/lib/parsanol/pools/slice_pool.rb +64 -0
- data/lib/parsanol/position.rb +89 -0
- data/lib/parsanol/result.rb +44 -0
- data/lib/parsanol/result_builder.rb +208 -0
- data/lib/parsanol/result_stream.rb +262 -0
- data/lib/parsanol/rig/rspec.rb +52 -0
- data/lib/parsanol/rope.rb +78 -0
- data/lib/parsanol/scope.rb +42 -0
- data/lib/parsanol/slice.rb +172 -0
- data/lib/parsanol/source/line_cache.rb +99 -0
- data/lib/parsanol/source.rb +171 -0
- data/lib/parsanol/source_location.rb +164 -0
- data/lib/parsanol/streaming_parser.rb +124 -0
- data/lib/parsanol/string_view.rb +192 -0
- data/lib/parsanol/transform.rb +267 -0
- data/lib/parsanol/version.rb +5 -0
- data/lib/parsanol/wasm/README.md +80 -0
- data/lib/parsanol/wasm/package.json +51 -0
- data/lib/parsanol/wasm/parsanol.js +252 -0
- data/lib/parsanol/wasm/parslet.d.ts +129 -0
- data/lib/parsanol/wasm_parser.rb +239 -0
- data/lib/parsanol.rb +408 -0
- data/parsanol-ruby.gemspec +56 -0
- data/spec/acceptance/examples_spec.rb +96 -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/benchmark/comparative/runner_spec.rb +105 -0
- data/spec/integration/array_pooling_spec.rb +193 -0
- data/spec/integration/buffer_allocation_spec.rb +324 -0
- data/spec/integration/position_pooling_spec.rb +184 -0
- data/spec/integration/result_builder_spec.rb +282 -0
- data/spec/integration/rope_stringview_integration_spec.rb +188 -0
- data/spec/integration/slice_pooling_spec.rb +63 -0
- data/spec/integration/string_view_integration_spec.rb +125 -0
- data/spec/lexer_spec.rb +231 -0
- data/spec/parsanol/atom_results_spec.rb +39 -0
- data/spec/parsanol/atoms/alternative_spec.rb +26 -0
- data/spec/parsanol/atoms/base_spec.rb +127 -0
- data/spec/parsanol/atoms/capture_spec.rb +21 -0
- data/spec/parsanol/atoms/combinations_spec.rb +5 -0
- data/spec/parsanol/atoms/custom_spec.rb +79 -0
- data/spec/parsanol/atoms/dsl_spec.rb +7 -0
- data/spec/parsanol/atoms/entity_spec.rb +77 -0
- data/spec/parsanol/atoms/ignored_spec.rb +15 -0
- data/spec/parsanol/atoms/infix_spec.rb +5 -0
- data/spec/parsanol/atoms/lookahead_spec.rb +22 -0
- data/spec/parsanol/atoms/named_spec.rb +4 -0
- data/spec/parsanol/atoms/re_spec.rb +14 -0
- data/spec/parsanol/atoms/repetition_spec.rb +24 -0
- data/spec/parsanol/atoms/scope_spec.rb +26 -0
- data/spec/parsanol/atoms/sequence_spec.rb +28 -0
- data/spec/parsanol/atoms/str_spec.rb +15 -0
- data/spec/parsanol/atoms/visitor_spec.rb +101 -0
- data/spec/parsanol/atoms_spec.rb +488 -0
- data/spec/parsanol/auto_optimize_spec.rb +334 -0
- data/spec/parsanol/buffer_spec.rb +219 -0
- data/spec/parsanol/builder_callbacks_spec.rb +377 -0
- data/spec/parsanol/choice_optimizer_spec.rb +231 -0
- data/spec/parsanol/convenience_spec.rb +54 -0
- data/spec/parsanol/cut_inserter_spec.rb +248 -0
- data/spec/parsanol/cut_spec.rb +66 -0
- data/spec/parsanol/edit_tracker_spec.rb +218 -0
- data/spec/parsanol/error_reporter/contextual_spec.rb +122 -0
- data/spec/parsanol/error_reporter/deepest_spec.rb +82 -0
- data/spec/parsanol/error_reporter/tree_spec.rb +7 -0
- data/spec/parsanol/export_spec.rb +67 -0
- data/spec/parsanol/expression/treetop_spec.rb +75 -0
- data/spec/parsanol/first_set_spec.rb +298 -0
- data/spec/parsanol/interval_tree_spec.rb +205 -0
- data/spec/parsanol/lazy_result_spec.rb +288 -0
- data/spec/parsanol/lookahead_optimizer_spec.rb +252 -0
- data/spec/parsanol/minilisp.citrus +29 -0
- data/spec/parsanol/minilisp.tt +29 -0
- data/spec/parsanol/optimizer_spec.rb +459 -0
- data/spec/parsanol/options/parslet_compat_spec.rb +166 -0
- data/spec/parsanol/options/ruby_transform_spec.rb +70 -0
- data/spec/parsanol/options/serialized_spec.rb +69 -0
- data/spec/parsanol/options/zero_copy_spec.rb +230 -0
- data/spec/parsanol/parser_spec.rb +36 -0
- data/spec/parsanol/parslet_spec.rb +38 -0
- data/spec/parsanol/pattern_spec.rb +272 -0
- data/spec/parsanol/pool_spec.rb +392 -0
- data/spec/parsanol/pools/array_pool_spec.rb +356 -0
- data/spec/parsanol/pools/buffer_pool_spec.rb +365 -0
- data/spec/parsanol/pools/position_pool_spec.rb +118 -0
- data/spec/parsanol/pools/slice_pool_spec.rb +262 -0
- data/spec/parsanol/position_spec.rb +14 -0
- data/spec/parsanol/result_builder_spec.rb +391 -0
- data/spec/parsanol/rig/rspec_spec.rb +54 -0
- data/spec/parsanol/rope_spec.rb +207 -0
- data/spec/parsanol/scope_spec.rb +45 -0
- data/spec/parsanol/slice_spec.rb +249 -0
- data/spec/parsanol/source/line_cache_spec.rb +74 -0
- data/spec/parsanol/source_spec.rb +207 -0
- data/spec/parsanol/string_view_spec.rb +345 -0
- data/spec/parsanol/transform/context_spec.rb +56 -0
- data/spec/parsanol/transform_spec.rb +183 -0
- data/spec/parsanol/tree_memoization_spec.rb +149 -0
- data/spec/parslet_compatibility/expressir_edge_cases_spec.rb +153 -0
- data/spec/parslet_compatibility/minimal_reproduction.rb +199 -0
- data/spec/parslet_compatibility_spec.rb +399 -0
- data/spec/parslet_imported/atom_spec.rb +93 -0
- data/spec/parslet_imported/combinator_spec.rb +161 -0
- data/spec/parslet_imported/spec_helper.rb +73 -0
- data/spec/performance/batch_parsing_benchmark.rb +129 -0
- data/spec/performance/complete_optimization_summary.rb +143 -0
- data/spec/performance/grammar_caching_analysis.rb +121 -0
- data/spec/performance/grammar_caching_benchmark.rb +80 -0
- data/spec/performance/native_benchmark_spec.rb +230 -0
- data/spec/performance/phase5_benchmark.rb +144 -0
- data/spec/performance/profiling_benchmark.rb +131 -0
- data/spec/performance/ruby_improvements_benchmark.rb +171 -0
- data/spec/performance_spec.rb +374 -0
- data/spec/spec_helper.rb +79 -0
- data/spec/support/opal.rb +8 -0
- data/spec/support/opal.rb.erb +14 -0
- metadata +485 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# A simple integer calculator to answer the question about how to do
|
|
2
|
+
# left and right associativity in parslet (PEG) once and for all.
|
|
3
|
+
|
|
4
|
+
$:.unshift File.dirname(__FILE__) + "/../lib"
|
|
5
|
+
|
|
6
|
+
require 'rspec'
|
|
7
|
+
require 'parsanol/parslet'
|
|
8
|
+
require 'parsanol/rig/rspec'
|
|
9
|
+
|
|
10
|
+
# This is the parsing stage. It expresses left associativity by compiling
|
|
11
|
+
# list of things that have the same associativity.
|
|
12
|
+
class CalcParser < Parsanol::Parser
|
|
13
|
+
root :addition
|
|
14
|
+
|
|
15
|
+
rule(:addition) {
|
|
16
|
+
multiplication.as(:l) >> (add_op >> multiplication.as(:r)).repeat(1) |
|
|
17
|
+
multiplication
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
rule(:multiplication) {
|
|
21
|
+
integer.as(:l) >> (mult_op >> integer.as(:r)).repeat(1) |
|
|
22
|
+
integer }
|
|
23
|
+
|
|
24
|
+
rule(:integer) { digit.repeat(1).as(:i) >> space? }
|
|
25
|
+
|
|
26
|
+
rule(:mult_op) { match['*/'].as(:o) >> space? }
|
|
27
|
+
rule(:add_op) { match['+-'].as(:o) >> space? }
|
|
28
|
+
|
|
29
|
+
rule(:digit) { match['0-9'] }
|
|
30
|
+
rule(:space?) { match['\s'].repeat }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Classes for the abstract syntax tree.
|
|
34
|
+
Int = Struct.new(:int) {
|
|
35
|
+
def eval; self end
|
|
36
|
+
def op(operation, other)
|
|
37
|
+
left = int
|
|
38
|
+
right = other.int
|
|
39
|
+
|
|
40
|
+
Int.new(
|
|
41
|
+
case operation
|
|
42
|
+
when '+'
|
|
43
|
+
left + right
|
|
44
|
+
when '-'
|
|
45
|
+
left - right
|
|
46
|
+
when '*'
|
|
47
|
+
left * right
|
|
48
|
+
when '/'
|
|
49
|
+
left / right
|
|
50
|
+
end)
|
|
51
|
+
end
|
|
52
|
+
def to_i
|
|
53
|
+
int
|
|
54
|
+
end
|
|
55
|
+
}
|
|
56
|
+
Seq = Struct.new(:sequence) {
|
|
57
|
+
def eval
|
|
58
|
+
sequence.reduce { |accum, operation|
|
|
59
|
+
operation.call(accum) }
|
|
60
|
+
end
|
|
61
|
+
}
|
|
62
|
+
LeftOp = Struct.new(:operation, :right) {
|
|
63
|
+
def call(left)
|
|
64
|
+
left = left.eval
|
|
65
|
+
right = self.right.eval
|
|
66
|
+
|
|
67
|
+
left.op(operation, right)
|
|
68
|
+
end
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
# Transforming intermediary syntax tree into a real AST.
|
|
72
|
+
class CalcTransform < Parsanol::Transform
|
|
73
|
+
rule(i: simple(:i)) { Int.new(Integer(i)) }
|
|
74
|
+
rule(o: simple(:o), r: simple(:i)) { LeftOp.new(o, i) }
|
|
75
|
+
rule(l: simple(:i)) { i }
|
|
76
|
+
rule(sequence(:seq)) { Seq.new(seq) }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# And this calls everything in the right order.
|
|
80
|
+
def calculate(str)
|
|
81
|
+
intermediary_tree = CalcParser.new.parse(str)
|
|
82
|
+
abstract_tree = CalcTransform.new.apply(intermediary_tree)
|
|
83
|
+
result = abstract_tree.eval
|
|
84
|
+
|
|
85
|
+
result.to_i
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# A test suite for the above parser
|
|
89
|
+
describe CalcParser do
|
|
90
|
+
let(:p) { described_class.new }
|
|
91
|
+
describe '#integer' do
|
|
92
|
+
let(:i) { p.integer }
|
|
93
|
+
it "parses integers" do
|
|
94
|
+
i.should parse('1')
|
|
95
|
+
i.should parse('123')
|
|
96
|
+
end
|
|
97
|
+
it "consumes trailing white space" do
|
|
98
|
+
i.should parse('123 ')
|
|
99
|
+
end
|
|
100
|
+
it "doesn't parse floats" do
|
|
101
|
+
i.should_not parse('1.3')
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
describe '#multiplication' do
|
|
105
|
+
let(:m) { p.multiplication }
|
|
106
|
+
it "parses simple multiplication" do
|
|
107
|
+
m.should parse('1*2')
|
|
108
|
+
end
|
|
109
|
+
it "parses division" do
|
|
110
|
+
m.should parse('1/2')
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
describe '#addition' do
|
|
114
|
+
let(:a) { p.addition }
|
|
115
|
+
|
|
116
|
+
it "parses simple addition" do
|
|
117
|
+
a.should parse('1+2')
|
|
118
|
+
a.should parse('1+2+3-4')
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
describe CalcTransform do
|
|
123
|
+
def t(obj)
|
|
124
|
+
described_class.new.apply(obj)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
it "transforms integers" do
|
|
128
|
+
t(i: '1').should == Int.new(1)
|
|
129
|
+
end
|
|
130
|
+
it "unwraps left operand" do
|
|
131
|
+
t(l: :obj).should == :obj
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
describe 'whole computation specs' do
|
|
135
|
+
def self.result_of(str, int)
|
|
136
|
+
it(str) { calculate(str).should == int }
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
result_of '1+1', 2
|
|
140
|
+
result_of '1-1-1', -1
|
|
141
|
+
result_of '1+1+3*5/2', 9
|
|
142
|
+
result_of '123*2', 246
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
# Enable these if you want to change the code.
|
|
147
|
+
# RSpec::Core::Runner.run([], $stderr, $stdout)
|
|
148
|
+
|
|
149
|
+
str = ARGV.join
|
|
150
|
+
str = '123*2' if str.match(/^\s*$/)
|
|
151
|
+
|
|
152
|
+
print "#{str} (command line): -> "
|
|
153
|
+
puts calculate(str)
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
# Calculator Example - ZeroCopy: Mirrored Objects (Direct FFI)
|
|
2
|
+
#
|
|
3
|
+
# This example demonstrates ZeroCopy where:
|
|
4
|
+
# 1. Rust parser (parsanol-rs) does the parsing
|
|
5
|
+
# 2. Rust transform converts to typed structs
|
|
6
|
+
# 3. Direct Ruby object construction via FFI (no serialization!)
|
|
7
|
+
# 4. Maximum performance with zero-copy
|
|
8
|
+
#
|
|
9
|
+
# This option provides the best performance but requires type definitions
|
|
10
|
+
# in both Rust and Ruby.
|
|
11
|
+
|
|
12
|
+
$:.unshift File.dirname(__FILE__) + "/../lib"
|
|
13
|
+
|
|
14
|
+
require 'parsanol'
|
|
15
|
+
|
|
16
|
+
# NOTE: This example requires:
|
|
17
|
+
# 1. Native extension support for parse_to_objects
|
|
18
|
+
# 2. #[derive(RubyObject)] proc macro in Rust
|
|
19
|
+
# 3. Matching Ruby class definitions
|
|
20
|
+
#
|
|
21
|
+
# This serves as an API preview.
|
|
22
|
+
|
|
23
|
+
# Step 1: Define Ruby classes that mirror Rust struct definitions
|
|
24
|
+
# These classes MUST match the Rust definitions exactly
|
|
25
|
+
module Calculator
|
|
26
|
+
class Expr
|
|
27
|
+
def eval
|
|
28
|
+
raise NotImplementedError
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
class Number < Expr
|
|
33
|
+
attr_reader :value
|
|
34
|
+
|
|
35
|
+
# This constructor is called directly from Rust FFI
|
|
36
|
+
def initialize(value)
|
|
37
|
+
@value = value
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def eval = @value
|
|
41
|
+
|
|
42
|
+
def to_s = @value.to_s
|
|
43
|
+
|
|
44
|
+
def ==(other)
|
|
45
|
+
other.is_a?(Number) && @value == other.value
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
class BinOp < Expr
|
|
50
|
+
attr_reader :left, :op, :right
|
|
51
|
+
|
|
52
|
+
# This constructor is called directly from Rust FFI
|
|
53
|
+
# Rust sets instance variables directly via rb_ivar_set
|
|
54
|
+
def initialize(left: nil, op: nil, right: nil)
|
|
55
|
+
@left = left
|
|
56
|
+
@op = op
|
|
57
|
+
@right = right
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def eval
|
|
61
|
+
left_val = @left.eval
|
|
62
|
+
right_val = @right.eval
|
|
63
|
+
|
|
64
|
+
case @op
|
|
65
|
+
when '+' then left_val + right_val
|
|
66
|
+
when '-' then left_val - right_val
|
|
67
|
+
when '*' then left_val * right_val
|
|
68
|
+
when '/' then left_val / right_val
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def to_s
|
|
73
|
+
"(#{@left} #{@op} #{@right})"
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Step 2: Define the parser with output type mapping
|
|
79
|
+
class CalculatorParser < Parsanol::Parser
|
|
80
|
+
# Include ZeroCopy module for direct FFI object construction
|
|
81
|
+
# include Parsanol::ZeroCopy
|
|
82
|
+
|
|
83
|
+
root :expression
|
|
84
|
+
|
|
85
|
+
rule(:expression) {
|
|
86
|
+
(term.as(:left) >> add_op.as(:op) >> expression.as(:right)).as(:binop) |
|
|
87
|
+
term
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
rule(:term) {
|
|
91
|
+
(factor.as(:left) >> mult_op.as(:op) >> term.as(:right)).as(:binop) |
|
|
92
|
+
factor
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
rule(:factor) {
|
|
96
|
+
lparen >> expression >> rparen |
|
|
97
|
+
number
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
rule(:number) {
|
|
101
|
+
(match('[0-9]').repeat(1)).as(:int) >> space?
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
rule(:add_op) { match('[+-]').as(:op) >> space? }
|
|
105
|
+
rule(:mult_op) { match('[*/]').as(:op) >> space? }
|
|
106
|
+
|
|
107
|
+
rule(:lparen) { str('(') >> space? }
|
|
108
|
+
rule(:rparen) { str(')') >> space? }
|
|
109
|
+
rule(:space?) { match('\s').repeat }
|
|
110
|
+
|
|
111
|
+
# Output type mapping (ZeroCopy feature)
|
|
112
|
+
# This tells Rust which Ruby classes to construct
|
|
113
|
+
# output_types(
|
|
114
|
+
# number: Calculator::Number,
|
|
115
|
+
# binop: Calculator::BinOp
|
|
116
|
+
# )
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Step 3: Parse with direct object construction
|
|
120
|
+
def calculate(input)
|
|
121
|
+
parser = CalculatorParser.new
|
|
122
|
+
|
|
123
|
+
# ZeroCopy: Parse and get direct Ruby objects
|
|
124
|
+
# NOTE: This requires native extension support
|
|
125
|
+
# expr = parser.parse(input)
|
|
126
|
+
# # expr is already a Calculator::Number or Calculator::BinOp!
|
|
127
|
+
# # No transform needed, no JSON serialization!
|
|
128
|
+
|
|
129
|
+
# For demonstration, simulate what ZeroCopy would return
|
|
130
|
+
# Real implementation would call:
|
|
131
|
+
# Native.parse_to_objects(grammar_json, input, output_types)
|
|
132
|
+
|
|
133
|
+
# Simulate direct object construction
|
|
134
|
+
expr = simulate_parse(input)
|
|
135
|
+
puts "AST: #{expr.class} -> #{expr.to_s}"
|
|
136
|
+
|
|
137
|
+
result = expr.eval
|
|
138
|
+
puts "Result: #{result}"
|
|
139
|
+
|
|
140
|
+
result
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Simulated parsing for demonstration
|
|
144
|
+
def simulate_parse(input)
|
|
145
|
+
case input.strip
|
|
146
|
+
when "42"
|
|
147
|
+
Calculator::Number.new(42)
|
|
148
|
+
when "1 + 2"
|
|
149
|
+
Calculator::BinOp.new(
|
|
150
|
+
left: Calculator::Number.new(1),
|
|
151
|
+
op: '+',
|
|
152
|
+
right: Calculator::Number.new(2)
|
|
153
|
+
)
|
|
154
|
+
when "3 * 4"
|
|
155
|
+
Calculator::BinOp.new(
|
|
156
|
+
left: Calculator::Number.new(3),
|
|
157
|
+
op: '*',
|
|
158
|
+
right: Calculator::Number.new(4)
|
|
159
|
+
)
|
|
160
|
+
when "2 + 3 * 4"
|
|
161
|
+
Calculator::BinOp.new(
|
|
162
|
+
left: Calculator::Number.new(2),
|
|
163
|
+
op: '+',
|
|
164
|
+
right: Calculator::BinOp.new(
|
|
165
|
+
left: Calculator::Number.new(3),
|
|
166
|
+
op: '*',
|
|
167
|
+
right: Calculator::Number.new(4)
|
|
168
|
+
)
|
|
169
|
+
)
|
|
170
|
+
when "(2 + 3) * 4"
|
|
171
|
+
Calculator::BinOp.new(
|
|
172
|
+
left: Calculator::BinOp.new(
|
|
173
|
+
left: Calculator::Number.new(2),
|
|
174
|
+
op: '+',
|
|
175
|
+
right: Calculator::Number.new(3)
|
|
176
|
+
),
|
|
177
|
+
op: '*',
|
|
178
|
+
right: Calculator::Number.new(4)
|
|
179
|
+
)
|
|
180
|
+
else
|
|
181
|
+
raise "Not simulated: #{input}"
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Example usage
|
|
186
|
+
if __FILE__ == $0
|
|
187
|
+
puts "=" * 60
|
|
188
|
+
puts "Calculator Example - ZeroCopy: Mirrored Objects"
|
|
189
|
+
puts "=" * 60
|
|
190
|
+
puts
|
|
191
|
+
puts "NOTE: This example shows the planned API for ZeroCopy."
|
|
192
|
+
puts "The native extension support for parse_to_objects is coming soon."
|
|
193
|
+
puts
|
|
194
|
+
|
|
195
|
+
test_cases = [
|
|
196
|
+
["42", 42],
|
|
197
|
+
["1 + 2", 3],
|
|
198
|
+
["3 * 4", 12],
|
|
199
|
+
["2 + 3 * 4", 14],
|
|
200
|
+
["(2 + 3) * 4", 20],
|
|
201
|
+
]
|
|
202
|
+
|
|
203
|
+
test_cases.each do |input, expected|
|
|
204
|
+
puts
|
|
205
|
+
puts "-" * 40
|
|
206
|
+
puts "Input: #{input}"
|
|
207
|
+
begin
|
|
208
|
+
result = calculate(input)
|
|
209
|
+
status = result == expected ? "✓ PASS" : "✗ FAIL"
|
|
210
|
+
puts "Expected: #{expected}, Got: #{result} - #{status}"
|
|
211
|
+
rescue => e
|
|
212
|
+
puts "Error: #{e.message}"
|
|
213
|
+
puts "✗ FAIL"
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
puts
|
|
218
|
+
puts "=" * 60
|
|
219
|
+
puts "ZeroCopy Benefits:"
|
|
220
|
+
puts "- FASTEST: No serialization overhead"
|
|
221
|
+
puts "- Zero-copy: Direct Ruby object construction"
|
|
222
|
+
puts "- Type-safe: Types defined in both Rust and Ruby"
|
|
223
|
+
puts "- Methods defined in Ruby (eval, to_s, etc.)"
|
|
224
|
+
puts
|
|
225
|
+
puts "ZeroCopy Requirements:"
|
|
226
|
+
puts "- Define types in Rust with #[derive(RubyObject)]"
|
|
227
|
+
puts "- Define matching Ruby classes"
|
|
228
|
+
puts "- Native extension compiled with ruby feature"
|
|
229
|
+
puts "=" * 60
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# Rust code that would be needed (for reference):
|
|
233
|
+
#
|
|
234
|
+
# // In parsanol-rs
|
|
235
|
+
# use parsanol_ruby_derive::RubyObject;
|
|
236
|
+
#
|
|
237
|
+
# #[derive(Debug, Clone, RubyObject)]
|
|
238
|
+
# #[ruby_class("Calculator::Expr")]
|
|
239
|
+
# pub enum Expr {
|
|
240
|
+
# #[ruby_variant("number")]
|
|
241
|
+
# Number(i64),
|
|
242
|
+
#
|
|
243
|
+
# #[ruby_variant("binop")]
|
|
244
|
+
# BinOp {
|
|
245
|
+
# left: Box<Expr>,
|
|
246
|
+
# op: String,
|
|
247
|
+
# right: Box<Expr>,
|
|
248
|
+
# },
|
|
249
|
+
# }
|
|
250
|
+
#
|
|
251
|
+
# // The proc macro generates:
|
|
252
|
+
# impl RubyObject for Expr {
|
|
253
|
+
# fn to_ruby(&self, ruby: &Ruby) -> Result<Value, Error> {
|
|
254
|
+
# match self {
|
|
255
|
+
# Expr::Number(n) => {
|
|
256
|
+
# let class = ruby.class("Calculator::Number")?;
|
|
257
|
+
# class.new_instance((*n,))
|
|
258
|
+
# }
|
|
259
|
+
# Expr::BinOp { left, op, right } => {
|
|
260
|
+
# let class = ruby.class("Calculator::BinOp")?;
|
|
261
|
+
# let obj = class.new_instance()?;
|
|
262
|
+
# obj.ivar_set("@left", left.to_ruby(ruby)?)?;
|
|
263
|
+
# obj.ivar_set("@op", op.to_ruby(ruby)?)?;
|
|
264
|
+
# obj.ivar_set("@right", right.to_ruby(ruby)?)?;
|
|
265
|
+
# Ok(obj.as_value())
|
|
266
|
+
# }
|
|
267
|
+
# }
|
|
268
|
+
# }
|
|
269
|
+
# }
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Calculator (Zero-Copy - Option C)
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
This implementation demonstrates direct FFI object construction: Rust parses
|
|
6
|
+
and directly constructs Ruby objects without serialization.
|
|
7
|
+
|
|
8
|
+
## When to Use
|
|
9
|
+
|
|
10
|
+
- Maximum performance required
|
|
11
|
+
- Production systems
|
|
12
|
+
- When zero-copy is critical
|
|
13
|
+
|
|
14
|
+
## Key Concepts
|
|
15
|
+
|
|
16
|
+
1. **Direct FFI**: No serialization overhead
|
|
17
|
+
2. **Ruby Object Construction**: Direct via rb_funcall
|
|
18
|
+
3. **Type Safety**: Mirrored types on both sides
|
|
19
|
+
|
|
20
|
+
## Running
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
ruby example/calculator/zero_copy.rb
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Output
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
Input: 42+8
|
|
30
|
+
Result: Calculator::AddExpr
|
|
31
|
+
Value: 50
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Note
|
|
35
|
+
|
|
36
|
+
This is the fastest option but requires more complex FFI setup.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
|
|
2
|
+
# This example demonstrates how pieces of input can be captured and matched
|
|
3
|
+
# against later on. Without this, you cannot match here-documents and other
|
|
4
|
+
# self-dependent grammars.
|
|
5
|
+
|
|
6
|
+
$:.unshift File.dirname(__FILE__) + "/../lib"
|
|
7
|
+
require 'parsanol/parslet'
|
|
8
|
+
require 'parsanol/convenience'
|
|
9
|
+
require 'pp'
|
|
10
|
+
|
|
11
|
+
class CapturingParser < Parsanol::Parser
|
|
12
|
+
root :document
|
|
13
|
+
|
|
14
|
+
# Introduce a scope for each document. This ensures that documents can be
|
|
15
|
+
# nested.
|
|
16
|
+
rule(:document) { scope { doc_start >> text >> doc_end } }
|
|
17
|
+
|
|
18
|
+
# Start of a document is a heredoc marker. This is captured in :marker
|
|
19
|
+
rule(:doc_start) { str('<') >> marker >> newline }
|
|
20
|
+
rule(:marker) { match['A-Z'].repeat(1).capture(:marker) }
|
|
21
|
+
|
|
22
|
+
# The content of a document can be either lines of text or another
|
|
23
|
+
# document, introduced by <HERE, where HERE is the doc marker.
|
|
24
|
+
rule(:text) { (document.as(:doc) | text_line.as(:line)).repeat(1) }
|
|
25
|
+
rule(:text_line) { captured_marker.absent? >> any >>
|
|
26
|
+
(newline.absent? >> any).repeat >> newline }
|
|
27
|
+
|
|
28
|
+
# The end of the document is marked by the marker that was at the beginning
|
|
29
|
+
# of the document, by itself on a line.
|
|
30
|
+
rule(:doc_end) { captured_marker }
|
|
31
|
+
rule(:captured_marker) {
|
|
32
|
+
dynamic { |source, context|
|
|
33
|
+
str(context.captures[:marker])
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
rule(:newline) { match["\n"] }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
parser = CapturingParser.new
|
|
41
|
+
pp parser.parse_with_debug %Q(<CAPTURE
|
|
42
|
+
Text1
|
|
43
|
+
<FOOBAR
|
|
44
|
+
Text3
|
|
45
|
+
Text4
|
|
46
|
+
FOOBAR
|
|
47
|
+
Text2
|
|
48
|
+
CAPTURE)
|
|
49
|
+
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Capture Patterns - Ruby Implementation
|
|
2
|
+
|
|
3
|
+
## How to Run
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
cd parsanol-ruby/example/capture
|
|
7
|
+
ruby basic.rb
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Code Walkthrough
|
|
11
|
+
|
|
12
|
+
### Capture Mechanism
|
|
13
|
+
|
|
14
|
+
The `capture(:name)` method captures matched text for later reference:
|
|
15
|
+
|
|
16
|
+
```ruby
|
|
17
|
+
rule(:marker) { match['A-Z'].repeat(1).capture(:marker) }
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
This captures the heredoc marker (like `CAPTURE` or `FOOBAR`) in the context.
|
|
21
|
+
|
|
22
|
+
### Dynamic Matching
|
|
23
|
+
|
|
24
|
+
The `dynamic` block accesses captured values:
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
rule(:captured_marker) {
|
|
28
|
+
dynamic { |source, context|
|
|
29
|
+
str(context.captures[:marker])
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`context.captures[:marker]` returns the previously captured string.
|
|
35
|
+
|
|
36
|
+
### Scope Isolation
|
|
37
|
+
|
|
38
|
+
The `scope` block creates isolated capture contexts:
|
|
39
|
+
|
|
40
|
+
```ruby
|
|
41
|
+
rule(:document) { scope { doc_start >> text >> doc_end } }
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Each document has its own capture namespace, allowing nested heredocs.
|
|
45
|
+
|
|
46
|
+
### Document Structure
|
|
47
|
+
|
|
48
|
+
Heredoc-style documents with nested content:
|
|
49
|
+
|
|
50
|
+
```ruby
|
|
51
|
+
rule(:text) {
|
|
52
|
+
(document.as(:doc) | text_line.as(:line)).repeat(1)
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Documents can contain other documents (nested heredocs).
|
|
57
|
+
|
|
58
|
+
### End Marker Matching
|
|
59
|
+
|
|
60
|
+
The end marker must match the start marker:
|
|
61
|
+
|
|
62
|
+
```ruby
|
|
63
|
+
rule(:doc_end) { captured_marker }
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
This ensures `<FOOBAR` ... `FOOBAR` pairs are correctly matched.
|
|
67
|
+
|
|
68
|
+
## Output Types
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
# Input:
|
|
72
|
+
# <CAPTURE
|
|
73
|
+
# Text1
|
|
74
|
+
# <FOOBAR
|
|
75
|
+
# Text3
|
|
76
|
+
# FOOBAR
|
|
77
|
+
# Text2
|
|
78
|
+
# CAPTURE
|
|
79
|
+
|
|
80
|
+
# Parse tree (simplified):
|
|
81
|
+
{:doc=>[
|
|
82
|
+
{:line=>"Text1\n"},
|
|
83
|
+
{:doc=>[
|
|
84
|
+
{:line=>"Text3\n"}
|
|
85
|
+
]},
|
|
86
|
+
{:line=>"Text2\n"}
|
|
87
|
+
]}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Design Decisions
|
|
91
|
+
|
|
92
|
+
### Why Capture Instead of Backreference?
|
|
93
|
+
|
|
94
|
+
Parslet's capture is more powerful than regex backreferences. It integrates with the parsing context and supports nesting.
|
|
95
|
+
|
|
96
|
+
### Why Scope Blocks?
|
|
97
|
+
|
|
98
|
+
Without scopes, nested documents would share the same capture namespace. Scopes isolate each document's captures.
|
|
99
|
+
|
|
100
|
+
### Why Dynamic Blocks?
|
|
101
|
+
|
|
102
|
+
Dynamic blocks provide access to the parsing context at parse time. This enables context-dependent matching.
|
|
103
|
+
|
|
104
|
+
### Ruby-Only Feature
|
|
105
|
+
|
|
106
|
+
This feature uses Parslet's `capture`, `dynamic`, and `scope` constructs. These have no direct equivalent in Rust and are specific to Parslet's Ruby implementation.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "capture",
|
|
3
|
+
"title": "Capture DSL",
|
|
4
|
+
"description": "Demonstrate Parslet's capture DSL for binding parse results to variables within the grammar.",
|
|
5
|
+
"category": "conceptual",
|
|
6
|
+
"tags": ["capture", "binding", "parslet", "variable"],
|
|
7
|
+
"difficulty": "intermediate",
|
|
8
|
+
"concepts": ["capture", "variable binding", "dynamic values", "cross-referencing"],
|
|
9
|
+
|
|
10
|
+
"motivation": {
|
|
11
|
+
"why": "Capture allows binding intermediate parse results to variables for later use in the grammar. This enables cross-referencing and building complex structures from parts.",
|
|
12
|
+
"useCases": [
|
|
13
|
+
"Cross-referencing within a grammar",
|
|
14
|
+
"Building complex structures from parts",
|
|
15
|
+
"Referencing earlier parse results in later rules"
|
|
16
|
+
]
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
"inputFormat": {
|
|
20
|
+
"description": "Simple expressions that can be captured and referenced.",
|
|
21
|
+
"examples": [
|
|
22
|
+
{ "input": "a", "description": "Simple character to capture", "valid": true },
|
|
23
|
+
{ "input": "abc", "description": "Multiple characters", "valid": true }
|
|
24
|
+
]
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
"outputFormat": {
|
|
28
|
+
"description": "Captured values bound to variables.",
|
|
29
|
+
"structure": {
|
|
30
|
+
"captured_value": { "description": "The value captured by the capture DSL" }
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
"rubyOnly": true,
|
|
35
|
+
"parsletCompatible": true,
|
|
36
|
+
"implementations": {
|
|
37
|
+
"ruby": { "basic": "basic.rb" }
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# A small example on how to parse common types of comments. The example
|
|
2
|
+
# started out with parser code from Stephen Waits.
|
|
3
|
+
|
|
4
|
+
$:.unshift File.dirname(__FILE__) + "/../lib"
|
|
5
|
+
|
|
6
|
+
require 'pp'
|
|
7
|
+
require 'parsanol/parslet'
|
|
8
|
+
require 'parsanol/convenience'
|
|
9
|
+
|
|
10
|
+
class ALanguage < Parsanol::Parser
|
|
11
|
+
root(:lines)
|
|
12
|
+
|
|
13
|
+
rule(:lines) { line.repeat }
|
|
14
|
+
rule(:line) { spaces >> expression.repeat >> newline }
|
|
15
|
+
rule(:newline) { str("\n") >> str("\r").maybe }
|
|
16
|
+
|
|
17
|
+
rule(:expression) { (str('a').as(:a) >> spaces).as(:exp) }
|
|
18
|
+
|
|
19
|
+
rule(:spaces) { space.repeat }
|
|
20
|
+
rule(:space) { multiline_comment | line_comment | str(' ') }
|
|
21
|
+
|
|
22
|
+
rule(:line_comment) { (str('//') >> (newline.absent? >> any).repeat).as(:line) }
|
|
23
|
+
rule(:multiline_comment) { (str('/*') >> (str('*/').absent? >> any).repeat >> str('*/')).as(:multi) }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
code = %q(
|
|
27
|
+
a
|
|
28
|
+
// line comment
|
|
29
|
+
a a a // line comment
|
|
30
|
+
a /* inline comment */ a
|
|
31
|
+
/* multiline
|
|
32
|
+
comment */
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
pp ALanguage.new.parse_with_debug(code)
|