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.

Files changed (336) hide show
  1. checksums.yaml +7 -0
  2. data/HISTORY.txt +25 -0
  3. data/LICENSE +23 -0
  4. data/README.adoc +643 -0
  5. data/Rakefile +189 -0
  6. data/example/balanced-parens/basic.rb +42 -0
  7. data/example/balanced-parens/basic.rb.md +86 -0
  8. data/example/balanced-parens/parens.rb +42 -0
  9. data/example/balanced-parens/ruby_transform.rb +162 -0
  10. data/example/big.erb +73 -0
  11. data/example/boolean-algebra/basic.rb +70 -0
  12. data/example/boolean-algebra/basic.rb.md +108 -0
  13. data/example/boolean-algebra/ruby_transform.rb +263 -0
  14. data/example/calculator/basic.rb +153 -0
  15. data/example/calculator/basic.rb.md +120 -0
  16. data/example/calculator/pattern.rb +153 -0
  17. data/example/calculator/ruby_transform.rb +156 -0
  18. data/example/calculator/ruby_transform.rb.md +32 -0
  19. data/example/calculator/serialized.rb +257 -0
  20. data/example/calculator/serialized.rb.md +32 -0
  21. data/example/calculator/transform.rb +153 -0
  22. data/example/calculator/zero_copy.rb +269 -0
  23. data/example/calculator/zero_copy.rb.md +36 -0
  24. data/example/capture/basic.rb +49 -0
  25. data/example/capture/basic.rb.md +106 -0
  26. data/example/capture/example.json +39 -0
  27. data/example/comments/basic.rb +35 -0
  28. data/example/comments/basic.rb.md +110 -0
  29. data/example/csv/ruby_transform.rb +148 -0
  30. data/example/csv/ruby_transform.rb.md +131 -0
  31. data/example/csv/serialized.rb +201 -0
  32. data/example/csv/serialized.rb.md +31 -0
  33. data/example/csv/zero_copy.rb +276 -0
  34. data/example/csv/zero_copy.rb.md +36 -0
  35. data/example/custom_atoms/indent_atom.rb +79 -0
  36. data/example/deepest-errors/basic.rb +131 -0
  37. data/example/deepest-errors/basic.rb.md +152 -0
  38. data/example/documentation/basic.rb +18 -0
  39. data/example/documentation/basic.rb.md +97 -0
  40. data/example/email/basic.rb +55 -0
  41. data/example/email/basic.rb.md +102 -0
  42. data/example/email/ruby_transform.rb +106 -0
  43. data/example/empty/basic.rb +13 -0
  44. data/example/empty/basic.rb.md +73 -0
  45. data/example/empty/example.json +38 -0
  46. data/example/erb/basic.rb +47 -0
  47. data/example/erb/basic.rb.md +103 -0
  48. data/example/erb/optimized.rb +42 -0
  49. data/example/error-reporting/basic.rb +132 -0
  50. data/example/error-reporting/basic.rb.md +122 -0
  51. data/example/expression-evaluator/basic.rb +284 -0
  52. data/example/expression-evaluator/basic.rb.md +138 -0
  53. data/example/ini/basic.rb +154 -0
  54. data/example/ini/basic.rb.md +129 -0
  55. data/example/ini/ruby_transform.rb +154 -0
  56. data/example/ip-address/basic.rb +125 -0
  57. data/example/ip-address/basic.rb.md +139 -0
  58. data/example/iso-6709/basic.rb +231 -0
  59. data/example/iso-6709/basic.rb.md +143 -0
  60. data/example/iso-8601/basic.rb +275 -0
  61. data/example/iso-8601/basic.rb.md +149 -0
  62. data/example/json/basic.rb +128 -0
  63. data/example/json/basic.rb.md +121 -0
  64. data/example/json/pattern.rb +128 -0
  65. data/example/json/ruby_transform.rb +200 -0
  66. data/example/json/ruby_transform.rb.md +32 -0
  67. data/example/json/serialized.rb +233 -0
  68. data/example/json/serialized.rb.md +31 -0
  69. data/example/json/transform.rb +128 -0
  70. data/example/json/zero_copy.rb +316 -0
  71. data/example/json/zero_copy.rb.md +36 -0
  72. data/example/local/basic.rb +34 -0
  73. data/example/local/basic.rb.md +91 -0
  74. data/example/local/example.json +38 -0
  75. data/example/markdown/basic.rb +287 -0
  76. data/example/markdown/basic.rb.md +160 -0
  77. data/example/markup/basic.rb +173 -0
  78. data/example/markup/basic.rb.md +118 -0
  79. data/example/mathn/basic.rb +47 -0
  80. data/example/mathn/basic.rb.md +96 -0
  81. data/example/mathn/example.json +39 -0
  82. data/example/minilisp/basic.rb +94 -0
  83. data/example/minilisp/basic.rb.md +133 -0
  84. data/example/modularity/basic.rb +47 -0
  85. data/example/modularity/basic.rb.md +152 -0
  86. data/example/nested-errors/basic.rb +132 -0
  87. data/example/nested-errors/basic.rb.md +157 -0
  88. data/example/output/boolean_algebra.out +4 -0
  89. data/example/output/calc.out +1 -0
  90. data/example/output/capture.out +3 -0
  91. data/example/output/comments.out +8 -0
  92. data/example/output/deepest_errors.out +54 -0
  93. data/example/output/documentation.err +4 -0
  94. data/example/output/documentation.out +1 -0
  95. data/example/output/email_parser.out +2 -0
  96. data/example/output/empty.err +1 -0
  97. data/example/output/erb.out +7 -0
  98. data/example/output/ignore.out +1 -0
  99. data/example/output/ignore_whitespace.out +1 -0
  100. data/example/output/ip_address.out +9 -0
  101. data/example/output/json.out +5 -0
  102. data/example/output/local.out +3 -0
  103. data/example/output/mathn.out +4 -0
  104. data/example/output/minilisp.out +5 -0
  105. data/example/output/modularity.out +0 -0
  106. data/example/output/nested_errors.out +54 -0
  107. data/example/output/optimized_erb.out +1 -0
  108. data/example/output/parens.out +8 -0
  109. data/example/output/prec_calc.out +5 -0
  110. data/example/output/readme.out +1 -0
  111. data/example/output/scopes.out +1 -0
  112. data/example/output/seasons.out +28 -0
  113. data/example/output/sentence.out +1 -0
  114. data/example/output/simple_xml.out +2 -0
  115. data/example/output/string_parser.out +3 -0
  116. data/example/prec-calc/basic.rb +71 -0
  117. data/example/prec-calc/basic.rb.md +114 -0
  118. data/example/readme/basic.rb +30 -0
  119. data/example/readme/basic.rb.md +80 -0
  120. data/example/scopes/basic.rb +15 -0
  121. data/example/scopes/basic.rb.md +73 -0
  122. data/example/scopes/example.json +38 -0
  123. data/example/seasons/basic.rb +46 -0
  124. data/example/seasons/basic.rb.md +117 -0
  125. data/example/seasons/example.json +40 -0
  126. data/example/sentence/basic.rb +36 -0
  127. data/example/sentence/basic.rb.md +81 -0
  128. data/example/sexp/ruby_transform.rb +180 -0
  129. data/example/sexp/ruby_transform.rb.md +143 -0
  130. data/example/simple-xml/basic.rb +54 -0
  131. data/example/simple-xml/basic.rb.md +125 -0
  132. data/example/simple.lit +3 -0
  133. data/example/string-literal/basic.rb +77 -0
  134. data/example/string-literal/basic.rb.md +128 -0
  135. data/example/test.lit +4 -0
  136. data/example/toml/basic.rb +226 -0
  137. data/example/toml/basic.rb.md +173 -0
  138. data/example/url/basic.rb +219 -0
  139. data/example/url/basic.rb.md +142 -0
  140. data/example/url/ruby_transform.rb +219 -0
  141. data/example/yaml/basic.rb +216 -0
  142. data/example/yaml/basic.rb.md +148 -0
  143. data/ext/parsanol_native/extconf.rb +4 -0
  144. data/lib/parsanol/accelerator/application.rb +62 -0
  145. data/lib/parsanol/accelerator/engine.rb +112 -0
  146. data/lib/parsanol/accelerator.rb +162 -0
  147. data/lib/parsanol/ast_visitor.rb +122 -0
  148. data/lib/parsanol/atoms/alternative.rb +97 -0
  149. data/lib/parsanol/atoms/base.rb +214 -0
  150. data/lib/parsanol/atoms/can_flatten.rb +192 -0
  151. data/lib/parsanol/atoms/capture.rb +41 -0
  152. data/lib/parsanol/atoms/context.rb +351 -0
  153. data/lib/parsanol/atoms/context_optimized.rb +42 -0
  154. data/lib/parsanol/atoms/custom.rb +110 -0
  155. data/lib/parsanol/atoms/cut.rb +62 -0
  156. data/lib/parsanol/atoms/dsl.rb +130 -0
  157. data/lib/parsanol/atoms/dynamic.rb +33 -0
  158. data/lib/parsanol/atoms/entity.rb +55 -0
  159. data/lib/parsanol/atoms/ignored.rb +28 -0
  160. data/lib/parsanol/atoms/infix.rb +121 -0
  161. data/lib/parsanol/atoms/lookahead.rb +64 -0
  162. data/lib/parsanol/atoms/named.rb +50 -0
  163. data/lib/parsanol/atoms/re.rb +61 -0
  164. data/lib/parsanol/atoms/repetition.rb +241 -0
  165. data/lib/parsanol/atoms/scope.rb +28 -0
  166. data/lib/parsanol/atoms/sequence.rb +157 -0
  167. data/lib/parsanol/atoms/str.rb +90 -0
  168. data/lib/parsanol/atoms/visitor.rb +91 -0
  169. data/lib/parsanol/atoms.rb +36 -0
  170. data/lib/parsanol/buffer.rb +130 -0
  171. data/lib/parsanol/builder_callbacks.rb +353 -0
  172. data/lib/parsanol/cause.rb +101 -0
  173. data/lib/parsanol/context.rb +23 -0
  174. data/lib/parsanol/convenience.rb +35 -0
  175. data/lib/parsanol/edit_tracker.rb +107 -0
  176. data/lib/parsanol/error_reporter/contextual.rb +122 -0
  177. data/lib/parsanol/error_reporter/deepest.rb +106 -0
  178. data/lib/parsanol/error_reporter/tree.rb +68 -0
  179. data/lib/parsanol/error_reporter.rb +98 -0
  180. data/lib/parsanol/export.rb +163 -0
  181. data/lib/parsanol/expression/treetop.rb +94 -0
  182. data/lib/parsanol/expression.rb +51 -0
  183. data/lib/parsanol/fast_mode.rb +145 -0
  184. data/lib/parsanol/first_set.rb +75 -0
  185. data/lib/parsanol/grammar_builder.rb +177 -0
  186. data/lib/parsanol/graphviz.rb +97 -0
  187. data/lib/parsanol/incremental_parser.rb +179 -0
  188. data/lib/parsanol/interval_tree.rb +215 -0
  189. data/lib/parsanol/lazy_result.rb +178 -0
  190. data/lib/parsanol/lexer.rb +146 -0
  191. data/lib/parsanol/native/parser.rb +630 -0
  192. data/lib/parsanol/native/serializer.rb +245 -0
  193. data/lib/parsanol/native/transformer.rb +438 -0
  194. data/lib/parsanol/native/types.rb +41 -0
  195. data/lib/parsanol/native.rb +217 -0
  196. data/lib/parsanol/optimizer.rb +86 -0
  197. data/lib/parsanol/optimizers/choice_optimizer.rb +78 -0
  198. data/lib/parsanol/optimizers/cut_inserter.rb +175 -0
  199. data/lib/parsanol/optimizers/lookahead_optimizer.rb +58 -0
  200. data/lib/parsanol/optimizers/quantifier_optimizer.rb +62 -0
  201. data/lib/parsanol/optimizers/sequence_optimizer.rb +97 -0
  202. data/lib/parsanol/options/ruby_transform.rb +109 -0
  203. data/lib/parsanol/options/serialized.rb +94 -0
  204. data/lib/parsanol/options/zero_copy.rb +130 -0
  205. data/lib/parsanol/options.rb +20 -0
  206. data/lib/parsanol/parallel.rb +133 -0
  207. data/lib/parsanol/parsanol_native.bundle +0 -0
  208. data/lib/parsanol/parser.rb +151 -0
  209. data/lib/parsanol/parslet.rb +148 -0
  210. data/lib/parsanol/parslet_native.bundle +0 -0
  211. data/lib/parsanol/pattern/binding.rb +49 -0
  212. data/lib/parsanol/pattern.rb +115 -0
  213. data/lib/parsanol/pool.rb +220 -0
  214. data/lib/parsanol/pools/array_pool.rb +75 -0
  215. data/lib/parsanol/pools/buffer_pool.rb +173 -0
  216. data/lib/parsanol/pools/position_pool.rb +92 -0
  217. data/lib/parsanol/pools/slice_pool.rb +64 -0
  218. data/lib/parsanol/position.rb +89 -0
  219. data/lib/parsanol/result.rb +44 -0
  220. data/lib/parsanol/result_builder.rb +208 -0
  221. data/lib/parsanol/result_stream.rb +262 -0
  222. data/lib/parsanol/rig/rspec.rb +52 -0
  223. data/lib/parsanol/rope.rb +78 -0
  224. data/lib/parsanol/scope.rb +42 -0
  225. data/lib/parsanol/slice.rb +172 -0
  226. data/lib/parsanol/source/line_cache.rb +99 -0
  227. data/lib/parsanol/source.rb +171 -0
  228. data/lib/parsanol/source_location.rb +164 -0
  229. data/lib/parsanol/streaming_parser.rb +124 -0
  230. data/lib/parsanol/string_view.rb +192 -0
  231. data/lib/parsanol/transform.rb +267 -0
  232. data/lib/parsanol/version.rb +5 -0
  233. data/lib/parsanol/wasm/README.md +80 -0
  234. data/lib/parsanol/wasm/package.json +51 -0
  235. data/lib/parsanol/wasm/parsanol.js +252 -0
  236. data/lib/parsanol/wasm/parslet.d.ts +129 -0
  237. data/lib/parsanol/wasm_parser.rb +239 -0
  238. data/lib/parsanol.rb +408 -0
  239. data/parsanol-ruby.gemspec +56 -0
  240. data/spec/acceptance/examples_spec.rb +96 -0
  241. data/spec/acceptance/infix_parser_spec.rb +145 -0
  242. data/spec/acceptance/mixing_parsers_spec.rb +74 -0
  243. data/spec/acceptance/regression_spec.rb +329 -0
  244. data/spec/acceptance/repetition_and_maybe_spec.rb +44 -0
  245. data/spec/acceptance/unconsumed_input_spec.rb +21 -0
  246. data/spec/benchmark/comparative/runner_spec.rb +105 -0
  247. data/spec/integration/array_pooling_spec.rb +193 -0
  248. data/spec/integration/buffer_allocation_spec.rb +324 -0
  249. data/spec/integration/position_pooling_spec.rb +184 -0
  250. data/spec/integration/result_builder_spec.rb +282 -0
  251. data/spec/integration/rope_stringview_integration_spec.rb +188 -0
  252. data/spec/integration/slice_pooling_spec.rb +63 -0
  253. data/spec/integration/string_view_integration_spec.rb +125 -0
  254. data/spec/lexer_spec.rb +231 -0
  255. data/spec/parsanol/atom_results_spec.rb +39 -0
  256. data/spec/parsanol/atoms/alternative_spec.rb +26 -0
  257. data/spec/parsanol/atoms/base_spec.rb +127 -0
  258. data/spec/parsanol/atoms/capture_spec.rb +21 -0
  259. data/spec/parsanol/atoms/combinations_spec.rb +5 -0
  260. data/spec/parsanol/atoms/custom_spec.rb +79 -0
  261. data/spec/parsanol/atoms/dsl_spec.rb +7 -0
  262. data/spec/parsanol/atoms/entity_spec.rb +77 -0
  263. data/spec/parsanol/atoms/ignored_spec.rb +15 -0
  264. data/spec/parsanol/atoms/infix_spec.rb +5 -0
  265. data/spec/parsanol/atoms/lookahead_spec.rb +22 -0
  266. data/spec/parsanol/atoms/named_spec.rb +4 -0
  267. data/spec/parsanol/atoms/re_spec.rb +14 -0
  268. data/spec/parsanol/atoms/repetition_spec.rb +24 -0
  269. data/spec/parsanol/atoms/scope_spec.rb +26 -0
  270. data/spec/parsanol/atoms/sequence_spec.rb +28 -0
  271. data/spec/parsanol/atoms/str_spec.rb +15 -0
  272. data/spec/parsanol/atoms/visitor_spec.rb +101 -0
  273. data/spec/parsanol/atoms_spec.rb +488 -0
  274. data/spec/parsanol/auto_optimize_spec.rb +334 -0
  275. data/spec/parsanol/buffer_spec.rb +219 -0
  276. data/spec/parsanol/builder_callbacks_spec.rb +377 -0
  277. data/spec/parsanol/choice_optimizer_spec.rb +231 -0
  278. data/spec/parsanol/convenience_spec.rb +54 -0
  279. data/spec/parsanol/cut_inserter_spec.rb +248 -0
  280. data/spec/parsanol/cut_spec.rb +66 -0
  281. data/spec/parsanol/edit_tracker_spec.rb +218 -0
  282. data/spec/parsanol/error_reporter/contextual_spec.rb +122 -0
  283. data/spec/parsanol/error_reporter/deepest_spec.rb +82 -0
  284. data/spec/parsanol/error_reporter/tree_spec.rb +7 -0
  285. data/spec/parsanol/export_spec.rb +67 -0
  286. data/spec/parsanol/expression/treetop_spec.rb +75 -0
  287. data/spec/parsanol/first_set_spec.rb +298 -0
  288. data/spec/parsanol/interval_tree_spec.rb +205 -0
  289. data/spec/parsanol/lazy_result_spec.rb +288 -0
  290. data/spec/parsanol/lookahead_optimizer_spec.rb +252 -0
  291. data/spec/parsanol/minilisp.citrus +29 -0
  292. data/spec/parsanol/minilisp.tt +29 -0
  293. data/spec/parsanol/optimizer_spec.rb +459 -0
  294. data/spec/parsanol/options/parslet_compat_spec.rb +166 -0
  295. data/spec/parsanol/options/ruby_transform_spec.rb +70 -0
  296. data/spec/parsanol/options/serialized_spec.rb +69 -0
  297. data/spec/parsanol/options/zero_copy_spec.rb +230 -0
  298. data/spec/parsanol/parser_spec.rb +36 -0
  299. data/spec/parsanol/parslet_spec.rb +38 -0
  300. data/spec/parsanol/pattern_spec.rb +272 -0
  301. data/spec/parsanol/pool_spec.rb +392 -0
  302. data/spec/parsanol/pools/array_pool_spec.rb +356 -0
  303. data/spec/parsanol/pools/buffer_pool_spec.rb +365 -0
  304. data/spec/parsanol/pools/position_pool_spec.rb +118 -0
  305. data/spec/parsanol/pools/slice_pool_spec.rb +262 -0
  306. data/spec/parsanol/position_spec.rb +14 -0
  307. data/spec/parsanol/result_builder_spec.rb +391 -0
  308. data/spec/parsanol/rig/rspec_spec.rb +54 -0
  309. data/spec/parsanol/rope_spec.rb +207 -0
  310. data/spec/parsanol/scope_spec.rb +45 -0
  311. data/spec/parsanol/slice_spec.rb +249 -0
  312. data/spec/parsanol/source/line_cache_spec.rb +74 -0
  313. data/spec/parsanol/source_spec.rb +207 -0
  314. data/spec/parsanol/string_view_spec.rb +345 -0
  315. data/spec/parsanol/transform/context_spec.rb +56 -0
  316. data/spec/parsanol/transform_spec.rb +183 -0
  317. data/spec/parsanol/tree_memoization_spec.rb +149 -0
  318. data/spec/parslet_compatibility/expressir_edge_cases_spec.rb +153 -0
  319. data/spec/parslet_compatibility/minimal_reproduction.rb +199 -0
  320. data/spec/parslet_compatibility_spec.rb +399 -0
  321. data/spec/parslet_imported/atom_spec.rb +93 -0
  322. data/spec/parslet_imported/combinator_spec.rb +161 -0
  323. data/spec/parslet_imported/spec_helper.rb +73 -0
  324. data/spec/performance/batch_parsing_benchmark.rb +129 -0
  325. data/spec/performance/complete_optimization_summary.rb +143 -0
  326. data/spec/performance/grammar_caching_analysis.rb +121 -0
  327. data/spec/performance/grammar_caching_benchmark.rb +80 -0
  328. data/spec/performance/native_benchmark_spec.rb +230 -0
  329. data/spec/performance/phase5_benchmark.rb +144 -0
  330. data/spec/performance/profiling_benchmark.rb +131 -0
  331. data/spec/performance/ruby_improvements_benchmark.rb +171 -0
  332. data/spec/performance_spec.rb +374 -0
  333. data/spec/spec_helper.rb +79 -0
  334. data/spec/support/opal.rb +8 -0
  335. data/spec/support/opal.rb.erb +14 -0
  336. metadata +485 -0
data/Rakefile ADDED
@@ -0,0 +1,189 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+ require 'rdoc/task'
6
+ require 'rubygems/package_task'
7
+
8
+ begin
9
+ require 'opal/rspec/rake_task'
10
+ rescue LoadError, NoMethodError
11
+ # Opal not available or incompatible with current Ruby version
12
+ end
13
+
14
+ # Native extension compilation using rake-compiler
15
+ begin
16
+ require 'rake/extensiontask'
17
+ Rake::ExtensionTask.new('parsanol_native') do |ext|
18
+ ext.lib_dir = 'lib/parsanol'
19
+ end
20
+ rescue LoadError
21
+ # rake-compiler not available
22
+ end
23
+
24
+ desc 'Run all tests'
25
+ RSpec::Core::RakeTask.new(:spec)
26
+
27
+ namespace :spec do
28
+ desc 'Run unit tests only'
29
+ RSpec::Core::RakeTask.new(:unit) do |task|
30
+ task.pattern = 'spec/parsanol/**/*_spec.rb'
31
+ end
32
+
33
+ if defined?(Opal::RSpec::RakeTask)
34
+ desc 'Run Opal (JavaScript) tests'
35
+ Opal::RSpec::RakeTask.new(:opal) do |task|
36
+ task.append_path 'lib'
37
+ end
38
+ end
39
+ end
40
+
41
+ RDoc::Task.new do |rdoc|
42
+ rdoc.rdoc_dir = 'rdoc'
43
+ rdoc.title = 'Parsanol'
44
+ rdoc.options << '--line-numbers'
45
+ rdoc.rdoc_files.include('README.adoc')
46
+ rdoc.rdoc_files.include('lib/**/*.rb')
47
+ end
48
+
49
+ desc 'Print LOC statistics'
50
+ task :stat do
51
+ %w[lib spec example].each do |dir|
52
+ next unless Dir.exist?(dir)
53
+
54
+ loc = `find #{dir} -name "*.rb" | xargs wc -l | grep 'total'`.split.first.to_i
55
+ printf("%20s %d\n", dir, loc)
56
+ end
57
+ end
58
+
59
+ # ===== Native Gem Building =====
60
+ # Platform definitions for precompiled gems
61
+ PLATFORMS = [
62
+ ['x64-mingw32', 'x86_64-w64-mingw32'],
63
+ ['x64-mingw-ucrt', 'x86_64-w64-mingw32'],
64
+ ['arm64-mingw-ucrt', 'aarch64-w64-mingw32'],
65
+ ['x86_64-linux', 'x86_64-linux-gnu'],
66
+ ['x86_64-linux-gnu', 'x86_64-linux-gnu'],
67
+ ['x86_64-linux-musl', 'x86_64-linux-musl'],
68
+ ['aarch64-linux', 'aarch64-linux-gnu'],
69
+ ['aarch64-linux-gnu', 'aarch64-linux-gnu'],
70
+ ['aarch64-linux-musl', 'aarch64-linux-musl'],
71
+ ['x86_64-darwin', 'x86_64-apple-darwin'],
72
+ ['arm64-darwin', 'arm64-apple-darwin'],
73
+ ]
74
+
75
+ namespace :gem do
76
+ desc 'Build install-compilation gem (platform: any)'
77
+ task 'native:any' do
78
+ sh 'rake platform:any gem'
79
+ end
80
+
81
+ desc 'Define the gem task to build on any platform (compile on install)'
82
+ task 'platform:any' do
83
+ spec = Gem::Specification.load('parsanol-ruby.gemspec').dup
84
+ task = Gem::PackageTask.new(spec)
85
+ task.define
86
+ end
87
+
88
+ # Generate tasks for each platform
89
+ PLATFORMS.each do |platform, _host|
90
+ desc "Build pre-compiled gem for the #{platform} platform"
91
+ task "native:#{platform}" do
92
+ sh "rake compile platform:#{platform} gem"
93
+ end
94
+
95
+ desc "Define the gem task to build on the #{platform} platform (binary gem)"
96
+ task "platform:#{platform}" do
97
+ spec = Gem::Specification.load('parsanol-ruby.gemspec').dup
98
+ spec.platform = Gem::Platform.new(platform)
99
+
100
+ # Include pre-compiled native extension
101
+ spec.files += Dir.glob('lib/parsanol/*.{so,dylib,dll,bundle}')
102
+
103
+ # Remove extension build for binary gems (already compiled)
104
+ spec.extensions = []
105
+
106
+ # Remove build-time dependencies
107
+ spec.dependencies.reject! { |d| d.name == 'rb_sys' }
108
+ spec.dependencies.reject! { |d| d.name == 'rake-compiler' }
109
+
110
+ task = Gem::PackageTask.new(spec)
111
+ task.define
112
+ end
113
+ end
114
+
115
+ desc 'Build all platform gems (requires cross-compilation setup)'
116
+ task :native do
117
+ puts 'Building all platform gems...'
118
+ puts 'Run individual tasks like: rake gem:native:x86_64-linux'
119
+ puts 'Or use the CI workflow for cross-compilation.'
120
+ end
121
+ end
122
+
123
+ namespace :benchmark do
124
+ desc 'Run comprehensive benchmark suite'
125
+ task :all do
126
+ ruby 'benchmark/benchmark_suite.rb'
127
+ end
128
+
129
+ desc 'Run example-focused benchmarks'
130
+ task :examples do
131
+ ruby 'benchmark/example_benchmarks.rb'
132
+ end
133
+
134
+ desc 'Run benchmarks and export results to JSON/YAML'
135
+ task :export do
136
+ ruby 'benchmark/benchmark_runner.rb'
137
+ end
138
+
139
+ desc 'Run quick benchmark (examples only)'
140
+ task quick: :examples
141
+ end
142
+
143
+ # Load comparative benchmark tasks
144
+ Dir.glob('benchmark/tasks/*.rake').each { |r| load r }
145
+
146
+ desc 'Run quick benchmarks'
147
+ task benchmark: 'benchmark:quick'
148
+
149
+ # ===== Parslet Compatibility Tests =====
150
+ namespace :compat do
151
+ desc 'Run imported Parslet tests with original Parslet (baseline)'
152
+ task :parslet do
153
+ ENV['PARSANOL_BACKEND'] = 'parslet'
154
+ sh 'bundle exec rspec spec/parslet_imported/ --format documentation'
155
+ end
156
+
157
+ desc 'Run imported Parslet tests with Parsanol compatibility layer'
158
+ task :parsanol do
159
+ ENV['PARSANOL_BACKEND'] = 'parsanol'
160
+ sh 'bundle exec rspec spec/parslet_imported/ --format documentation'
161
+ end
162
+
163
+ desc 'Run both and save results for comparison'
164
+ task :compare do
165
+ require 'fileutils'
166
+
167
+ results_dir = 'tmp/compat_results'
168
+ FileUtils.mkdir_p(results_dir)
169
+
170
+ puts '=== Running with original Parslet ==='
171
+ ENV['PARSANOL_BACKEND'] = 'parslet'
172
+ sh "bundle exec rspec spec/parslet_imported/ --format documentation > #{results_dir}/parslet.txt 2>&1"
173
+
174
+ puts "\n=== Running with Parsanol::Parslet ==="
175
+ ENV['PARSANOL_BACKEND'] = 'parsanol'
176
+ sh "bundle exec rspec spec/parslet_imported/ --format documentation > #{results_dir}/parsanol.txt 2>&1"
177
+
178
+ puts "\n=== Comparing results ==="
179
+ puts "Results saved to:"
180
+ puts " - #{results_dir}/parslet.txt"
181
+ puts " - #{results_dir}/parsanol.txt"
182
+ puts "\nTo compare: diff #{results_dir}/parslet.txt #{results_dir}/parsanol.txt"
183
+ end
184
+
185
+ desc 'Run imported Parslet tests (default: with Parsanol)'
186
+ task run: :parsanol
187
+ end
188
+
189
+ task default: :spec
@@ -0,0 +1,42 @@
1
+ # A small example that demonstrates the power of tree pattern matching. Also
2
+ # uses '.as(:name)' to construct a tree that can reliably be matched
3
+ # afterwards.
4
+
5
+ $:.unshift File.dirname(__FILE__) + "/../lib"
6
+
7
+ require 'pp'
8
+ require 'parsanol/parslet'
9
+
10
+ module LISP # as in 'lots of insipid and stupid parenthesis'
11
+ class Parser < Parsanol::Parser
12
+ rule(:balanced) {
13
+ str('(').as(:l) >> balanced.maybe.as(:m) >> str(')').as(:r)
14
+ }
15
+
16
+ root(:balanced)
17
+ end
18
+
19
+ class Transform < Parsanol::Transform
20
+ rule(:l => '(', :m => simple(:x), :r => ')') {
21
+ # innermost :m will contain nil
22
+ x.nil? ? 1 : x+1
23
+ }
24
+ end
25
+ end
26
+
27
+ parser = LISP::Parser.new
28
+ transform = LISP::Transform.new
29
+ %w!
30
+ ()
31
+ (())
32
+ ((((()))))
33
+ ((())
34
+ !.each do |pexp|
35
+ begin
36
+ result = parser.parse(pexp)
37
+ puts "#{"%20s"%pexp}: #{result.inspect} (#{transform.apply(result)} parens)"
38
+ rescue Parsanol::ParseFailed => m
39
+ puts "#{"%20s"%pexp}: #{m}"
40
+ end
41
+ puts
42
+ end
@@ -0,0 +1,86 @@
1
+ # Balanced Parentheses - Ruby Implementation
2
+
3
+ ## How to Run
4
+
5
+ ```bash
6
+ cd parsanol-ruby/example/balanced-parens
7
+ ruby basic.rb
8
+ ```
9
+
10
+ ## Code Walkthrough
11
+
12
+ ### Recursive Balanced Rule
13
+
14
+ Parentheses are defined recursively:
15
+
16
+ ```ruby
17
+ rule(:balanced) {
18
+ str('(').as(:l) >> balanced.maybe.as(:m) >> str(')').as(:r)
19
+ }
20
+ ```
21
+
22
+ Each level contains opening paren, optional nested content, and closing paren.
23
+
24
+ ### Labeled Components
25
+
26
+ Each part is labeled for pattern matching:
27
+
28
+ ```ruby
29
+ str('(').as(:l) # Opening paren labeled :l
30
+ balanced.maybe.as(:m) # Middle content labeled :m
31
+ str(')').as(:r) # Closing paren labeled :r
32
+ ```
33
+
34
+ Labels enable precise pattern matching in transforms.
35
+
36
+ ### Transform for Counting
37
+
38
+ The transform counts nesting depth:
39
+
40
+ ```ruby
41
+ class Transform < Parsanol::Transform
42
+ rule(:l => '(', :m => simple(:x), :r => ')') {
43
+ x.nil? ? 1 : x+1
44
+ }
45
+ end
46
+ ```
47
+
48
+ Pattern matches the structure; nil indicates innermost level.
49
+
50
+ ### Recursive Tree Structure
51
+
52
+ Deep nesting creates nested parse trees:
53
+
54
+ ```ruby
55
+ # Input: ((()))
56
+ # Tree: {:l=>"(", :m=>{:l=>"(", :m=>{:l=>"(", :m=>nil, :r=>")"}, :r=>")"}, :r=>")"}
57
+ ```
58
+
59
+ The innermost `:m` is nil when nothing is inside.
60
+
61
+ ## Output Types
62
+
63
+ ```ruby
64
+ # Parse tree for "(())":
65
+ {:l=>"(", :m=>{:l=>"(", :m=>nil, :r=>")"}, :r=>")"}
66
+
67
+ # After transform:
68
+ 2 # Depth of nesting
69
+
70
+ # Invalid input:
71
+ # Raises Parsanol::ParseFailed
72
+ ```
73
+
74
+ ## Design Decisions
75
+
76
+ ### Why Use Labels for Literals?
77
+
78
+ Labeling constant values (`:l => '('`) allows pattern matching on structure, not just values.
79
+
80
+ ### Why Maybe for Middle Content?
81
+
82
+ The innermost parentheses have nothing inside. `.maybe` handles this base case.
83
+
84
+ ### Why Count Depth in Transform?
85
+
86
+ Counting demonstrates tree traversal. Real applications might validate matching pairs or build AST structures.
@@ -0,0 +1,42 @@
1
+ # A small example that demonstrates the power of tree pattern matching. Also
2
+ # uses '.as(:name)' to construct a tree that can reliably be matched
3
+ # afterwards.
4
+
5
+ $:.unshift File.dirname(__FILE__) + "/../lib"
6
+
7
+ require 'pp'
8
+ require 'parsanol/parslet'
9
+
10
+ module LISP # as in 'lots of insipid and stupid parenthesis'
11
+ class Parser < Parsanol::Parser
12
+ rule(:balanced) {
13
+ str('(').as(:l) >> balanced.maybe.as(:m) >> str(')').as(:r)
14
+ }
15
+
16
+ root(:balanced)
17
+ end
18
+
19
+ class Transform < Parsanol::Transform
20
+ rule(:l => '(', :m => simple(:x), :r => ')') {
21
+ # innermost :m will contain nil
22
+ x.nil? ? 1 : x+1
23
+ }
24
+ end
25
+ end
26
+
27
+ parser = LISP::Parser.new
28
+ transform = LISP::Transform.new
29
+ %w!
30
+ ()
31
+ (())
32
+ ((((()))))
33
+ ((())
34
+ !.each do |pexp|
35
+ begin
36
+ result = parser.parse(pexp)
37
+ puts "#{"%20s"%pexp}: #{result.inspect} (#{transform.apply(result)} parens)"
38
+ rescue Parsanol::ParseFailed => m
39
+ puts "#{"%20s"%pexp}: #{m}"
40
+ end
41
+ puts
42
+ end
@@ -0,0 +1,162 @@
1
+ # Balanced Parentheses Parser Example - RubyTransform
2
+ #
3
+ # This example demonstrates parsing balanced parentheses expressions.
4
+ # Shows recursive grammar rules and validation.
5
+ #
6
+ # Run with: ruby -Ilib example/balanced_parens_ruby_transform.rb
7
+
8
+ $:.unshift File.dirname(__FILE__) + "/../lib"
9
+
10
+ require 'parsanol'
11
+
12
+ # Step 1: Define the balanced parentheses grammar
13
+ # Using a PEG-friendly approach: parse multiple balanced groups
14
+ class BalancedParensParser < Parsanol::Parser
15
+ root :content
16
+
17
+ # Content: zero or more balanced groups
18
+ rule(:content) {
19
+ balanced.repeat
20
+ }
21
+
22
+ # Balanced: a parenthesized group that may contain more groups
23
+ rule(:balanced) {
24
+ str('(') >> content >> str(')')
25
+ }
26
+ end
27
+
28
+ # Step 2: Node classes
29
+ class ParenExpr
30
+ attr_reader :inner
31
+
32
+ def initialize(inner)
33
+ @inner = inner
34
+ end
35
+
36
+ def balanced?
37
+ true
38
+ end
39
+
40
+ def to_s
41
+ "(#{@inner})"
42
+ end
43
+
44
+ def depth
45
+ 1 + (@inner.respond_to?(:depth) ? @inner.depth : 0)
46
+ end
47
+ end
48
+
49
+ class EmptyExpr
50
+ def balanced?
51
+ true
52
+ end
53
+
54
+ def to_s
55
+ ""
56
+ end
57
+
58
+ def depth
59
+ 0
60
+ end
61
+ end
62
+
63
+ class SequenceExpr
64
+ attr_reader :exprs
65
+
66
+ def initialize(exprs)
67
+ @exprs = exprs
68
+ end
69
+
70
+ def balanced?
71
+ @exprs.all?(&:balanced?)
72
+ end
73
+
74
+ def to_s
75
+ @exprs.map(&:to_s).join
76
+ end
77
+
78
+ def depth
79
+ @exprs.map(&:depth).max || 0
80
+ end
81
+ end
82
+
83
+ # Step 3: Parse and build AST
84
+ def parse_balanced(input)
85
+ parser = BalancedParensParser.new
86
+
87
+ tree = parser.parse(input)
88
+ puts "Parse tree: #{tree.inspect}"
89
+
90
+ # Build AST from tree
91
+ ast = build_ast(tree)
92
+ puts "AST: #{ast.to_s}"
93
+ puts "Balanced: #{ast.balanced?}"
94
+ puts "Max depth: #{ast.depth}"
95
+
96
+ ast
97
+ rescue Parsanol::ParseFailed => e
98
+ puts "Parse failed: #{e.message}"
99
+ nil
100
+ rescue SystemStackError => e
101
+ puts "Stack overflow - grammar too complex for input"
102
+ nil
103
+ end
104
+
105
+ def build_ast(tree)
106
+ # Handle nil and empty
107
+ return EmptyExpr.new if tree.nil?
108
+ return EmptyExpr.new if tree.to_s.empty?
109
+
110
+ if tree.is_a?(Array)
111
+ exprs = tree.map { |t| build_ast(t) }.reject { |e| e.is_a?(EmptyExpr) }
112
+ return EmptyExpr.new if exprs.empty?
113
+ return exprs.first if exprs.length == 1
114
+ SequenceExpr.new(exprs)
115
+ elsif tree.is_a?(Hash)
116
+ if tree[:balanced]
117
+ inner = build_ast(tree[:balanced])
118
+ ParenExpr.new(inner)
119
+ elsif tree[:content]
120
+ build_ast(tree[:content])
121
+ else
122
+ EmptyExpr.new
123
+ end
124
+ else
125
+ EmptyExpr.new
126
+ end
127
+ end
128
+
129
+ # Example usage
130
+ if __FILE__ == $0
131
+ puts "=" * 60
132
+ puts "Balanced Parentheses Parser - RubyTransform"
133
+ puts "=" * 60
134
+ puts
135
+
136
+ test_cases = [
137
+ "", # Empty - balanced
138
+ "()", # Simple - balanced
139
+ "(())", # Nested - balanced
140
+ "(()())", # Multiple - balanced
141
+ "((()))", # Deeply nested - balanced
142
+ "(()())()", # Multiple groups - balanced
143
+ "(())(())", # Two groups - balanced
144
+ "(", # Unbalanced - should fail
145
+ ")", # Unbalanced - should fail
146
+ "(()", # Unbalanced - should fail
147
+ "())", # Unbalanced - should fail
148
+ "((())", # Unbalanced - should fail
149
+ ]
150
+
151
+ test_cases.each do |input|
152
+ puts "-" * 40
153
+ puts "Input: '#{input}'"
154
+ ast = parse_balanced(input)
155
+ if ast
156
+ puts "Result: #{ast.balanced? ? '✓ BALANCED' : '✗ UNBALANCED'}"
157
+ else
158
+ puts "Result: ✗ PARSE FAILED"
159
+ end
160
+ puts
161
+ end
162
+ end
data/example/big.erb ADDED
@@ -0,0 +1,73 @@
1
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
2
+ tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
3
+ quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
4
+ consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
5
+ cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
6
+ proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
7
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
8
+ tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
9
+ quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
10
+ consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
11
+ cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
12
+ proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
13
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
14
+ tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
15
+ quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
16
+ consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
17
+ cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
18
+ proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
19
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
20
+ tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
21
+ quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
22
+ consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
23
+ cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
24
+ proident, sunt in culpa qui officia deserunt mollit anim id est laborum.Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
25
+ tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
26
+ quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
27
+ consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
28
+ cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
29
+ proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
30
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
31
+ tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
32
+ quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
33
+ consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
34
+ cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
35
+ proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
36
+
37
+ <%= erb tag %>
38
+
39
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
40
+ tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
41
+ quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
42
+ consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
43
+ cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
44
+ proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
45
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
46
+ tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
47
+ quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
48
+ consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
49
+ cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
50
+ proident, sunt in culpa qui officia deserunt mollit anim id est laborum.Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
51
+ tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
52
+ quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
53
+ consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
54
+ cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
55
+ proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
56
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
57
+ tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
58
+ quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
59
+ consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
60
+ cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
61
+ proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
62
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
63
+ tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
64
+ quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
65
+ consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
66
+ cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
67
+ proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
68
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
69
+ tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
70
+ quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
71
+ consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
72
+ cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
73
+ proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
@@ -0,0 +1,70 @@
1
+ $:.unshift File.dirname(__FILE__) + "/../lib"
2
+
3
+ require "parsanol/parslet"
4
+ require "pp"
5
+
6
+ # Parses strings like "var1 and (var2 or var3)" respecting operator precedence
7
+ # and parentheses. After that transforms the parse tree into an array of
8
+ # arrays like this:
9
+ #
10
+ # [["1", "2"], ["1", "3"]]
11
+ #
12
+ # The array represents a DNF (disjunctive normal form). Elements of outer
13
+ # array are connected with "or" operator, while elements of inner arrays are
14
+ # joined with "and".
15
+ #
16
+ class MyParser < Parsanol::Parser
17
+ rule(:space) { match[" "].repeat(1) }
18
+ rule(:space?) { space.maybe }
19
+
20
+ rule(:lparen) { str("(") >> space? }
21
+ rule(:rparen) { str(")") >> space? }
22
+
23
+ rule(:and_operator) { str("and") >> space? }
24
+ rule(:or_operator) { str("or") >> space? }
25
+
26
+ rule(:var) { str("var") >> match["0-9"].repeat(1).as(:var) >> space? }
27
+
28
+ # The primary rule deals with parentheses.
29
+ rule(:primary) { lparen >> or_operation >> rparen | var }
30
+
31
+ # Note that following rules are both right-recursive.
32
+ rule(:and_operation) {
33
+ (primary.as(:left) >> and_operator >>
34
+ and_operation.as(:right)).as(:and) |
35
+ primary }
36
+
37
+ rule(:or_operation) {
38
+ (and_operation.as(:left) >> or_operator >>
39
+ or_operation.as(:right)).as(:or) |
40
+ and_operation }
41
+
42
+ # We start at the lowest precedence rule.
43
+ root(:or_operation)
44
+ end
45
+
46
+ class Transformer < Parsanol::Transform
47
+ rule(:var => simple(:var)) { [[String(var)]] }
48
+
49
+ rule(:or => { :left => subtree(:left), :right => subtree(:right) }) do
50
+ (left + right)
51
+ end
52
+
53
+ rule(:and => { :left => subtree(:left), :right => subtree(:right) }) do
54
+ res = []
55
+ left.each do |l|
56
+ right.each do |r|
57
+ res << (l + r)
58
+ end
59
+ end
60
+ res
61
+ end
62
+ end
63
+
64
+ pp tree = MyParser.new.parse("var1 and (var2 or var3)")
65
+ # {:and=>
66
+ # {:left=>{:var=>"1"@3},
67
+ # :right=>{:or=>{:left=>{:var=>"2"@13}, :right=>{:var=>"3"@21}}}}}
68
+ pp Transformer.new.apply(tree)
69
+ # [["1", "2"], ["1", "3"]]
70
+