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,156 @@
|
|
|
1
|
+
# Calculator Example - Ruby Transform: Ruby Transform (Parslet-Compatible)
|
|
2
|
+
#
|
|
3
|
+
# This example demonstrates Ruby Transform where:
|
|
4
|
+
# 1. Rust parser (parsanol-rs) does the fast parsing
|
|
5
|
+
# 2. Returns a generic tree (hash/array/string structure)
|
|
6
|
+
# 3. Ruby transform converts tree to Ruby objects
|
|
7
|
+
#
|
|
8
|
+
# This is the most flexible option and is 100% Parslet API compatible.
|
|
9
|
+
|
|
10
|
+
$:.unshift File.dirname(__FILE__) + "/../lib"
|
|
11
|
+
|
|
12
|
+
require 'parsanol'
|
|
13
|
+
|
|
14
|
+
# Step 1: Define the parser grammar
|
|
15
|
+
class CalculatorParser < Parsanol::Parser
|
|
16
|
+
root :expression
|
|
17
|
+
|
|
18
|
+
rule(:expression) {
|
|
19
|
+
(term.as(:left) >> add_op.as(:op) >> expression.as(:right)).as(:binop) |
|
|
20
|
+
term
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
rule(:term) {
|
|
24
|
+
(factor.as(:left) >> mult_op.as(:op) >> term.as(:right)).as(:binop) |
|
|
25
|
+
factor
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
rule(:factor) {
|
|
29
|
+
lparen >> expression >> rparen |
|
|
30
|
+
number
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
rule(:number) {
|
|
34
|
+
(match('[0-9]').repeat(1)).as(:int) >> space?
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
rule(:add_op) { match('[+-]').as(:op) >> space? }
|
|
38
|
+
rule(:mult_op) { match('[*/]').as(:op) >> space? }
|
|
39
|
+
|
|
40
|
+
rule(:lparen) { str('(') >> space? }
|
|
41
|
+
rule(:rparen) { str(')') >> space? }
|
|
42
|
+
rule(:space?) { match('\s').repeat }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Step 2: Define the AST classes
|
|
46
|
+
class IntExpr
|
|
47
|
+
attr_reader :value
|
|
48
|
+
|
|
49
|
+
def initialize(value)
|
|
50
|
+
@value = value
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def eval = @value
|
|
54
|
+
|
|
55
|
+
def to_s = @value.to_s
|
|
56
|
+
|
|
57
|
+
def ==(other)
|
|
58
|
+
other.is_a?(IntExpr) && @value == other.value
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
class BinOpExpr
|
|
63
|
+
attr_reader :left, :op, :right
|
|
64
|
+
|
|
65
|
+
def initialize(left, op, right)
|
|
66
|
+
@left = left
|
|
67
|
+
@op = op
|
|
68
|
+
@right = right
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def eval
|
|
72
|
+
left_val = @left.eval
|
|
73
|
+
right_val = @right.eval
|
|
74
|
+
|
|
75
|
+
case @op
|
|
76
|
+
when '+' then left_val + right_val
|
|
77
|
+
when '-' then left_val - right_val
|
|
78
|
+
when '*' then left_val * right_val
|
|
79
|
+
when '/' then left_val / right_val
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def to_s
|
|
84
|
+
"(#{@left} #{@op} #{@right})"
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Step 3: Define the transform (Parslet-style)
|
|
89
|
+
class CalculatorTransform < Parsanol::Transform
|
|
90
|
+
# Transform integer captures
|
|
91
|
+
rule(int: simple(:n)) { IntExpr.new(Integer(n)) }
|
|
92
|
+
|
|
93
|
+
# Transform binary operations
|
|
94
|
+
# NOTE: The grammar wraps op with as(:op), so we get { op: { op: "+" } }
|
|
95
|
+
# The outer :op is from add_op.as(:op), inner :op is from match('[+-]').as(:op)
|
|
96
|
+
rule(left: simple(:l), op: { op: simple(:o) }, right: simple(:r)) {
|
|
97
|
+
BinOpExpr.new(l, o, r)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
# Handle binop wrapper
|
|
101
|
+
rule(binop: simple(:b)) { b }
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Step 4: Parse and transform
|
|
105
|
+
def calculate(input)
|
|
106
|
+
parser = CalculatorParser.new
|
|
107
|
+
transform = CalculatorTransform.new
|
|
108
|
+
|
|
109
|
+
# Ruby Transform: Parse in Rust, transform in Ruby
|
|
110
|
+
tree = parser.parse(input)
|
|
111
|
+
puts "Parse tree: #{tree.inspect}"
|
|
112
|
+
|
|
113
|
+
ast = transform.apply(tree)
|
|
114
|
+
puts "AST: #{ast.to_s}"
|
|
115
|
+
|
|
116
|
+
result = ast.eval
|
|
117
|
+
puts "Result: #{result}"
|
|
118
|
+
|
|
119
|
+
result
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Example usage
|
|
123
|
+
if __FILE__ == $0
|
|
124
|
+
test_cases = [
|
|
125
|
+
["42", 42],
|
|
126
|
+
["1 + 2", 3],
|
|
127
|
+
["3 * 4", 12],
|
|
128
|
+
["2 + 3 * 4", 14],
|
|
129
|
+
["(2 + 3) * 4", 20],
|
|
130
|
+
["10 - 3 - 2", 5], # Left associative: (10 - 3) - 2
|
|
131
|
+
["100 / 5 / 2", 10], # Left associative: (100 / 5) / 2
|
|
132
|
+
]
|
|
133
|
+
|
|
134
|
+
puts "=" * 60
|
|
135
|
+
puts "Calculator Example - Ruby Transform: Ruby Transform"
|
|
136
|
+
puts "=" * 60
|
|
137
|
+
|
|
138
|
+
test_cases.each do |input, expected|
|
|
139
|
+
puts
|
|
140
|
+
puts "-" * 40
|
|
141
|
+
puts "Input: #{input}"
|
|
142
|
+
begin
|
|
143
|
+
result = calculate(input)
|
|
144
|
+
status = result == expected ? "✓ PASS" : "✗ FAIL"
|
|
145
|
+
puts "Expected: #{expected}, Got: #{result} - #{status}"
|
|
146
|
+
rescue => e
|
|
147
|
+
puts "Error: #{e.message}"
|
|
148
|
+
puts "✗ FAIL"
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Performance comparison note:
|
|
154
|
+
# - Ruby Transform is slower than Options B and C+ because transform happens in Ruby
|
|
155
|
+
# - But it's still faster than pure Ruby Parslet because parsing is in Rust
|
|
156
|
+
# - Use Ruby Transform for maximum flexibility and debugging
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Calculator (Ruby Transform - Option A)
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
This implementation demonstrates Parslet-compatible parsing: Rust parses the
|
|
6
|
+
input, Ruby transforms the result into domain objects.
|
|
7
|
+
|
|
8
|
+
## When to Use
|
|
9
|
+
|
|
10
|
+
- Migrating from Parslet
|
|
11
|
+
- Maximum flexibility in transformation
|
|
12
|
+
- When domain logic should stay in Ruby
|
|
13
|
+
|
|
14
|
+
## Key Concepts
|
|
15
|
+
|
|
16
|
+
1. **Rust Parsing**: Fast native parsing engine
|
|
17
|
+
2. **Ruby Transform**: Familiar Parslet::Transform API
|
|
18
|
+
3. **Flexible Output**: Any Ruby object structure
|
|
19
|
+
|
|
20
|
+
## Running
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
ruby example/calculator/ruby_transform.rb
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Output
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
Input: 42+8
|
|
30
|
+
Parse tree: {int: "42", op: "+", rhs: {int: "8"}}
|
|
31
|
+
Result: 50
|
|
32
|
+
```
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# Calculator Example - Serialized: JSON Serialization
|
|
2
|
+
#
|
|
3
|
+
# This example demonstrates Serialized where:
|
|
4
|
+
# 1. Rust parser (parsanol-rs) does the parsing
|
|
5
|
+
# 2. Result is serialized to JSON
|
|
6
|
+
# 3. Ruby deserializes JSON to Ruby objects
|
|
7
|
+
#
|
|
8
|
+
# This option provides cross-language compatibility and structured output.
|
|
9
|
+
|
|
10
|
+
$:.unshift File.dirname(__FILE__) + "/../lib"
|
|
11
|
+
|
|
12
|
+
require 'parsanol'
|
|
13
|
+
require 'json'
|
|
14
|
+
|
|
15
|
+
# Check native extension availability
|
|
16
|
+
unless Parsanol::Native.available?
|
|
17
|
+
puts "=" * 60
|
|
18
|
+
puts "Calculator Example - Serialized: JSON Serialization"
|
|
19
|
+
puts "=" * 60
|
|
20
|
+
puts
|
|
21
|
+
puts "ERROR: Native extension not available!"
|
|
22
|
+
puts "Please run: rake compile"
|
|
23
|
+
puts "=" * 60
|
|
24
|
+
exit 1
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
puts "=" * 60
|
|
28
|
+
puts "Calculator Example - Serialized: JSON Serialization"
|
|
29
|
+
puts "=" * 60
|
|
30
|
+
puts
|
|
31
|
+
puts "✓ Native extension loaded successfully!"
|
|
32
|
+
puts
|
|
33
|
+
|
|
34
|
+
# Step 1: Define the parser grammar (same as RubyTransform)
|
|
35
|
+
class CalculatorParser < Parsanol::Parser
|
|
36
|
+
root :expression
|
|
37
|
+
|
|
38
|
+
rule(:expression) {
|
|
39
|
+
(term.as(:left) >> add_op.as(:op) >> expression.as(:right)).as(:binop) |
|
|
40
|
+
term
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
rule(:term) {
|
|
44
|
+
(factor.as(:left) >> mult_op.as(:op) >> term.as(:right)).as(:binop) |
|
|
45
|
+
factor
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
rule(:factor) {
|
|
49
|
+
lparen >> expression >> rparen |
|
|
50
|
+
number
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
rule(:number) {
|
|
54
|
+
(match('[0-9]').repeat(1)).as(:int) >> space?
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
rule(:add_op) { match('[+-]').as(:op) >> space? }
|
|
58
|
+
rule(:mult_op) { match('[*/]').as(:op) >> space? }
|
|
59
|
+
|
|
60
|
+
rule(:lparen) { str('(') >> space? }
|
|
61
|
+
rule(:rparen) { str(')') >> space? }
|
|
62
|
+
rule(:space?) { match('\s').repeat }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Step 2: Define AST classes
|
|
66
|
+
class Expr
|
|
67
|
+
def eval
|
|
68
|
+
raise NotImplementedError
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
class NumberExpr < Expr
|
|
73
|
+
attr_reader :value
|
|
74
|
+
|
|
75
|
+
def initialize(value)
|
|
76
|
+
@value = value
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def eval = @value
|
|
80
|
+
def to_s = @value.to_s
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
class BinOpExpr < Expr
|
|
84
|
+
attr_reader :left, :op, :right
|
|
85
|
+
|
|
86
|
+
def initialize(left, op, right)
|
|
87
|
+
@left = left
|
|
88
|
+
@op = op
|
|
89
|
+
@right = right
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def eval
|
|
93
|
+
left_val = @left.eval
|
|
94
|
+
right_val = @right.eval
|
|
95
|
+
|
|
96
|
+
case @op
|
|
97
|
+
when '+' then left_val + right_val
|
|
98
|
+
when '-' then left_val - right_val
|
|
99
|
+
when '*' then left_val * right_val
|
|
100
|
+
when '/' then left_val / right_val
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def to_s
|
|
105
|
+
"(#{@left} #{@op} #{@right})"
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Step 3: Native JSON parser
|
|
110
|
+
def parse_json_to_expr(data)
|
|
111
|
+
case data
|
|
112
|
+
when Hash
|
|
113
|
+
if data.key?('int')
|
|
114
|
+
int_val = data['int']
|
|
115
|
+
int_val = int_val.first if int_val.is_a?(Array)
|
|
116
|
+
NumberExpr.new(Integer(int_val))
|
|
117
|
+
elsif data.key?('binop')
|
|
118
|
+
binop = data['binop']
|
|
119
|
+
|
|
120
|
+
# Handle both array and hash formats
|
|
121
|
+
if binop.is_a?(Array)
|
|
122
|
+
# Array format: [{"left": ...}, {"op": ...}, {"right": ...}]
|
|
123
|
+
left_data = binop.find { |e| e.is_a?(Hash) && e.key?('left') }&.dig('left')
|
|
124
|
+
op_data = binop.find { |e| e.is_a?(Hash) && e.key?('op') }&.dig('op')
|
|
125
|
+
right_data = binop.find { |e| e.is_a?(Hash) && e.key?('right') }&.dig('right')
|
|
126
|
+
else
|
|
127
|
+
# Hash format
|
|
128
|
+
left_data = binop['left']
|
|
129
|
+
op_data = binop['op']
|
|
130
|
+
right_data = binop['right']
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
op = extract_op(op_data)
|
|
134
|
+
left = parse_json_to_expr(left_data)
|
|
135
|
+
right = parse_json_to_expr(right_data)
|
|
136
|
+
BinOpExpr.new(left, op, right)
|
|
137
|
+
else
|
|
138
|
+
# Try to find the first value that's parseable
|
|
139
|
+
data.each_value do |v|
|
|
140
|
+
result = parse_json_to_expr(v)
|
|
141
|
+
return result if result
|
|
142
|
+
end
|
|
143
|
+
nil
|
|
144
|
+
end
|
|
145
|
+
when Array
|
|
146
|
+
# Arrays often contain [value, whitespace] - extract the value
|
|
147
|
+
# Or they could be [elem1, elem2, elem3] format
|
|
148
|
+
result = nil
|
|
149
|
+
data.each do |elem|
|
|
150
|
+
parsed = parse_json_to_expr(elem)
|
|
151
|
+
result = parsed if parsed
|
|
152
|
+
end
|
|
153
|
+
result
|
|
154
|
+
when String
|
|
155
|
+
# Could be a number string
|
|
156
|
+
Integer(data) rescue nil
|
|
157
|
+
else
|
|
158
|
+
nil
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def extract_op(data)
|
|
163
|
+
case data
|
|
164
|
+
when Hash
|
|
165
|
+
if data.key?('op')
|
|
166
|
+
extract_op(data['op'])
|
|
167
|
+
else
|
|
168
|
+
extract_op(data.values.first)
|
|
169
|
+
end
|
|
170
|
+
when Array
|
|
171
|
+
# Find the op value in the array
|
|
172
|
+
data.each do |elem|
|
|
173
|
+
result = extract_op(elem)
|
|
174
|
+
return result if result.is_a?(String) && result.match?(/^[+\-*\/]$/)
|
|
175
|
+
end
|
|
176
|
+
nil
|
|
177
|
+
when String
|
|
178
|
+
data
|
|
179
|
+
else
|
|
180
|
+
data.to_s
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Step 4: Parse using native extension
|
|
185
|
+
def calculate(input)
|
|
186
|
+
parser = CalculatorParser.new
|
|
187
|
+
|
|
188
|
+
# Serialized: Parse using native extension and get JSON
|
|
189
|
+
grammar_json = Parsanol::Native.serialize_grammar(parser.root)
|
|
190
|
+
json_string = Parsanol::Native.parse_to_json(grammar_json, input)
|
|
191
|
+
|
|
192
|
+
puts "Native JSON: #{json_string}"
|
|
193
|
+
|
|
194
|
+
# Deserialize to Ruby objects
|
|
195
|
+
data = JSON.parse(json_string)
|
|
196
|
+
expr = parse_json_to_expr(data)
|
|
197
|
+
|
|
198
|
+
if expr
|
|
199
|
+
puts "AST: #{expr.to_s}"
|
|
200
|
+
result = expr.eval
|
|
201
|
+
puts "Result: #{result}"
|
|
202
|
+
result
|
|
203
|
+
else
|
|
204
|
+
# Fall back to pure Ruby parsing
|
|
205
|
+
tree = parser.parse(input)
|
|
206
|
+
transform = CalculatorTransform.new
|
|
207
|
+
ast = transform.apply(tree)
|
|
208
|
+
puts "AST (fallback): #{ast.to_s}"
|
|
209
|
+
result = ast.eval
|
|
210
|
+
puts "Result: #{result}"
|
|
211
|
+
result
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Transform class for fallback
|
|
216
|
+
class CalculatorTransform < Parsanol::Transform
|
|
217
|
+
rule(int: simple(:n)) { NumberExpr.new(Integer(n)) }
|
|
218
|
+
rule(left: simple(:l), op: { op: simple(:o) }, right: simple(:r)) {
|
|
219
|
+
BinOpExpr.new(l, o, r)
|
|
220
|
+
}
|
|
221
|
+
rule(binop: simple(:b)) { b }
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Example usage
|
|
225
|
+
if __FILE__ == $0
|
|
226
|
+
test_cases = [
|
|
227
|
+
["42", 42],
|
|
228
|
+
["1 + 2", 3],
|
|
229
|
+
["3 * 4", 12],
|
|
230
|
+
["2 + 3 * 4", 14],
|
|
231
|
+
["(2 + 3) * 4", 20],
|
|
232
|
+
]
|
|
233
|
+
|
|
234
|
+
test_cases.each do |input, expected|
|
|
235
|
+
puts
|
|
236
|
+
puts "-" * 40
|
|
237
|
+
puts "Input: #{input}"
|
|
238
|
+
begin
|
|
239
|
+
result = calculate(input)
|
|
240
|
+
status = result == expected ? "✓ PASS" : "✗ FAIL"
|
|
241
|
+
puts "Expected: #{expected}, Got: #{result} - #{status}"
|
|
242
|
+
rescue => e
|
|
243
|
+
puts "Error: #{e.message}"
|
|
244
|
+
puts e.backtrace.first(3).join("\n")
|
|
245
|
+
puts "✗ FAIL"
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
puts
|
|
250
|
+
puts "=" * 60
|
|
251
|
+
puts "Serialized Benefits:"
|
|
252
|
+
puts "- Cross-language: Same JSON works for Python, JavaScript, etc."
|
|
253
|
+
puts "- Native performance: All parsing done in Rust"
|
|
254
|
+
puts "- Structured output with type information"
|
|
255
|
+
puts "- Easy to cache/store results"
|
|
256
|
+
puts "=" * 60
|
|
257
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Calculator (Serialized - Option B)
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
This implementation demonstrates full Rust processing with JSON output:
|
|
6
|
+
Rust parses AND transforms, returning serialized JSON.
|
|
7
|
+
|
|
8
|
+
## When to Use
|
|
9
|
+
|
|
10
|
+
- Cross-language compatibility
|
|
11
|
+
- Structured output required
|
|
12
|
+
- Performance-critical applications
|
|
13
|
+
|
|
14
|
+
## Key Concepts
|
|
15
|
+
|
|
16
|
+
1. **Rust Parsing + Transform**: All processing in Rust
|
|
17
|
+
2. **JSON Serialization**: Language-agnostic output
|
|
18
|
+
3. **Type Safety**: Schema-driven structure
|
|
19
|
+
|
|
20
|
+
## Running
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
ruby example/calculator/serialized.rb
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Output
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
Input: 42+8
|
|
30
|
+
JSON: {"type":"AddExpr","left":{"type":"Number","value":42},"op":"+","right":{"type":"Number","value":8}}
|
|
31
|
+
Result: 50
|
|
32
|
+
```
|