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 @@
|
|
|
1
|
+
parses 'aba'
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"And when Spring comes"
|
|
2
|
+
{:bud=>{:stem=>[{:branch=>:leaf}]}}
|
|
3
|
+
|
|
4
|
+
"And when Summer comes"
|
|
5
|
+
{:bud=>{:stem=>[{:branch=>[:leaf, :flower]}]}}
|
|
6
|
+
|
|
7
|
+
"And when Fall comes"
|
|
8
|
+
Fruit!
|
|
9
|
+
Falling Leaves!
|
|
10
|
+
{:bud=>{:stem=>[{:branch=>[]}]}}
|
|
11
|
+
|
|
12
|
+
"And when Winter comes"
|
|
13
|
+
{:bud=>{:stem=>[]}}
|
|
14
|
+
|
|
15
|
+
"And when Spring comes"
|
|
16
|
+
{:bud=>{:stem=>[{:branch=>:leaf}]}}
|
|
17
|
+
|
|
18
|
+
"And when Summer comes"
|
|
19
|
+
{:bud=>{:stem=>[{:branch=>[:leaf, :flower]}]}}
|
|
20
|
+
|
|
21
|
+
"And when Fall comes"
|
|
22
|
+
Fruit!
|
|
23
|
+
Falling Leaves!
|
|
24
|
+
{:bud=>{:stem=>[{:branch=>[]}]}}
|
|
25
|
+
|
|
26
|
+
"And when Winter comes"
|
|
27
|
+
{:bud=>{:stem=>[]}}
|
|
28
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
["RubyKaigi2009のテーマは、「変わる/変える」です。", " 前回のRubyKaigi2008のテーマであった「多様性」の言葉の通り、 2008年はRubyそのものに関しても、またRubyの活躍する舞台に関しても、 ますます多様化が進みつつあります。", "RubyKaigi2008は、そのような Rubyの生態系をあらためて認識する場となりました。", " しかし、こうした多様化が進む中、異なる者同士が単純に距離を 置いたままでは、その違いを認識したところであまり意味がありません。", " 異なる実装、異なる思想、異なる背景といった、様々な多様性を理解しつつ、 すり合わせるべきものをすり合わせ、変えていくべきところを 変えていくことが、豊かな未来へとつながる道に違いありません。"]
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
|
|
2
|
+
# A demonstration of the new precedence climbing infix expression parser.
|
|
3
|
+
|
|
4
|
+
$:.unshift File.dirname(__FILE__) + "/../lib"
|
|
5
|
+
|
|
6
|
+
require 'pp'
|
|
7
|
+
require 'rspec'
|
|
8
|
+
require 'parsanol/parslet'
|
|
9
|
+
require 'parsanol/rig/rspec'
|
|
10
|
+
require 'parsanol/convenience'
|
|
11
|
+
|
|
12
|
+
class InfixExpressionParser < Parsanol::Parser
|
|
13
|
+
root :variable_assignment_list
|
|
14
|
+
|
|
15
|
+
rule(:space) { match[' '] }
|
|
16
|
+
|
|
17
|
+
def cts atom
|
|
18
|
+
atom >> space.repeat
|
|
19
|
+
end
|
|
20
|
+
def infix *args
|
|
21
|
+
Infix.new(*args)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# This is the heart of the infix expression parser: real simple definitions
|
|
25
|
+
# for all the pieces we need.
|
|
26
|
+
rule(:mul_op) { cts match['*/'] }
|
|
27
|
+
rule(:add_op) { cts match['+-'] }
|
|
28
|
+
rule(:digit) { match['0-9'] }
|
|
29
|
+
rule(:integer) { cts digit.repeat(1).as(:int) }
|
|
30
|
+
|
|
31
|
+
rule(:expression) { infix_expression(integer,
|
|
32
|
+
[mul_op, 2, :left],
|
|
33
|
+
[add_op, 1, :right]) }
|
|
34
|
+
|
|
35
|
+
# And now adding variable assignments to that, just to a) demonstrate this
|
|
36
|
+
# embedded in a bigger parser, and b) make the example interesting.
|
|
37
|
+
rule(:variable_assignment_list) {
|
|
38
|
+
variable_assignment.repeat(1) }
|
|
39
|
+
rule(:variable_assignment) {
|
|
40
|
+
identifier.as(:ident) >> equal_sign >> expression.as(:exp) >> eol }
|
|
41
|
+
rule(:identifier) {
|
|
42
|
+
cts (match['a-z'] >> match['a-zA-Z0-9'].repeat) }
|
|
43
|
+
rule(:equal_sign) {
|
|
44
|
+
cts str('=') }
|
|
45
|
+
rule(:eol) {
|
|
46
|
+
cts(str("\n")) | any.absent? }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
class InfixInterpreter < Parsanol::Transform
|
|
50
|
+
rule(int: simple(:int)) { Integer(int) }
|
|
51
|
+
rule(ident: simple(:ident), exp: simple(:result)) { |d|
|
|
52
|
+
d[:doc][d[:ident].to_s.strip.to_sym] = d[:result] }
|
|
53
|
+
|
|
54
|
+
rule(l: simple(:l), o: /^\*/, r: simple(:r)) { l * r }
|
|
55
|
+
rule(l: simple(:l), o: /^\+/, r: simple(:r)) { l + r }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
input = <<ASSIGNMENTS
|
|
59
|
+
a = 1
|
|
60
|
+
b = 2
|
|
61
|
+
c = 3 * 25
|
|
62
|
+
d = 100 + 3*4
|
|
63
|
+
ASSIGNMENTS
|
|
64
|
+
|
|
65
|
+
puts input
|
|
66
|
+
|
|
67
|
+
int_tree = InfixExpressionParser.new.parse_with_debug(input)
|
|
68
|
+
bindings = {}
|
|
69
|
+
result = InfixInterpreter.new.apply(int_tree, doc: bindings)
|
|
70
|
+
|
|
71
|
+
pp bindings
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Precedence Calculator - Ruby Implementation
|
|
2
|
+
|
|
3
|
+
## How to Run
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
cd parsanol-ruby/example/prec-calc
|
|
7
|
+
ruby basic.rb
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Code Walkthrough
|
|
11
|
+
|
|
12
|
+
### Infix Expression Parser
|
|
13
|
+
|
|
14
|
+
Parslet's Infix helper simplifies precedence handling:
|
|
15
|
+
|
|
16
|
+
```ruby
|
|
17
|
+
rule(:expression) { infix_expression(integer,
|
|
18
|
+
[mul_op, 2, :left],
|
|
19
|
+
[add_op, 1, :right]) }
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Each operator tuple specifies: operator rule, precedence level, and associativity.
|
|
23
|
+
|
|
24
|
+
### Operator Definitions
|
|
25
|
+
|
|
26
|
+
Operators are simple character matches:
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
29
|
+
rule(:mul_op) { cts match['*/'] }
|
|
30
|
+
rule(:add_op) { cts match['+-'] }
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`cts` (consume trailing space) is a helper that strips whitespace after atoms.
|
|
34
|
+
|
|
35
|
+
### Helper Methods
|
|
36
|
+
|
|
37
|
+
The `cts` and `infix` methods reduce repetition:
|
|
38
|
+
|
|
39
|
+
```ruby
|
|
40
|
+
def cts atom
|
|
41
|
+
atom >> space.repeat
|
|
42
|
+
end
|
|
43
|
+
def infix *args
|
|
44
|
+
Infix.new(*args)
|
|
45
|
+
end
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
These keep the grammar DRY and readable.
|
|
49
|
+
|
|
50
|
+
### Variable Assignment Rule
|
|
51
|
+
|
|
52
|
+
Assignments combine identifier with expression:
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
rule(:variable_assignment) {
|
|
56
|
+
identifier.as(:ident) >> equal_sign >> expression.as(:exp) >> eol
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Labels (`:ident`, `:exp`) mark captures for transformation.
|
|
61
|
+
|
|
62
|
+
### Transform Rules
|
|
63
|
+
|
|
64
|
+
The interpreter transforms parse tree to values:
|
|
65
|
+
|
|
66
|
+
```ruby
|
|
67
|
+
rule(int: simple(:int)) { Integer(int) }
|
|
68
|
+
rule(l: simple(:l), o: /^\*/, r: simple(:r)) { l * r }
|
|
69
|
+
rule(l: simple(:l), o: /^\+/, r: simple(:r)) { l + r }
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Regex patterns on `:o` distinguish operators in transform rules.
|
|
73
|
+
|
|
74
|
+
### Binding Context
|
|
75
|
+
|
|
76
|
+
Transform receives context for variable storage:
|
|
77
|
+
|
|
78
|
+
```ruby
|
|
79
|
+
rule(ident: simple(:ident), exp: simple(:result)) { |d|
|
|
80
|
+
d[:doc][d[:ident].to_s.strip.to_sym] = d[:result]
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The `doc:` parameter passes a hash for accumulating bindings.
|
|
85
|
+
|
|
86
|
+
## Output Types
|
|
87
|
+
|
|
88
|
+
```ruby
|
|
89
|
+
# Parse tree
|
|
90
|
+
[{:ident=>"a"@0, :exp=>{:int=>"1"@4}},
|
|
91
|
+
{:ident=>"b"@8, :exp=>{:int=>"2"@12}},
|
|
92
|
+
{:ident=>"c"@16, :exp=>{:l=>{:int=>"3"@20}, :o=>"*"@22, :r=>{:int=>"25"@24}}}]
|
|
93
|
+
|
|
94
|
+
# After transform (bindings)
|
|
95
|
+
{:a=>1, :b=>2, :c=>75}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Design Decisions
|
|
99
|
+
|
|
100
|
+
### Why Infix Helper?
|
|
101
|
+
|
|
102
|
+
The `Infix` class encapsulates precedence climbing logic. Without it, you'd need multiple recursive rules with careful ordering.
|
|
103
|
+
|
|
104
|
+
### Why Regex in Transform?
|
|
105
|
+
|
|
106
|
+
Parslet's transform doesn't support operator-specific rules directly. Regex matching on the operator string provides clean separation.
|
|
107
|
+
|
|
108
|
+
### Why Pass Context to Transform?
|
|
109
|
+
|
|
110
|
+
Variable bindings need accumulation across statements. Passing a context hash enables this without global state.
|
|
111
|
+
|
|
112
|
+
### Why Right Associativity for Addition?
|
|
113
|
+
|
|
114
|
+
This example demonstrates both associativities. In practice, addition is typically left-associative; the right associativity here is for demonstration.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# The example from the readme. With this, I am making sure that the readme
|
|
2
|
+
# 'works'. Is this too messy?
|
|
3
|
+
|
|
4
|
+
$:.unshift File.dirname(__FILE__) + "/../lib"
|
|
5
|
+
|
|
6
|
+
# cut here -------------------------------------------------------------------
|
|
7
|
+
require 'parsanol/parslet'
|
|
8
|
+
include Parsanol::Parslet
|
|
9
|
+
|
|
10
|
+
# Constructs a parser using a Parser Expression Grammar like DSL:
|
|
11
|
+
parser = str('"') >>
|
|
12
|
+
(
|
|
13
|
+
str('\\') >> any |
|
|
14
|
+
str('"').absent? >> any
|
|
15
|
+
).repeat.as(:string) >>
|
|
16
|
+
str('"')
|
|
17
|
+
|
|
18
|
+
# Parse the string and capture parts of the interpretation (:string above)
|
|
19
|
+
tree = parser.parse('"This is a \\"String\\" in which you can escape stuff"')
|
|
20
|
+
|
|
21
|
+
tree # => {:string=>"This is a \\\"String\\\" in which you can escape stuff"}
|
|
22
|
+
|
|
23
|
+
# Here's how you can grab results from that tree:
|
|
24
|
+
|
|
25
|
+
transform = Parsanol::Transform.new do
|
|
26
|
+
rule(:string => simple(:x)) {
|
|
27
|
+
puts "String contents: #{x}" }
|
|
28
|
+
end
|
|
29
|
+
transform.apply(tree)
|
|
30
|
+
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Error Demo - Ruby Implementation
|
|
2
|
+
|
|
3
|
+
## How to Run
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
cd parsanol-ruby/example/readme
|
|
7
|
+
ruby basic.rb
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Code Walkthrough
|
|
11
|
+
|
|
12
|
+
### Simple Parser Definition
|
|
13
|
+
|
|
14
|
+
A minimal parser that matches repeated 'a' characters:
|
|
15
|
+
|
|
16
|
+
```ruby
|
|
17
|
+
class MyParser < Parsanol::Parser
|
|
18
|
+
rule(:a) { str('a').repeat }
|
|
19
|
+
|
|
20
|
+
def parse(str)
|
|
21
|
+
a.parse(str)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The parser only accepts strings of 'a's.
|
|
27
|
+
|
|
28
|
+
### Successful Parse
|
|
29
|
+
|
|
30
|
+
When input matches the grammar:
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
MyParser.new.parse('aaaa')
|
|
34
|
+
# => "aaaa"
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Returns the matched string.
|
|
38
|
+
|
|
39
|
+
### Failed Parse
|
|
40
|
+
|
|
41
|
+
When input doesn't match:
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
MyParser.new.parse('bbbb')
|
|
45
|
+
# => Parsanol::ParseFailed: Expected "a" at line 1, column 1
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Raises an exception with error details.
|
|
49
|
+
|
|
50
|
+
### Error Reporting
|
|
51
|
+
|
|
52
|
+
Parslet provides rich error messages:
|
|
53
|
+
|
|
54
|
+
- What was expected
|
|
55
|
+
- Where the error occurred (line, column)
|
|
56
|
+
- What was found instead
|
|
57
|
+
|
|
58
|
+
## Output Types
|
|
59
|
+
|
|
60
|
+
```ruby
|
|
61
|
+
# Success:
|
|
62
|
+
"aaaa"
|
|
63
|
+
|
|
64
|
+
# Failure (raises exception):
|
|
65
|
+
# Parsanol::ParseFailed: Expected "a" at line 1, column 1
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Design Decisions
|
|
69
|
+
|
|
70
|
+
### Why This Example?
|
|
71
|
+
|
|
72
|
+
Demonstrates basic error handling behavior. Understanding errors is crucial for debugging parsers.
|
|
73
|
+
|
|
74
|
+
### Why Repeat?
|
|
75
|
+
|
|
76
|
+
`.repeat` allows zero or more matches. Empty string would also parse successfully.
|
|
77
|
+
|
|
78
|
+
### Ruby-Only Feature
|
|
79
|
+
|
|
80
|
+
This is a simple demonstration of Parslet-compatible error handling.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
|
|
2
|
+
$:.unshift File.dirname(__FILE__) + "/../lib"
|
|
3
|
+
require 'parsanol/parslet'
|
|
4
|
+
|
|
5
|
+
include Parsanol::Parslet
|
|
6
|
+
|
|
7
|
+
parser = str('a').capture(:a) >> scope { str('b').capture(:a) } >>
|
|
8
|
+
dynamic { |s,c| str(c.captures[:a]) }
|
|
9
|
+
|
|
10
|
+
begin
|
|
11
|
+
parser.parse('aba')
|
|
12
|
+
puts "parses 'aba'"
|
|
13
|
+
rescue
|
|
14
|
+
puts "exception!"
|
|
15
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Scope Handling - Ruby Implementation
|
|
2
|
+
|
|
3
|
+
## How to Run
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
cd parsanol-ruby/example/scopes
|
|
7
|
+
ruby basic.rb
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Code Walkthrough
|
|
11
|
+
|
|
12
|
+
### Scope Blocks
|
|
13
|
+
|
|
14
|
+
Scopes isolate captures:
|
|
15
|
+
|
|
16
|
+
```ruby
|
|
17
|
+
parser = str('a').capture(:a) >>
|
|
18
|
+
scope { str('b').capture(:a) } >>
|
|
19
|
+
dynamic { |s,c| str(c.captures[:a]) }
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The outer capture of 'a' is shadowed by the inner scope's capture of 'b'.
|
|
23
|
+
|
|
24
|
+
### Shadowing Behavior
|
|
25
|
+
|
|
26
|
+
Inner scopes can shadow outer captures:
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
29
|
+
# Outer scope captures :a => 'a'
|
|
30
|
+
str('a').capture(:a) >>
|
|
31
|
+
# Inner scope captures :a => 'b' (shadows outer)
|
|
32
|
+
scope { str('b').capture(:a) } >>
|
|
33
|
+
# After scope, :a still refers to inner capture
|
|
34
|
+
dynamic { |s,c| str(c.captures[:a]) } # matches 'b'
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Isolation Benefits
|
|
38
|
+
|
|
39
|
+
Scopes prevent capture pollution:
|
|
40
|
+
|
|
41
|
+
- Nested constructs don't interfere with outer captures
|
|
42
|
+
- Each level has its own namespace
|
|
43
|
+
- Cleanup is automatic when scope exits
|
|
44
|
+
|
|
45
|
+
## Output Types
|
|
46
|
+
|
|
47
|
+
```ruby
|
|
48
|
+
# Input: 'aba'
|
|
49
|
+
# Parses successfully because:
|
|
50
|
+
# - 'a' captured as :a in outer
|
|
51
|
+
# - 'b' captured as :a in inner scope (shadows)
|
|
52
|
+
# - dynamic matches 'b' (inner capture)
|
|
53
|
+
|
|
54
|
+
# Input: 'aaa'
|
|
55
|
+
# Would FAIL because:
|
|
56
|
+
# - 'a' captured as :a
|
|
57
|
+
# - scope captures 'a' as :a
|
|
58
|
+
# - dynamic matches 'a' (not 'b')
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Design Decisions
|
|
62
|
+
|
|
63
|
+
### Why Lexical Scoping?
|
|
64
|
+
|
|
65
|
+
Lexical scoping matches how variables work in most programming languages. Users can reason about capture visibility.
|
|
66
|
+
|
|
67
|
+
### Why Shadow Instead of Merge?
|
|
68
|
+
|
|
69
|
+
Shadowing prevents accidental capture conflicts. Explicit naming would be needed for merging.
|
|
70
|
+
|
|
71
|
+
### Ruby-Only Feature
|
|
72
|
+
|
|
73
|
+
Scope blocks are a Parslet-specific feature for managing capture context. The Rust implementation uses different patterns for similar functionality.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "scopes",
|
|
3
|
+
"title": "Scope DSL",
|
|
4
|
+
"description": "Demonstrate Parslet's scope DSL for managing variable bindings across grammar rules.",
|
|
5
|
+
"category": "conceptual",
|
|
6
|
+
"tags": ["scope", "binding", "parslet", "context"],
|
|
7
|
+
"difficulty": "intermediate",
|
|
8
|
+
"concepts": ["scope", "variable binding", "context", "nested rules"],
|
|
9
|
+
|
|
10
|
+
"motivation": {
|
|
11
|
+
"why": "Scopes provide isolated variable binding contexts within grammar rules. This enables nested language constructs and parameterized rules.",
|
|
12
|
+
"useCases": [
|
|
13
|
+
"Nested language constructs",
|
|
14
|
+
"Parameterized rules",
|
|
15
|
+
"Context-sensitive parsing"
|
|
16
|
+
]
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
"inputFormat": {
|
|
20
|
+
"description": "Expressions with scoped variable bindings.",
|
|
21
|
+
"examples": [
|
|
22
|
+
{ "input": "let x = 1", "description": "Variable declaration in scope", "valid": true }
|
|
23
|
+
]
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
"outputFormat": {
|
|
27
|
+
"description": "Scoped variable bindings.",
|
|
28
|
+
"structure": {
|
|
29
|
+
"scope": { "description": "The scope context with bound variables" }
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
"rubyOnly": true,
|
|
34
|
+
"parsletCompatible": true,
|
|
35
|
+
"implementations": {
|
|
36
|
+
"ruby": { "basic": "basic.rb" }
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
$:.unshift File.dirname(__FILE__) + "/../lib"
|
|
2
|
+
|
|
3
|
+
require 'parsanol/parslet'
|
|
4
|
+
require 'pp'
|
|
5
|
+
|
|
6
|
+
tree = {:bud => {:stem => []}}
|
|
7
|
+
|
|
8
|
+
class Spring < Parsanol::Transform
|
|
9
|
+
rule(:stem => sequence(:branches)) {
|
|
10
|
+
{:stem => (branches + [{:branch => :leaf}])}
|
|
11
|
+
}
|
|
12
|
+
end
|
|
13
|
+
class Summer < Parsanol::Transform
|
|
14
|
+
rule(:stem => subtree(:branches)) {
|
|
15
|
+
new_branches = branches.map { |b| {:branch => [:leaf, :flower]} }
|
|
16
|
+
{:stem => new_branches}
|
|
17
|
+
}
|
|
18
|
+
end
|
|
19
|
+
class Fall < Parsanol::Transform
|
|
20
|
+
rule(:branch => sequence(:x)) {
|
|
21
|
+
x.each { |e| puts "Fruit!" if e==:flower }
|
|
22
|
+
x.each { |e| puts "Falling Leaves!" if e==:leaf }
|
|
23
|
+
{:branch => []}
|
|
24
|
+
}
|
|
25
|
+
end
|
|
26
|
+
class Winter < Parsanol::Transform
|
|
27
|
+
rule(:stem => subtree(:x)) {
|
|
28
|
+
{:stem => []}
|
|
29
|
+
}
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def do_seasons(tree)
|
|
33
|
+
[Spring, Summer, Fall, Winter].each do |season|
|
|
34
|
+
p "And when #{season} comes"
|
|
35
|
+
tree = season.new.apply(tree)
|
|
36
|
+
pp tree
|
|
37
|
+
puts
|
|
38
|
+
end
|
|
39
|
+
tree
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# What marvel of life!
|
|
43
|
+
tree = do_seasons(tree)
|
|
44
|
+
tree = do_seasons(tree)
|
|
45
|
+
|
|
46
|
+
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Transform Patterns - Ruby Implementation
|
|
2
|
+
|
|
3
|
+
## How to Run
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
cd parsanol-ruby/example/seasons
|
|
7
|
+
ruby basic.rb
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Code Walkthrough
|
|
11
|
+
|
|
12
|
+
### Transform Class Structure
|
|
13
|
+
|
|
14
|
+
Each season is a separate transform class:
|
|
15
|
+
|
|
16
|
+
```ruby
|
|
17
|
+
class Spring < Parsanol::Transform
|
|
18
|
+
rule(:stem => sequence(:branches)) {
|
|
19
|
+
{:stem => (branches + [{:branch => :leaf}])}
|
|
20
|
+
}
|
|
21
|
+
end
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Different classes encapsulate different transformation behaviors.
|
|
25
|
+
|
|
26
|
+
### Sequence Pattern
|
|
27
|
+
|
|
28
|
+
`sequence(:x)` matches arrays of simple values:
|
|
29
|
+
|
|
30
|
+
```ruby
|
|
31
|
+
rule(:stem => sequence(:branches)) {
|
|
32
|
+
{:stem => (branches + [{:branch => :leaf}])}
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`branches` is bound to the array content.
|
|
37
|
+
|
|
38
|
+
### Subtree Pattern
|
|
39
|
+
|
|
40
|
+
`subtree(:x)` matches any nested structure:
|
|
41
|
+
|
|
42
|
+
```ruby
|
|
43
|
+
rule(:stem => subtree(:branches)) {
|
|
44
|
+
new_branches = branches.map { |b| {:branch => [:leaf, :flower]} }
|
|
45
|
+
{:stem => new_branches}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Recursively applies to children.
|
|
50
|
+
|
|
51
|
+
### Branch Pattern
|
|
52
|
+
|
|
53
|
+
Match and transform nested arrays:
|
|
54
|
+
|
|
55
|
+
```ruby
|
|
56
|
+
class Fall < Parsanol::Transform
|
|
57
|
+
rule(:branch => sequence(:x)) {
|
|
58
|
+
x.each { |e| puts "Fruit!" if e == :flower }
|
|
59
|
+
x.each { |e| puts "Falling Leaves!" if e == :leaf }
|
|
60
|
+
{:branch => []}
|
|
61
|
+
}
|
|
62
|
+
end
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Iterate over array contents while transforming.
|
|
66
|
+
|
|
67
|
+
### Composition
|
|
68
|
+
|
|
69
|
+
Multiple transforms are applied in sequence:
|
|
70
|
+
|
|
71
|
+
```ruby
|
|
72
|
+
def do_seasons(tree)
|
|
73
|
+
[Spring, Summer, Fall, Winter].each do |season|
|
|
74
|
+
tree = season.new.apply(tree)
|
|
75
|
+
end
|
|
76
|
+
tree
|
|
77
|
+
end
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Each transform passes its output to the next.
|
|
81
|
+
|
|
82
|
+
## Output Types
|
|
83
|
+
|
|
84
|
+
```ruby
|
|
85
|
+
# Initial tree:
|
|
86
|
+
{:bud => {:stem => []}}
|
|
87
|
+
|
|
88
|
+
# After Spring:
|
|
89
|
+
{:bud => {:stem => [{:branch => :leaf}]}}
|
|
90
|
+
|
|
91
|
+
# After Summer:
|
|
92
|
+
{:bud => {:stem => [{:branch => [:leaf, :flower]}]}}
|
|
93
|
+
|
|
94
|
+
# After Fall:
|
|
95
|
+
{:bud => {:stem => [{:branch => []}]}}
|
|
96
|
+
|
|
97
|
+
# After Winter:
|
|
98
|
+
{:bud => {:stem => []}}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Design Decisions
|
|
102
|
+
|
|
103
|
+
### Why Separate Transform Classes?
|
|
104
|
+
|
|
105
|
+
Each transform has a single responsibility. Separation makes transformations testable and reusable.
|
|
106
|
+
|
|
107
|
+
### Why sequence vs subtree?
|
|
108
|
+
|
|
109
|
+
`sequence` is for homogeneous arrays; `subtree` handles arbitrary nesting. Different patterns for different structures.
|
|
110
|
+
|
|
111
|
+
### Why Apply in Sequence?
|
|
112
|
+
|
|
113
|
+
Pipelining transforms creates complex behavior from simple pieces. Each step is easy to understand and debug.
|
|
114
|
+
|
|
115
|
+
### Ruby-Only Feature
|
|
116
|
+
|
|
117
|
+
Transform patterns are Parslet's Ruby DSL for AST manipulation. Rust uses pattern matching and visitor patterns instead.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "seasons",
|
|
3
|
+
"title": "Transform Patterns",
|
|
4
|
+
"description": "Demonstrate Parslet transform DSL patterns through seasonal tree transformations.",
|
|
5
|
+
"category": "conceptual",
|
|
6
|
+
"tags": ["transform", "pattern-matching", "parslet", "ast"],
|
|
7
|
+
"difficulty": "intermediate",
|
|
8
|
+
"concepts": ["transform", "pattern matching", "sequence", "subtree", "AST transformation"],
|
|
9
|
+
|
|
10
|
+
"motivation": {
|
|
11
|
+
"why": "Transforms convert parse trees to domain objects using pattern matching. This example shows how multiple transforms can be composed to create complex behavior from simple pieces.",
|
|
12
|
+
"useCases": [
|
|
13
|
+
"AST transformation",
|
|
14
|
+
"Code generation",
|
|
15
|
+
"Data normalization",
|
|
16
|
+
"Multi-stage processing"
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
"inputFormat": {
|
|
21
|
+
"description": "Tree structure representing a plant through seasons.",
|
|
22
|
+
"examples": [
|
|
23
|
+
{ "input": "{bud: {stem: []}}", "description": "Initial tree state", "valid": true }
|
|
24
|
+
]
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
"outputFormat": {
|
|
28
|
+
"description": "Transformed tree after seasonal transformations.",
|
|
29
|
+
"structure": {
|
|
30
|
+
"season": { "description": "The season transform being applied" },
|
|
31
|
+
"tree": { "description": "The resulting tree structure" }
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
"rubyOnly": true,
|
|
36
|
+
"parsletCompatible": true,
|
|
37
|
+
"implementations": {
|
|
38
|
+
"ruby": { "basic": "basic.rb" }
|
|
39
|
+
}
|
|
40
|
+
}
|