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
@@ -0,0 +1,108 @@
1
+ # Boolean Algebra - Ruby Implementation
2
+
3
+ ## How to Run
4
+
5
+ ```bash
6
+ cd parsanol-ruby/example/boolean-algebra
7
+ ruby basic.rb
8
+ ```
9
+
10
+ ## Code Walkthrough
11
+
12
+ ### Variable Rule
13
+
14
+ Variables are `var` followed by digits:
15
+
16
+ ```ruby
17
+ rule(:var) { str("var") >> match["0-9"].repeat(1).as(:var) >> space? }
18
+ ```
19
+
20
+ The `.as(:var)` captures the numeric suffix for later use.
21
+
22
+ ### Operator Precedence
23
+
24
+ AND binds tighter than OR through rule ordering:
25
+
26
+ ```ruby
27
+ rule(:and_operation) {
28
+ (primary.as(:left) >> and_operator >> and_operation.as(:right)).as(:and) |
29
+ primary
30
+ }
31
+
32
+ rule(:or_operation) {
33
+ (and_operation.as(:left) >> or_operator >> or_operation.as(:right)).as(:or) |
34
+ and_operation
35
+ }
36
+ ```
37
+
38
+ OR calls AND first, giving AND higher precedence.
39
+
40
+ ### Parentheses Handling
41
+
42
+ Primary handles grouping:
43
+
44
+ ```ruby
45
+ rule(:primary) { lparen >> or_operation >> rparen | var }
46
+ ```
47
+
48
+ Parenthesized expressions recurse back to the top-level OR rule.
49
+
50
+ ### DNF Transform
51
+
52
+ The transform converts to Disjunctive Normal Form:
53
+
54
+ ```ruby
55
+ class Transformer < Parsanol::Transform
56
+ rule(:var => simple(:var)) { [[String(var)]] }
57
+
58
+ rule(:or => { :left => subtree(:left), :right => subtree(:right) }) do
59
+ (left + right)
60
+ end
61
+
62
+ rule(:and => { :left => subtree(:left), :right => subtree(:right) }) do
63
+ res = []
64
+ left.each do |l|
65
+ right.each do |r|
66
+ res << (l + r)
67
+ end
68
+ end
69
+ res
70
+ end
71
+ end
72
+ ```
73
+
74
+ OR concatenates alternatives; AND creates all combinations.
75
+
76
+ ### DNF Output Structure
77
+
78
+ Arrays of arrays represent the formula:
79
+
80
+ ```ruby
81
+ # var1 and (var2 or var3)
82
+ # => [["1", "2"], ["1", "3"]]
83
+ # Means: (var1 AND var2) OR (var1 AND var3)
84
+ ```
85
+
86
+ ## Output Types
87
+
88
+ ```ruby
89
+ # Parse tree for "var1 and (var2 or var3)":
90
+ {:and=>{:left=>{:var=>"1"}, :right=>{:or=>{:left=>{:var=>"2"}, :right=>{:var=>"3"}}}}}
91
+
92
+ # After transform (DNF):
93
+ [["1", "2"], ["1", "3"]]
94
+ ```
95
+
96
+ ## Design Decisions
97
+
98
+ ### Why Right Recursion?
99
+
100
+ Right recursion naturally handles left-to-right parsing while building the correct tree structure.
101
+
102
+ ### Why DNF Output?
103
+
104
+ DNF is useful for query optimization and database searches. Each inner array is a conjunction that must all be true.
105
+
106
+ ### Why subtree Instead of simple?
107
+
108
+ `subtree(:x)` matches any tree structure, allowing recursive matching of nested AND/OR operations.
@@ -0,0 +1,263 @@
1
+ # Boolean Algebra Parser Example - RubyTransform: Ruby Transform
2
+ #
3
+ # This example demonstrates parsing boolean expressions with AND/OR operators.
4
+ # Shows operator precedence, parentheses handling, and evaluation.
5
+ #
6
+ # Run with: ruby -Ilib example/boolean_algebra_ruby_transform.rb
7
+
8
+ $:.unshift File.dirname(__FILE__) + "/../lib"
9
+
10
+ require 'parsanol'
11
+
12
+ # Step 1: Define the parser grammar
13
+ class BooleanAlgebraParser < Parsanol::Parser
14
+ root :expression
15
+
16
+ rule(:expression) { or_expr }
17
+
18
+ # OR expression (lowest precedence)
19
+ rule(:or_expr) {
20
+ (and_expr.as(:left) >> or_op >> or_expr.as(:right)).as(:or) |
21
+ and_expr
22
+ }
23
+
24
+ # AND expression (higher precedence)
25
+ rule(:and_expr) {
26
+ (primary.as(:left) >> and_op >> and_expr.as(:right)).as(:and) |
27
+ primary
28
+ }
29
+
30
+ # Primary: variable or parenthesized expression
31
+ rule(:primary) {
32
+ lparen >> expression >> rparen |
33
+ variable
34
+ }
35
+
36
+ rule(:variable) { match['a-z'].repeat(1).as(:var) >> digit.repeat(1).as(:num) }
37
+ rule(:digit) { match('[0-9]') }
38
+
39
+ rule(:or_op) { spaces >> str('or') >> spaces }
40
+ rule(:and_op) { spaces >> str('and') >> spaces }
41
+
42
+ rule(:lparen) { str('(') >> spaces? }
43
+ rule(:rparen) { spaces? >> str(')') }
44
+ rule(:spaces) { match('\s').repeat(1) }
45
+ rule(:spaces?) { match('\s').repeat }
46
+ end
47
+
48
+ # Step 2: Define AST classes
49
+ class BoolExpr
50
+ def eval(bindings)
51
+ raise NotImplementedError
52
+ end
53
+ end
54
+
55
+ class VarExpr < BoolExpr
56
+ attr_reader :name
57
+
58
+ def initialize(name)
59
+ @name = name
60
+ end
61
+
62
+ def eval(bindings)
63
+ bindings[@name] || raise("Unknown variable: #{@name}")
64
+ end
65
+
66
+ def to_s = @name
67
+ end
68
+
69
+ class AndExpr < BoolExpr
70
+ attr_reader :left, :right
71
+
72
+ def initialize(left, right)
73
+ @left = left
74
+ @right = right
75
+ end
76
+
77
+ def eval(bindings)
78
+ @left.eval(bindings) && @right.eval(bindings)
79
+ end
80
+
81
+ def to_s = "(#{@left} AND #{@right})"
82
+ end
83
+
84
+ class OrExpr < BoolExpr
85
+ attr_reader :left, :right
86
+
87
+ def initialize(left, right)
88
+ @left = left
89
+ @right = right
90
+ end
91
+
92
+ def eval(bindings)
93
+ @left.eval(bindings) || @right.eval(bindings)
94
+ end
95
+
96
+ def to_s = "(#{@left} OR #{@right})"
97
+ end
98
+
99
+ # Step 3: Define transform
100
+ class BooleanTransform < Parsanol::Transform
101
+ rule(var: simple(:v), num: simple(:n)) { VarExpr.new("#{v}#{n}") }
102
+ rule(and: simple(:a)) { a }
103
+ rule(or: simple(:o)) { o }
104
+ rule(left: simple(:l), right: simple(:r)) {
105
+ # This handles the case where there's no explicit operator
106
+ l # Just return the left side for non-binary expressions
107
+ }
108
+ end
109
+
110
+ # Transform that handles binary expressions properly
111
+ class BooleanTransformFull < Parsanol::Transform
112
+ rule(var: simple(:v), num: simple(:n)) { VarExpr.new("#{v}#{n}") }
113
+
114
+ rule(left: simple(:l), right: simple(:r)) {
115
+ # This catches expressions without explicit and/or tags
116
+ # Return just the first one (this is a simplified handling)
117
+ l
118
+ }
119
+
120
+ # These would need the actual tree structure to work correctly
121
+ rule(and: subtree(:a)) {
122
+ if a.is_a?(Hash) && a[:left] && a[:right]
123
+ AndExpr.new(transform(a[:left]), transform(a[:right]))
124
+ else
125
+ a
126
+ end
127
+ }
128
+
129
+ rule(or: subtree(:o)) {
130
+ if o.is_a?(Hash) && o[:left] && o[:right]
131
+ OrExpr.new(transform(o[:left]), transform(o[:right]))
132
+ else
133
+ o
134
+ end
135
+ }
136
+ end
137
+
138
+ # Manual AST builder for demonstration
139
+ def build_ast(tree)
140
+ case tree
141
+ when Hash
142
+ if tree[:var] && tree[:num]
143
+ VarExpr.new("#{tree[:var]}#{tree[:num]}")
144
+ elsif tree[:and]
145
+ build_and_expr(tree[:and])
146
+ elsif tree[:or]
147
+ build_or_expr(tree[:or])
148
+ else
149
+ # Try to extract from nested structure
150
+ tree.each_value do |v|
151
+ result = build_ast(v)
152
+ return result if result.is_a?(BoolExpr)
153
+ end
154
+ nil
155
+ end
156
+ when Array
157
+ build_ast(tree.first)
158
+ when Parsanol::Slice
159
+ nil
160
+ else
161
+ nil
162
+ end
163
+ end
164
+
165
+ def build_and_expr(data)
166
+ case data
167
+ when Hash
168
+ left = build_ast(data[:left])
169
+ right = build_ast(data[:right])
170
+ if left && right
171
+ AndExpr.new(left, right)
172
+ else
173
+ build_ast(data)
174
+ end
175
+ else
176
+ build_ast(data)
177
+ end
178
+ end
179
+
180
+ def build_or_expr(data)
181
+ case data
182
+ when Hash
183
+ left = build_ast(data[:left])
184
+ right = build_ast(data[:right])
185
+ if left && right
186
+ OrExpr.new(left, right)
187
+ else
188
+ build_ast(data)
189
+ end
190
+ else
191
+ build_ast(data)
192
+ end
193
+ end
194
+
195
+ # Step 4: Parse and transform
196
+ def parse_boolean(input)
197
+ parser = BooleanAlgebraParser.new
198
+
199
+ # RubyTransform: Parse and get tree
200
+ tree = parser.parse(input)
201
+ puts "Parse tree: #{tree.inspect}"
202
+
203
+ # Build AST
204
+ ast = build_ast(tree)
205
+ puts "AST: #{ast.to_s}"
206
+
207
+ ast
208
+ end
209
+
210
+ # Example usage
211
+ if __FILE__ == $0
212
+ puts "=" * 60
213
+ puts "Boolean Algebra Parser - RubyTransform"
214
+ puts "=" * 60
215
+ puts
216
+
217
+ expressions = [
218
+ "var1",
219
+ "var1 and var2",
220
+ "var1 or var2",
221
+ "var1 and var2 or var3",
222
+ "var1 or var2 and var3",
223
+ "(var1 or var2) and var3",
224
+ ]
225
+
226
+ expressions.each do |expr_str|
227
+ puts "-" * 40
228
+ puts "Input: #{expr_str}"
229
+ begin
230
+ ast = parse_boolean(expr_str)
231
+ rescue => e
232
+ puts "Error: #{e.message}"
233
+ end
234
+ puts
235
+ end
236
+
237
+ # Demonstrate evaluation
238
+ puts "=" * 60
239
+ puts "Evaluation Example"
240
+ puts "=" * 60
241
+
242
+ bindings = { "var1" => true, "var2" => false, "var3" => true }
243
+ puts "Bindings: var1=true, var2=false, var3=true"
244
+ puts
245
+
246
+ eval_exprs = [
247
+ "var1 and var2",
248
+ "var1 or var2",
249
+ "var1 and var3",
250
+ "(var1 or var2) and var3",
251
+ ]
252
+
253
+ eval_exprs.each do |expr_str|
254
+ begin
255
+ ast = parse_boolean(expr_str)
256
+ result = ast.eval(bindings)
257
+ puts " #{expr_str} = #{result}"
258
+ rescue => e
259
+ puts " #{expr_str} = ERROR: #{e.message}"
260
+ end
261
+ puts
262
+ end
263
+ end
@@ -0,0 +1,153 @@
1
+ # A simple integer calculator to answer the question about how to do
2
+ # left and right associativity in parslet (PEG) once and for all.
3
+
4
+ $:.unshift File.dirname(__FILE__) + "/../lib"
5
+
6
+ require 'rspec'
7
+ require 'parsanol/parslet'
8
+ require 'parsanol/rig/rspec'
9
+
10
+ # This is the parsing stage. It expresses left associativity by compiling
11
+ # list of things that have the same associativity.
12
+ class CalcParser < Parsanol::Parser
13
+ root :addition
14
+
15
+ rule(:addition) {
16
+ multiplication.as(:l) >> (add_op >> multiplication.as(:r)).repeat(1) |
17
+ multiplication
18
+ }
19
+
20
+ rule(:multiplication) {
21
+ integer.as(:l) >> (mult_op >> integer.as(:r)).repeat(1) |
22
+ integer }
23
+
24
+ rule(:integer) { digit.repeat(1).as(:i) >> space? }
25
+
26
+ rule(:mult_op) { match['*/'].as(:o) >> space? }
27
+ rule(:add_op) { match['+-'].as(:o) >> space? }
28
+
29
+ rule(:digit) { match['0-9'] }
30
+ rule(:space?) { match['\s'].repeat }
31
+ end
32
+
33
+ # Classes for the abstract syntax tree.
34
+ Int = Struct.new(:int) {
35
+ def eval; self end
36
+ def op(operation, other)
37
+ left = int
38
+ right = other.int
39
+
40
+ Int.new(
41
+ case operation
42
+ when '+'
43
+ left + right
44
+ when '-'
45
+ left - right
46
+ when '*'
47
+ left * right
48
+ when '/'
49
+ left / right
50
+ end)
51
+ end
52
+ def to_i
53
+ int
54
+ end
55
+ }
56
+ Seq = Struct.new(:sequence) {
57
+ def eval
58
+ sequence.reduce { |accum, operation|
59
+ operation.call(accum) }
60
+ end
61
+ }
62
+ LeftOp = Struct.new(:operation, :right) {
63
+ def call(left)
64
+ left = left.eval
65
+ right = self.right.eval
66
+
67
+ left.op(operation, right)
68
+ end
69
+ }
70
+
71
+ # Transforming intermediary syntax tree into a real AST.
72
+ class CalcTransform < Parsanol::Transform
73
+ rule(i: simple(:i)) { Int.new(Integer(i)) }
74
+ rule(o: simple(:o), r: simple(:i)) { LeftOp.new(o, i) }
75
+ rule(l: simple(:i)) { i }
76
+ rule(sequence(:seq)) { Seq.new(seq) }
77
+ end
78
+
79
+ # And this calls everything in the right order.
80
+ def calculate(str)
81
+ intermediary_tree = CalcParser.new.parse(str)
82
+ abstract_tree = CalcTransform.new.apply(intermediary_tree)
83
+ result = abstract_tree.eval
84
+
85
+ result.to_i
86
+ end
87
+
88
+ # A test suite for the above parser
89
+ describe CalcParser do
90
+ let(:p) { described_class.new }
91
+ describe '#integer' do
92
+ let(:i) { p.integer }
93
+ it "parses integers" do
94
+ i.should parse('1')
95
+ i.should parse('123')
96
+ end
97
+ it "consumes trailing white space" do
98
+ i.should parse('123 ')
99
+ end
100
+ it "doesn't parse floats" do
101
+ i.should_not parse('1.3')
102
+ end
103
+ end
104
+ describe '#multiplication' do
105
+ let(:m) { p.multiplication }
106
+ it "parses simple multiplication" do
107
+ m.should parse('1*2')
108
+ end
109
+ it "parses division" do
110
+ m.should parse('1/2')
111
+ end
112
+ end
113
+ describe '#addition' do
114
+ let(:a) { p.addition }
115
+
116
+ it "parses simple addition" do
117
+ a.should parse('1+2')
118
+ a.should parse('1+2+3-4')
119
+ end
120
+ end
121
+ end
122
+ describe CalcTransform do
123
+ def t(obj)
124
+ described_class.new.apply(obj)
125
+ end
126
+
127
+ it "transforms integers" do
128
+ t(i: '1').should == Int.new(1)
129
+ end
130
+ it "unwraps left operand" do
131
+ t(l: :obj).should == :obj
132
+ end
133
+ end
134
+ describe 'whole computation specs' do
135
+ def self.result_of(str, int)
136
+ it(str) { calculate(str).should == int }
137
+ end
138
+
139
+ result_of '1+1', 2
140
+ result_of '1-1-1', -1
141
+ result_of '1+1+3*5/2', 9
142
+ result_of '123*2', 246
143
+ end
144
+
145
+
146
+ # Enable these if you want to change the code.
147
+ # RSpec::Core::Runner.run([], $stderr, $stdout)
148
+
149
+ str = ARGV.join
150
+ str = '123*2' if str.match(/^\s*$/)
151
+
152
+ print "#{str} (command line): -> "
153
+ puts calculate(str)
@@ -0,0 +1,120 @@
1
+ # Calculator - Ruby Implementation
2
+
3
+ ## How to Run
4
+
5
+ ```bash
6
+ cd parsanol-ruby/example/calculator
7
+ ruby basic.rb "1+2*3"
8
+ ```
9
+
10
+ ## Code Walkthrough
11
+
12
+ ### Left Associativity via Repetition
13
+
14
+ Addition uses repetition for left associativity:
15
+
16
+ ```ruby
17
+ rule(:addition) {
18
+ multiplication.as(:l) >> (add_op >> multiplication.as(:r)).repeat(1) |
19
+ multiplication
20
+ }
21
+ ```
22
+
23
+ The pattern `l >> (op >> r).repeat` creates left-associative chains like `((1+2)+3)`.
24
+
25
+ ### Multiplication Rule
26
+
27
+ Multiplication has higher precedence:
28
+
29
+ ```ruby
30
+ rule(:multiplication) {
31
+ integer.as(:l) >> (mult_op >> integer.as(:r)).repeat(1) |
32
+ integer
33
+ }
34
+ ```
35
+
36
+ Multiplication is tried first in the addition rule, giving it higher precedence.
37
+
38
+ ### Operator Rules
39
+
40
+ Operators capture their symbol:
41
+
42
+ ```ruby
43
+ rule(:mult_op) { match['*/'].as(:o) >> space? }
44
+ rule(:add_op) { match['+-'].as(:o) >> space? }
45
+ ```
46
+
47
+ The `:o` label marks the operator for transform matching.
48
+
49
+ ### AST Node Classes
50
+
51
+ Ruby structs represent AST nodes:
52
+
53
+ ```ruby
54
+ Int = Struct.new(:int) {
55
+ def eval; self end
56
+ def op(operation, other)
57
+ left = int
58
+ right = other.int
59
+ Int.new(
60
+ case operation
61
+ when '+' then left + right
62
+ when '-' then left - right
63
+ when '*' then left * right
64
+ when '/' then left / right
65
+ end)
66
+ end
67
+ }
68
+
69
+ Seq = Struct.new(:sequence) {
70
+ def eval
71
+ sequence.reduce { |accum, operation|
72
+ operation.call(accum) }
73
+ end
74
+ }
75
+
76
+ LeftOp = Struct.new(:operation, :right) {
77
+ def call(left)
78
+ left = left.eval
79
+ right = self.right.eval
80
+ left.op(operation, right)
81
+ end
82
+ }
83
+ ```
84
+
85
+ `Int` represents values; `Seq` represents expression chains; `LeftOp` represents binary operations.
86
+
87
+ ### Transform Rules
88
+
89
+ Transform builds the AST:
90
+
91
+ ```ruby
92
+ class CalcTransform < Parsanol::Transform
93
+ rule(i: simple(:i)) { Int.new(Integer(i)) }
94
+ rule(o: simple(:o), r: simple(:i)) { LeftOp.new(o, i) }
95
+ rule(l: simple(:i)) { i }
96
+ rule(sequence(:seq)) { Seq.new(seq) }
97
+ end
98
+ ```
99
+
100
+ Pattern matching extracts components and constructs typed objects.
101
+
102
+ ## Output Types
103
+
104
+ ```ruby
105
+ Int.new(42) # Integer value
106
+ LeftOp.new('+', Int.new(2)) # Binary operation
107
+ Seq.new([Int.new(1), LeftOp.new('+', Int.new(2))]) # Expression chain
108
+ ```
109
+
110
+ After `eval`: returns `Int` with result value.
111
+
112
+ ## Design Decisions
113
+
114
+ ### Why Struct for AST Nodes?
115
+
116
+ Structs are lightweight, immutable, and can define methods. They're ideal for simple AST representation.
117
+
118
+ ### Why `repeat(1)` for Operators?
119
+
120
+ `repeat(1)` requires at least one operator, distinguishing `1+2` from bare `1`. The alternative handles the base case.