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