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,132 @@
|
|
|
1
|
+
$:.unshift File.dirname(__FILE__) + "/../lib"
|
|
2
|
+
|
|
3
|
+
require 'parsanol/parslet'
|
|
4
|
+
require 'parsanol/convenience'
|
|
5
|
+
|
|
6
|
+
# This example demonstrates tree error reporting in a real life example.
|
|
7
|
+
# Originally contributed to Parslet, ported to Parsanol as an example.
|
|
8
|
+
|
|
9
|
+
def prettify(str)
|
|
10
|
+
puts " "*3 + " "*4 + "." + " "*4 + "10" + " "*3 + "." + " "*4 + "20"
|
|
11
|
+
str.lines.each_with_index do |line, index|
|
|
12
|
+
printf "%02d %s\n",
|
|
13
|
+
index+1,
|
|
14
|
+
line.chomp
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class Parser < Parsanol::Parser
|
|
19
|
+
|
|
20
|
+
# commons
|
|
21
|
+
|
|
22
|
+
rule(:space) { match('[ \t]').repeat(1) }
|
|
23
|
+
rule(:space?) { space.maybe }
|
|
24
|
+
|
|
25
|
+
rule(:newline) { match('[\r\n]') }
|
|
26
|
+
|
|
27
|
+
rule(:comment) { str('#') >> match('[^\r\n]').repeat }
|
|
28
|
+
|
|
29
|
+
rule(:line_separator) {
|
|
30
|
+
(space? >> ((comment.maybe >> newline) | str(';')) >> space?).repeat(1)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
rule(:blank) { line_separator | space }
|
|
34
|
+
rule(:blank?) { blank.maybe }
|
|
35
|
+
|
|
36
|
+
rule(:identifier) { match('[a-zA-Z0-9_]').repeat(1) }
|
|
37
|
+
|
|
38
|
+
# res_statement
|
|
39
|
+
|
|
40
|
+
rule(:reference) {
|
|
41
|
+
(str('@').repeat(1,2) >> identifier).as(:reference)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
rule(:res_action_or_link) {
|
|
45
|
+
str('.').as(:dot) >> (identifier >> str('?').maybe ).as(:name) >> str('()')
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
rule(:res_actions) {
|
|
49
|
+
(
|
|
50
|
+
reference
|
|
51
|
+
).as(:resources) >>
|
|
52
|
+
(
|
|
53
|
+
res_action_or_link.as(:res_action)
|
|
54
|
+
).repeat(0).as(:res_actions)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
rule(:res_statement) {
|
|
58
|
+
res_actions >>
|
|
59
|
+
(str(':') >> identifier.as(:name)).maybe.as(:res_field)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
# expression
|
|
63
|
+
|
|
64
|
+
rule(:expression) {
|
|
65
|
+
res_statement
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
# body
|
|
69
|
+
|
|
70
|
+
rule(:body) {
|
|
71
|
+
(line_separator >> (block | expression)).repeat(1).as(:body) >>
|
|
72
|
+
line_separator
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
# blocks
|
|
76
|
+
|
|
77
|
+
rule(:begin_block) {
|
|
78
|
+
(str('concurrent').as(:type) >> space).maybe.as(:pre) >>
|
|
79
|
+
str('begin').as(:begin) >>
|
|
80
|
+
body >>
|
|
81
|
+
str('end')
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
rule(:define_block) {
|
|
85
|
+
str('define').as(:define) >> space >>
|
|
86
|
+
identifier.as(:name) >> str('()') >>
|
|
87
|
+
body >>
|
|
88
|
+
str('end')
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
rule(:block) {
|
|
92
|
+
define_block | begin_block
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
# root
|
|
96
|
+
|
|
97
|
+
rule(:radix) {
|
|
98
|
+
line_separator.maybe >> block >> line_separator.maybe
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
root(:radix)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
ds = [
|
|
106
|
+
%{
|
|
107
|
+
define f()
|
|
108
|
+
@res.name
|
|
109
|
+
end
|
|
110
|
+
},
|
|
111
|
+
%{
|
|
112
|
+
define f()
|
|
113
|
+
begin
|
|
114
|
+
@res.name
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
}
|
|
118
|
+
]
|
|
119
|
+
|
|
120
|
+
ds.each do |d|
|
|
121
|
+
|
|
122
|
+
puts '-' * 80
|
|
123
|
+
prettify(d)
|
|
124
|
+
|
|
125
|
+
parser = Parser.new
|
|
126
|
+
|
|
127
|
+
begin
|
|
128
|
+
parser.parse_with_debug(d)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
puts '-' * 80
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Error Reporting - Ruby Implementation
|
|
2
|
+
|
|
3
|
+
## How to Run
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
cd parsanol-ruby/example/error-reporting
|
|
7
|
+
ruby basic.rb
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Code Walkthrough
|
|
11
|
+
|
|
12
|
+
### Common Rules
|
|
13
|
+
|
|
14
|
+
Whitespace and comments are handled uniformly:
|
|
15
|
+
|
|
16
|
+
```ruby
|
|
17
|
+
rule(:space) { match('[ \t]').repeat(1) }
|
|
18
|
+
rule(:space?) { space.maybe }
|
|
19
|
+
rule(:newline) { match('[\r\n]') }
|
|
20
|
+
rule(:comment) { str('#') >> match('[^\r\n]').repeat }
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Separating common rules improves readability.
|
|
24
|
+
|
|
25
|
+
### Line Separator Rule
|
|
26
|
+
|
|
27
|
+
Complex line ending handles comments:
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
rule(:line_separator) {
|
|
31
|
+
(space? >> ((comment.maybe >> newline) | str(';')) >> space?).repeat(1)
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Lines can end with newline, semicolon, or comment followed by newline.
|
|
36
|
+
|
|
37
|
+
### Block Structure
|
|
38
|
+
|
|
39
|
+
Define and begin blocks share body structure:
|
|
40
|
+
|
|
41
|
+
```ruby
|
|
42
|
+
rule(:begin_block) {
|
|
43
|
+
(str('concurrent').as(:type) >> space).maybe.as(:pre) >>
|
|
44
|
+
str('begin').as(:begin) >>
|
|
45
|
+
body >>
|
|
46
|
+
str('end')
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
rule(:define_block) {
|
|
50
|
+
str('define').as(:define) >> space >>
|
|
51
|
+
identifier.as(:name) >> str('()') >>
|
|
52
|
+
body >>
|
|
53
|
+
str('end')
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Both have opening keyword, content, and closing `end`.
|
|
58
|
+
|
|
59
|
+
### Body Rule
|
|
60
|
+
|
|
61
|
+
Bodies contain expressions or nested blocks:
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
rule(:body) {
|
|
65
|
+
(line_separator >> (block | expression)).repeat(1).as(:body) >>
|
|
66
|
+
line_separator
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Recursive structure allows arbitrary nesting.
|
|
71
|
+
|
|
72
|
+
### parse_with_debug
|
|
73
|
+
|
|
74
|
+
The example uses debug parsing:
|
|
75
|
+
|
|
76
|
+
```ruby
|
|
77
|
+
parser.parse_with_debug(d)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
This method prints detailed error information when parsing fails.
|
|
81
|
+
|
|
82
|
+
### Prettify Helper
|
|
83
|
+
|
|
84
|
+
Error context is displayed with line numbers:
|
|
85
|
+
|
|
86
|
+
```ruby
|
|
87
|
+
def prettify(str)
|
|
88
|
+
puts " "*3 + " "*4 + "." + " "*4 + "10" + " "*3 + "." + " "*4 + "20"
|
|
89
|
+
str.lines.each_with_index do |line, index|
|
|
90
|
+
printf "%02d %s\n", index+1, line.chomp
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Column markers help identify error positions.
|
|
96
|
+
|
|
97
|
+
## Output Types
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
# Successful parse:
|
|
101
|
+
{:define=>"define", :name=>"f", :body=>[...]}
|
|
102
|
+
|
|
103
|
+
# Parse failure with debug:
|
|
104
|
+
# Displays:
|
|
105
|
+
# - Expected token at position
|
|
106
|
+
# - Line and column information
|
|
107
|
+
# - What was found vs. expected
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Design Decisions
|
|
111
|
+
|
|
112
|
+
### Why Separate Line Separator?
|
|
113
|
+
|
|
114
|
+
Line endings in this language can include comments. A dedicated rule handles the complexity.
|
|
115
|
+
|
|
116
|
+
### Why parse_with_debug?
|
|
117
|
+
|
|
118
|
+
`parse_with_debug` provides developer-friendly error output, essential for language tooling.
|
|
119
|
+
|
|
120
|
+
### Why Multiple Test Cases?
|
|
121
|
+
|
|
122
|
+
The example tests both simple and nested structures to demonstrate error reporting at different nesting levels.
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
# Expression Evaluator - Ruby Implementation
|
|
2
|
+
#
|
|
3
|
+
# A complete expression parser with operator precedence, variables,
|
|
4
|
+
# and function calls. Demonstrates building a practical calculator.
|
|
5
|
+
#
|
|
6
|
+
# Run with: ruby example/expression-evaluator/basic.rb
|
|
7
|
+
|
|
8
|
+
$:.unshift File.dirname(__FILE__) + "/../lib"
|
|
9
|
+
|
|
10
|
+
require 'parsanol/parslet'
|
|
11
|
+
|
|
12
|
+
# Expression parser with full operator precedence
|
|
13
|
+
class ExpressionParser < Parsanol::Parser
|
|
14
|
+
root :expression
|
|
15
|
+
|
|
16
|
+
# Comparison (lowest precedence)
|
|
17
|
+
rule(:expression) { comparison }
|
|
18
|
+
|
|
19
|
+
rule(:comparison) {
|
|
20
|
+
addition.as(:left) >>
|
|
21
|
+
((match('==|!=|<=|>=|<>').as(:op) | str('<') | str('>')).as(:op) >>
|
|
22
|
+
addition.as(:right)).repeat(1) |
|
|
23
|
+
addition
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
# Addition/subtraction
|
|
27
|
+
rule(:addition) {
|
|
28
|
+
multiplication.as(:left) >>
|
|
29
|
+
(match('[+-]').as(:op) >> multiplication.as(:right)).repeat(1) |
|
|
30
|
+
multiplication
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
# Multiplication/division/modulo
|
|
34
|
+
rule(:multiplication) {
|
|
35
|
+
power.as(:left) >>
|
|
36
|
+
(match('[*/%]').as(:op) >> power.as(:right)).repeat(1) |
|
|
37
|
+
power
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
# Exponentiation (right associative)
|
|
41
|
+
rule(:power) {
|
|
42
|
+
unary.as(:left) >>
|
|
43
|
+
(str('^').as(:op) >> power.as(:right)).maybe |
|
|
44
|
+
unary
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
# Unary operators
|
|
48
|
+
rule(:unary) {
|
|
49
|
+
(str('-').as(:op) >> unary.as(:operand)).as(:unary) |
|
|
50
|
+
(str('!').as(:op) >> unary.as(:operand)).as(:unary) |
|
|
51
|
+
primary
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# Primary: number, function call, variable, or parenthesized expression
|
|
55
|
+
rule(:primary) {
|
|
56
|
+
funcall |
|
|
57
|
+
number |
|
|
58
|
+
variable |
|
|
59
|
+
lparen >> expression >> rparen
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
rule(:number) {
|
|
63
|
+
(match('[0-9]').repeat(1) >> str('.') >> match('[0-9]').repeat(1) |
|
|
64
|
+
match('[0-9]').repeat(1)).as(:number) >> space?
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
rule(:variable) {
|
|
68
|
+
(match('[a-zA-Z_]') >> match('[a-zA-Z0-9_]').repeat).as(:variable) >> space?
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
rule(:funcall) {
|
|
72
|
+
(match('[a-zA-Z_]') >> match('[a-zA-Z0-9_]').repeat).as(:name) >>
|
|
73
|
+
lparen >>
|
|
74
|
+
arglist.as(:args) >>
|
|
75
|
+
rparen
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
rule(:arglist) {
|
|
79
|
+
(expression >> (comma >> expression).repeat).maybe
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
rule(:lparen) { str('(') >> space? }
|
|
83
|
+
rule(:rparen) { str(')') >> space? }
|
|
84
|
+
rule(:comma) { str(',') >> space? }
|
|
85
|
+
rule(:space?) { match('\s').repeat }
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# AST node classes
|
|
89
|
+
Number = Struct.new(:value) do
|
|
90
|
+
def eval(_ctx)
|
|
91
|
+
value
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
Variable = Struct.new(:name) do
|
|
96
|
+
def eval(ctx)
|
|
97
|
+
ctx.variables.fetch(name) { raise "Unknown variable: #{name}" }
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
BinaryOp = Struct.new(:left, :op, :right) do
|
|
102
|
+
def eval(ctx)
|
|
103
|
+
l = left.eval(ctx)
|
|
104
|
+
r = right.eval(ctx)
|
|
105
|
+
|
|
106
|
+
case op
|
|
107
|
+
when '+' then l + r
|
|
108
|
+
when '-' then l - r
|
|
109
|
+
when '*' then l * r
|
|
110
|
+
when '/' then l / r
|
|
111
|
+
when '%' then l % r
|
|
112
|
+
when '^' then l ** r
|
|
113
|
+
when '==' then l == r ? 1.0 : 0.0
|
|
114
|
+
when '!=' then l != r ? 1.0 : 0.0
|
|
115
|
+
when '<' then l < r ? 1.0 : 0.0
|
|
116
|
+
when '>' then l > r ? 1.0 : 0.0
|
|
117
|
+
when '<=' then l <= r ? 1.0 : 0.0
|
|
118
|
+
when '>=' then l >= r ? 1.0 : 0.0
|
|
119
|
+
else raise "Unknown operator: #{op}"
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
UnaryOp = Struct.new(:op, :operand) do
|
|
125
|
+
def eval(ctx)
|
|
126
|
+
v = operand.eval(ctx)
|
|
127
|
+
case op
|
|
128
|
+
when '-' then -v
|
|
129
|
+
when '!' then v == 0 ? 1.0 : 0.0
|
|
130
|
+
else raise "Unknown unary operator: #{op}"
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
FunctionCall = Struct.new(:name, :args) do
|
|
136
|
+
def eval(ctx)
|
|
137
|
+
arg_values = args.map { |a| a.eval(ctx) }
|
|
138
|
+
|
|
139
|
+
if ctx.functions.key?(name)
|
|
140
|
+
ctx.functions[name].call(arg_values)
|
|
141
|
+
else
|
|
142
|
+
raise "Unknown function: #{name}"
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Transform parse tree to AST
|
|
148
|
+
class ExpressionTransform < Parsanol::Transform
|
|
149
|
+
rule(number: simple(:n)) { Number.new(n.to_s.to_f) }
|
|
150
|
+
rule(variable: simple(:v)) { Variable.new(v.to_s) }
|
|
151
|
+
|
|
152
|
+
rule(name: simple(:n), args: simple(:a)) {
|
|
153
|
+
FunctionCall.new(n.to_s, a.is_a?(Array) ? a : [a])
|
|
154
|
+
}
|
|
155
|
+
rule(name: simple(:n), args: sequence(:a)) {
|
|
156
|
+
FunctionCall.new(n.to_s, a)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
rule(left: simple(:l)) { l }
|
|
160
|
+
|
|
161
|
+
rule(left: simple(:l), op: simple(:o), right: simple(:r)) {
|
|
162
|
+
BinaryOp.new(l, o.to_s, r)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
rule(unary: { op: simple(:o), operand: simple(:e) }) {
|
|
166
|
+
UnaryOp.new(o.to_s, e)
|
|
167
|
+
}
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Evaluation context with variables and functions
|
|
171
|
+
class EvalContext
|
|
172
|
+
attr_accessor :variables, :functions
|
|
173
|
+
|
|
174
|
+
def initialize
|
|
175
|
+
@variables = {
|
|
176
|
+
'PI' => Math::PI,
|
|
177
|
+
'E' => Math::E
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
@functions = {
|
|
181
|
+
'sin' => ->(args) { Math.sin(args[0] || 0) },
|
|
182
|
+
'cos' => ->(args) { Math.cos(args[0] || 0) },
|
|
183
|
+
'tan' => ->(args) { Math.tan(args[0] || 0) },
|
|
184
|
+
'sqrt' => ->(args) { Math.sqrt(args[0] || 0) },
|
|
185
|
+
'abs' => ->(args) { (args[0] || 0).abs },
|
|
186
|
+
'floor' => ->(args) { (args[0] || 0).floor },
|
|
187
|
+
'ceil' => ->(args) { (args[0] || 0).ceil },
|
|
188
|
+
'round' => ->(args) { (args[0] || 0).round },
|
|
189
|
+
'min' => ->(args) { [args[0] || 0, args[1] || 0].min },
|
|
190
|
+
'max' => ->(args) { [args[0] || 0, args[1] || 0].max },
|
|
191
|
+
'log' => ->(args) { Math.log(args[0] || 1) },
|
|
192
|
+
'exp' => ->(args) { Math.exp(args[0] || 0) }
|
|
193
|
+
}
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def set(name, value)
|
|
197
|
+
@variables[name] = value
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Evaluate an expression string
|
|
202
|
+
def evaluate(str, ctx = EvalContext.new)
|
|
203
|
+
parser = ExpressionParser.new
|
|
204
|
+
transform = ExpressionTransform.new
|
|
205
|
+
|
|
206
|
+
tree = parser.parse(str)
|
|
207
|
+
ast = transform.apply(tree)
|
|
208
|
+
|
|
209
|
+
# Handle BinaryOp chains with multiple ops
|
|
210
|
+
if ast.is_a?(Array) && ast.length == 1
|
|
211
|
+
ast = ast.first
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Reduce left-associative chains
|
|
215
|
+
while ast.is_a?(Hash) && ast.key?(:left)
|
|
216
|
+
left = ast[:left]
|
|
217
|
+
if left.is_a?(Hash) && left.key?(:left)
|
|
218
|
+
# Nested chain - flatten
|
|
219
|
+
inner = evaluate_helper(left, ctx)
|
|
220
|
+
ast = BinaryOp.new(inner, ast[:op], ast[:right])
|
|
221
|
+
else
|
|
222
|
+
ast = BinaryOp.new(left, ast[:op], ast[:right])
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
ast.eval(ctx)
|
|
227
|
+
rescue => e
|
|
228
|
+
"Error: #{e.message}"
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def evaluate_helper(node, ctx)
|
|
232
|
+
return node unless node.is_a?(Hash)
|
|
233
|
+
|
|
234
|
+
if node.key?(:left)
|
|
235
|
+
left = evaluate_helper(node[:left], ctx)
|
|
236
|
+
right = evaluate_helper(node[:right], ctx)
|
|
237
|
+
BinaryOp.new(left, node[:op], right)
|
|
238
|
+
else
|
|
239
|
+
node
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# Main demo
|
|
244
|
+
if __FILE__ == $0
|
|
245
|
+
ctx = EvalContext.new
|
|
246
|
+
ctx.set('x', 10.0)
|
|
247
|
+
ctx.set('y', 5.0)
|
|
248
|
+
|
|
249
|
+
puts "Expression Evaluator Example"
|
|
250
|
+
puts "=" * 40
|
|
251
|
+
puts
|
|
252
|
+
puts "Variables: x = #{ctx.variables['x']}, y = #{ctx.variables['y']}"
|
|
253
|
+
puts "Constants: PI = #{ctx.variables['PI']}, E = #{ctx.variables['E']}"
|
|
254
|
+
puts
|
|
255
|
+
|
|
256
|
+
expressions = [
|
|
257
|
+
"1 + 2 * 3",
|
|
258
|
+
"(1 + 2) * 3",
|
|
259
|
+
"2 ^ 3 ^ 2",
|
|
260
|
+
"x + y",
|
|
261
|
+
"x * y - 5",
|
|
262
|
+
"sin(PI / 2)",
|
|
263
|
+
"sqrt(16)",
|
|
264
|
+
"max(x, y)",
|
|
265
|
+
"x > y",
|
|
266
|
+
"min(sin(0), cos(0))"
|
|
267
|
+
]
|
|
268
|
+
|
|
269
|
+
printf "%-25s | %s\n", "Expression", "Result"
|
|
270
|
+
puts "-" * 40
|
|
271
|
+
|
|
272
|
+
expressions.each do |expr|
|
|
273
|
+
result = evaluate(expr, ctx)
|
|
274
|
+
printf "%-25s | %s\n", expr, result
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
# Command line argument
|
|
278
|
+
if ARGV.length > 0
|
|
279
|
+
expr = ARGV.join(' ')
|
|
280
|
+
puts
|
|
281
|
+
puts "Evaluating: #{expr}"
|
|
282
|
+
puts "Result: #{evaluate(expr, ctx)}"
|
|
283
|
+
end
|
|
284
|
+
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Expression Evaluator - Ruby Implementation
|
|
2
|
+
|
|
3
|
+
## How to Run
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
cd parsanol-ruby/example/expression-evaluator
|
|
7
|
+
ruby basic.rb "1+2*3"
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Code Walkthrough
|
|
11
|
+
|
|
12
|
+
### Operator Precedence Hierarchy
|
|
13
|
+
|
|
14
|
+
The parser uses layered rules for precedence:
|
|
15
|
+
|
|
16
|
+
```ruby
|
|
17
|
+
rule(:expression) { comparison }
|
|
18
|
+
rule(:comparison) { addition >> (op >> addition).repeat | addition }
|
|
19
|
+
rule(:addition) { multiplication >> (op >> multiplication).repeat | multiplication }
|
|
20
|
+
rule(:multiplication) { power >> (op >> power).repeat | power }
|
|
21
|
+
rule(:power) { unary >> (str('^') >> power).maybe | unary }
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Lower precedence operators are higher in the rule hierarchy. Comparison is tried first, falling through to addition, then multiplication, then power.
|
|
25
|
+
|
|
26
|
+
### Right Associativity for Exponentiation
|
|
27
|
+
|
|
28
|
+
Power uses `.maybe` for right associativity:
|
|
29
|
+
|
|
30
|
+
```ruby
|
|
31
|
+
rule(:power) {
|
|
32
|
+
unary.as(:left) >>
|
|
33
|
+
(str('^').as(:op) >> power.as(:right)).maybe |
|
|
34
|
+
unary
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Recursive reference to `power` on the right creates `2^(3^2)` instead of `(2^3)^2`.
|
|
39
|
+
|
|
40
|
+
### Unary Operators
|
|
41
|
+
|
|
42
|
+
Unary minus and logical not prefix expressions:
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
rule(:unary) {
|
|
46
|
+
(str('-').as(:op) >> unary.as(:operand)).as(:unary) |
|
|
47
|
+
(str('!').as(:op) >> unary.as(:operand)).as(:unary) |
|
|
48
|
+
primary
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Recursive definition allows `--5` (double negation).
|
|
53
|
+
|
|
54
|
+
### Function Calls
|
|
55
|
+
|
|
56
|
+
Functions have name followed by parenthesized arguments:
|
|
57
|
+
|
|
58
|
+
```ruby
|
|
59
|
+
rule(:funcall) {
|
|
60
|
+
(match('[a-zA-Z_]') >> match('[a-zA-Z0-9_]').repeat).as(:name) >>
|
|
61
|
+
lparen >>
|
|
62
|
+
arglist.as(:args) >>
|
|
63
|
+
rparen
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
rule(:arglist) {
|
|
67
|
+
(expression >> (comma >> expression).repeat).maybe
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Arguments are comma-separated expressions; empty arglists are valid.
|
|
72
|
+
|
|
73
|
+
### AST Node Classes
|
|
74
|
+
|
|
75
|
+
Ruby structs represent AST nodes with evaluation logic:
|
|
76
|
+
|
|
77
|
+
```ruby
|
|
78
|
+
BinaryOp = Struct.new(:left, :op, :right) do
|
|
79
|
+
def eval(ctx)
|
|
80
|
+
l = left.eval(ctx)
|
|
81
|
+
r = right.eval(ctx)
|
|
82
|
+
case op
|
|
83
|
+
when '+' then l + r
|
|
84
|
+
when '-' then l - r
|
|
85
|
+
# ...
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Each node type implements `eval(context)` for recursive evaluation.
|
|
92
|
+
|
|
93
|
+
### Evaluation Context
|
|
94
|
+
|
|
95
|
+
Context holds variables and functions:
|
|
96
|
+
|
|
97
|
+
```ruby
|
|
98
|
+
class EvalContext
|
|
99
|
+
attr_accessor :variables, :functions
|
|
100
|
+
|
|
101
|
+
def initialize
|
|
102
|
+
@variables = { 'PI' => Math::PI, 'E' => Math::E }
|
|
103
|
+
@functions = {
|
|
104
|
+
'sin' => ->(args) { Math.sin(args[0] || 0) },
|
|
105
|
+
'cos' => ->(args) { Math.cos(args[0] || 0) },
|
|
106
|
+
# ...
|
|
107
|
+
}
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Functions are Ruby lambdas that receive argument arrays.
|
|
113
|
+
|
|
114
|
+
## Output Types
|
|
115
|
+
|
|
116
|
+
```ruby
|
|
117
|
+
Number.new(42.0) # Numeric literal
|
|
118
|
+
Variable.new("x") # Variable reference
|
|
119
|
+
BinaryOp.new(Number.new(1), "+", Number.new(2)) # Binary operation
|
|
120
|
+
UnaryOp.new("-", Number.new(5)) # Unary negation
|
|
121
|
+
FunctionCall.new("sin", [Variable.new("x")]) # Function call
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
After `eval`: returns Float result.
|
|
125
|
+
|
|
126
|
+
## Design Decisions
|
|
127
|
+
|
|
128
|
+
### Why Layered Rules for Precedence?
|
|
129
|
+
|
|
130
|
+
Layered rules naturally express precedence in PEG parsers. Each layer only "sees" operators at its level, preventing incorrect bindings.
|
|
131
|
+
|
|
132
|
+
### Why Structs with eval Methods?
|
|
133
|
+
|
|
134
|
+
Structs are lightweight and can define instance methods. Embedding `eval` in each node enables clean recursive evaluation without visitor patterns.
|
|
135
|
+
|
|
136
|
+
### Why Lambda for Functions?
|
|
137
|
+
|
|
138
|
+
Ruby lambdas are first-class and can be stored in a hash. This allows easy extension of the function library without modifying the evaluator.
|