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,269 @@
1
+ # Calculator Example - ZeroCopy: Mirrored Objects (Direct FFI)
2
+ #
3
+ # This example demonstrates ZeroCopy where:
4
+ # 1. Rust parser (parsanol-rs) does the parsing
5
+ # 2. Rust transform converts to typed structs
6
+ # 3. Direct Ruby object construction via FFI (no serialization!)
7
+ # 4. Maximum performance with zero-copy
8
+ #
9
+ # This option provides the best performance but requires type definitions
10
+ # in both Rust and Ruby.
11
+
12
+ $:.unshift File.dirname(__FILE__) + "/../lib"
13
+
14
+ require 'parsanol'
15
+
16
+ # NOTE: This example requires:
17
+ # 1. Native extension support for parse_to_objects
18
+ # 2. #[derive(RubyObject)] proc macro in Rust
19
+ # 3. Matching Ruby class definitions
20
+ #
21
+ # This serves as an API preview.
22
+
23
+ # Step 1: Define Ruby classes that mirror Rust struct definitions
24
+ # These classes MUST match the Rust definitions exactly
25
+ module Calculator
26
+ class Expr
27
+ def eval
28
+ raise NotImplementedError
29
+ end
30
+ end
31
+
32
+ class Number < Expr
33
+ attr_reader :value
34
+
35
+ # This constructor is called directly from Rust FFI
36
+ def initialize(value)
37
+ @value = value
38
+ end
39
+
40
+ def eval = @value
41
+
42
+ def to_s = @value.to_s
43
+
44
+ def ==(other)
45
+ other.is_a?(Number) && @value == other.value
46
+ end
47
+ end
48
+
49
+ class BinOp < Expr
50
+ attr_reader :left, :op, :right
51
+
52
+ # This constructor is called directly from Rust FFI
53
+ # Rust sets instance variables directly via rb_ivar_set
54
+ def initialize(left: nil, op: nil, right: nil)
55
+ @left = left
56
+ @op = op
57
+ @right = right
58
+ end
59
+
60
+ def eval
61
+ left_val = @left.eval
62
+ right_val = @right.eval
63
+
64
+ case @op
65
+ when '+' then left_val + right_val
66
+ when '-' then left_val - right_val
67
+ when '*' then left_val * right_val
68
+ when '/' then left_val / right_val
69
+ end
70
+ end
71
+
72
+ def to_s
73
+ "(#{@left} #{@op} #{@right})"
74
+ end
75
+ end
76
+ end
77
+
78
+ # Step 2: Define the parser with output type mapping
79
+ class CalculatorParser < Parsanol::Parser
80
+ # Include ZeroCopy module for direct FFI object construction
81
+ # include Parsanol::ZeroCopy
82
+
83
+ root :expression
84
+
85
+ rule(:expression) {
86
+ (term.as(:left) >> add_op.as(:op) >> expression.as(:right)).as(:binop) |
87
+ term
88
+ }
89
+
90
+ rule(:term) {
91
+ (factor.as(:left) >> mult_op.as(:op) >> term.as(:right)).as(:binop) |
92
+ factor
93
+ }
94
+
95
+ rule(:factor) {
96
+ lparen >> expression >> rparen |
97
+ number
98
+ }
99
+
100
+ rule(:number) {
101
+ (match('[0-9]').repeat(1)).as(:int) >> space?
102
+ }
103
+
104
+ rule(:add_op) { match('[+-]').as(:op) >> space? }
105
+ rule(:mult_op) { match('[*/]').as(:op) >> space? }
106
+
107
+ rule(:lparen) { str('(') >> space? }
108
+ rule(:rparen) { str(')') >> space? }
109
+ rule(:space?) { match('\s').repeat }
110
+
111
+ # Output type mapping (ZeroCopy feature)
112
+ # This tells Rust which Ruby classes to construct
113
+ # output_types(
114
+ # number: Calculator::Number,
115
+ # binop: Calculator::BinOp
116
+ # )
117
+ end
118
+
119
+ # Step 3: Parse with direct object construction
120
+ def calculate(input)
121
+ parser = CalculatorParser.new
122
+
123
+ # ZeroCopy: Parse and get direct Ruby objects
124
+ # NOTE: This requires native extension support
125
+ # expr = parser.parse(input)
126
+ # # expr is already a Calculator::Number or Calculator::BinOp!
127
+ # # No transform needed, no JSON serialization!
128
+
129
+ # For demonstration, simulate what ZeroCopy would return
130
+ # Real implementation would call:
131
+ # Native.parse_to_objects(grammar_json, input, output_types)
132
+
133
+ # Simulate direct object construction
134
+ expr = simulate_parse(input)
135
+ puts "AST: #{expr.class} -> #{expr.to_s}"
136
+
137
+ result = expr.eval
138
+ puts "Result: #{result}"
139
+
140
+ result
141
+ end
142
+
143
+ # Simulated parsing for demonstration
144
+ def simulate_parse(input)
145
+ case input.strip
146
+ when "42"
147
+ Calculator::Number.new(42)
148
+ when "1 + 2"
149
+ Calculator::BinOp.new(
150
+ left: Calculator::Number.new(1),
151
+ op: '+',
152
+ right: Calculator::Number.new(2)
153
+ )
154
+ when "3 * 4"
155
+ Calculator::BinOp.new(
156
+ left: Calculator::Number.new(3),
157
+ op: '*',
158
+ right: Calculator::Number.new(4)
159
+ )
160
+ when "2 + 3 * 4"
161
+ Calculator::BinOp.new(
162
+ left: Calculator::Number.new(2),
163
+ op: '+',
164
+ right: Calculator::BinOp.new(
165
+ left: Calculator::Number.new(3),
166
+ op: '*',
167
+ right: Calculator::Number.new(4)
168
+ )
169
+ )
170
+ when "(2 + 3) * 4"
171
+ Calculator::BinOp.new(
172
+ left: Calculator::BinOp.new(
173
+ left: Calculator::Number.new(2),
174
+ op: '+',
175
+ right: Calculator::Number.new(3)
176
+ ),
177
+ op: '*',
178
+ right: Calculator::Number.new(4)
179
+ )
180
+ else
181
+ raise "Not simulated: #{input}"
182
+ end
183
+ end
184
+
185
+ # Example usage
186
+ if __FILE__ == $0
187
+ puts "=" * 60
188
+ puts "Calculator Example - ZeroCopy: Mirrored Objects"
189
+ puts "=" * 60
190
+ puts
191
+ puts "NOTE: This example shows the planned API for ZeroCopy."
192
+ puts "The native extension support for parse_to_objects is coming soon."
193
+ puts
194
+
195
+ test_cases = [
196
+ ["42", 42],
197
+ ["1 + 2", 3],
198
+ ["3 * 4", 12],
199
+ ["2 + 3 * 4", 14],
200
+ ["(2 + 3) * 4", 20],
201
+ ]
202
+
203
+ test_cases.each do |input, expected|
204
+ puts
205
+ puts "-" * 40
206
+ puts "Input: #{input}"
207
+ begin
208
+ result = calculate(input)
209
+ status = result == expected ? "✓ PASS" : "✗ FAIL"
210
+ puts "Expected: #{expected}, Got: #{result} - #{status}"
211
+ rescue => e
212
+ puts "Error: #{e.message}"
213
+ puts "✗ FAIL"
214
+ end
215
+ end
216
+
217
+ puts
218
+ puts "=" * 60
219
+ puts "ZeroCopy Benefits:"
220
+ puts "- FASTEST: No serialization overhead"
221
+ puts "- Zero-copy: Direct Ruby object construction"
222
+ puts "- Type-safe: Types defined in both Rust and Ruby"
223
+ puts "- Methods defined in Ruby (eval, to_s, etc.)"
224
+ puts
225
+ puts "ZeroCopy Requirements:"
226
+ puts "- Define types in Rust with #[derive(RubyObject)]"
227
+ puts "- Define matching Ruby classes"
228
+ puts "- Native extension compiled with ruby feature"
229
+ puts "=" * 60
230
+ end
231
+
232
+ # Rust code that would be needed (for reference):
233
+ #
234
+ # // In parsanol-rs
235
+ # use parsanol_ruby_derive::RubyObject;
236
+ #
237
+ # #[derive(Debug, Clone, RubyObject)]
238
+ # #[ruby_class("Calculator::Expr")]
239
+ # pub enum Expr {
240
+ # #[ruby_variant("number")]
241
+ # Number(i64),
242
+ #
243
+ # #[ruby_variant("binop")]
244
+ # BinOp {
245
+ # left: Box<Expr>,
246
+ # op: String,
247
+ # right: Box<Expr>,
248
+ # },
249
+ # }
250
+ #
251
+ # // The proc macro generates:
252
+ # impl RubyObject for Expr {
253
+ # fn to_ruby(&self, ruby: &Ruby) -> Result<Value, Error> {
254
+ # match self {
255
+ # Expr::Number(n) => {
256
+ # let class = ruby.class("Calculator::Number")?;
257
+ # class.new_instance((*n,))
258
+ # }
259
+ # Expr::BinOp { left, op, right } => {
260
+ # let class = ruby.class("Calculator::BinOp")?;
261
+ # let obj = class.new_instance()?;
262
+ # obj.ivar_set("@left", left.to_ruby(ruby)?)?;
263
+ # obj.ivar_set("@op", op.to_ruby(ruby)?)?;
264
+ # obj.ivar_set("@right", right.to_ruby(ruby)?)?;
265
+ # Ok(obj.as_value())
266
+ # }
267
+ # }
268
+ # }
269
+ # }
@@ -0,0 +1,36 @@
1
+ # Calculator (Zero-Copy - Option C)
2
+
3
+ ## Purpose
4
+
5
+ This implementation demonstrates direct FFI object construction: Rust parses
6
+ and directly constructs Ruby objects without serialization.
7
+
8
+ ## When to Use
9
+
10
+ - Maximum performance required
11
+ - Production systems
12
+ - When zero-copy is critical
13
+
14
+ ## Key Concepts
15
+
16
+ 1. **Direct FFI**: No serialization overhead
17
+ 2. **Ruby Object Construction**: Direct via rb_funcall
18
+ 3. **Type Safety**: Mirrored types on both sides
19
+
20
+ ## Running
21
+
22
+ ```bash
23
+ ruby example/calculator/zero_copy.rb
24
+ ```
25
+
26
+ ## Output
27
+
28
+ ```
29
+ Input: 42+8
30
+ Result: Calculator::AddExpr
31
+ Value: 50
32
+ ```
33
+
34
+ ## Note
35
+
36
+ This is the fastest option but requires more complex FFI setup.
@@ -0,0 +1,49 @@
1
+
2
+ # This example demonstrates how pieces of input can be captured and matched
3
+ # against later on. Without this, you cannot match here-documents and other
4
+ # self-dependent grammars.
5
+
6
+ $:.unshift File.dirname(__FILE__) + "/../lib"
7
+ require 'parsanol/parslet'
8
+ require 'parsanol/convenience'
9
+ require 'pp'
10
+
11
+ class CapturingParser < Parsanol::Parser
12
+ root :document
13
+
14
+ # Introduce a scope for each document. This ensures that documents can be
15
+ # nested.
16
+ rule(:document) { scope { doc_start >> text >> doc_end } }
17
+
18
+ # Start of a document is a heredoc marker. This is captured in :marker
19
+ rule(:doc_start) { str('<') >> marker >> newline }
20
+ rule(:marker) { match['A-Z'].repeat(1).capture(:marker) }
21
+
22
+ # The content of a document can be either lines of text or another
23
+ # document, introduced by <HERE, where HERE is the doc marker.
24
+ rule(:text) { (document.as(:doc) | text_line.as(:line)).repeat(1) }
25
+ rule(:text_line) { captured_marker.absent? >> any >>
26
+ (newline.absent? >> any).repeat >> newline }
27
+
28
+ # The end of the document is marked by the marker that was at the beginning
29
+ # of the document, by itself on a line.
30
+ rule(:doc_end) { captured_marker }
31
+ rule(:captured_marker) {
32
+ dynamic { |source, context|
33
+ str(context.captures[:marker])
34
+ }
35
+ }
36
+
37
+ rule(:newline) { match["\n"] }
38
+ end
39
+
40
+ parser = CapturingParser.new
41
+ pp parser.parse_with_debug %Q(<CAPTURE
42
+ Text1
43
+ <FOOBAR
44
+ Text3
45
+ Text4
46
+ FOOBAR
47
+ Text2
48
+ CAPTURE)
49
+
@@ -0,0 +1,106 @@
1
+ # Capture Patterns - Ruby Implementation
2
+
3
+ ## How to Run
4
+
5
+ ```bash
6
+ cd parsanol-ruby/example/capture
7
+ ruby basic.rb
8
+ ```
9
+
10
+ ## Code Walkthrough
11
+
12
+ ### Capture Mechanism
13
+
14
+ The `capture(:name)` method captures matched text for later reference:
15
+
16
+ ```ruby
17
+ rule(:marker) { match['A-Z'].repeat(1).capture(:marker) }
18
+ ```
19
+
20
+ This captures the heredoc marker (like `CAPTURE` or `FOOBAR`) in the context.
21
+
22
+ ### Dynamic Matching
23
+
24
+ The `dynamic` block accesses captured values:
25
+
26
+ ```ruby
27
+ rule(:captured_marker) {
28
+ dynamic { |source, context|
29
+ str(context.captures[:marker])
30
+ }
31
+ }
32
+ ```
33
+
34
+ `context.captures[:marker]` returns the previously captured string.
35
+
36
+ ### Scope Isolation
37
+
38
+ The `scope` block creates isolated capture contexts:
39
+
40
+ ```ruby
41
+ rule(:document) { scope { doc_start >> text >> doc_end } }
42
+ ```
43
+
44
+ Each document has its own capture namespace, allowing nested heredocs.
45
+
46
+ ### Document Structure
47
+
48
+ Heredoc-style documents with nested content:
49
+
50
+ ```ruby
51
+ rule(:text) {
52
+ (document.as(:doc) | text_line.as(:line)).repeat(1)
53
+ }
54
+ ```
55
+
56
+ Documents can contain other documents (nested heredocs).
57
+
58
+ ### End Marker Matching
59
+
60
+ The end marker must match the start marker:
61
+
62
+ ```ruby
63
+ rule(:doc_end) { captured_marker }
64
+ ```
65
+
66
+ This ensures `<FOOBAR` ... `FOOBAR` pairs are correctly matched.
67
+
68
+ ## Output Types
69
+
70
+ ```ruby
71
+ # Input:
72
+ # <CAPTURE
73
+ # Text1
74
+ # <FOOBAR
75
+ # Text3
76
+ # FOOBAR
77
+ # Text2
78
+ # CAPTURE
79
+
80
+ # Parse tree (simplified):
81
+ {:doc=>[
82
+ {:line=>"Text1\n"},
83
+ {:doc=>[
84
+ {:line=>"Text3\n"}
85
+ ]},
86
+ {:line=>"Text2\n"}
87
+ ]}
88
+ ```
89
+
90
+ ## Design Decisions
91
+
92
+ ### Why Capture Instead of Backreference?
93
+
94
+ Parslet's capture is more powerful than regex backreferences. It integrates with the parsing context and supports nesting.
95
+
96
+ ### Why Scope Blocks?
97
+
98
+ Without scopes, nested documents would share the same capture namespace. Scopes isolate each document's captures.
99
+
100
+ ### Why Dynamic Blocks?
101
+
102
+ Dynamic blocks provide access to the parsing context at parse time. This enables context-dependent matching.
103
+
104
+ ### Ruby-Only Feature
105
+
106
+ This feature uses Parslet's `capture`, `dynamic`, and `scope` constructs. These have no direct equivalent in Rust and are specific to Parslet's Ruby implementation.
@@ -0,0 +1,39 @@
1
+ {
2
+ "id": "capture",
3
+ "title": "Capture DSL",
4
+ "description": "Demonstrate Parslet's capture DSL for binding parse results to variables within the grammar.",
5
+ "category": "conceptual",
6
+ "tags": ["capture", "binding", "parslet", "variable"],
7
+ "difficulty": "intermediate",
8
+ "concepts": ["capture", "variable binding", "dynamic values", "cross-referencing"],
9
+
10
+ "motivation": {
11
+ "why": "Capture allows binding intermediate parse results to variables for later use in the grammar. This enables cross-referencing and building complex structures from parts.",
12
+ "useCases": [
13
+ "Cross-referencing within a grammar",
14
+ "Building complex structures from parts",
15
+ "Referencing earlier parse results in later rules"
16
+ ]
17
+ },
18
+
19
+ "inputFormat": {
20
+ "description": "Simple expressions that can be captured and referenced.",
21
+ "examples": [
22
+ { "input": "a", "description": "Simple character to capture", "valid": true },
23
+ { "input": "abc", "description": "Multiple characters", "valid": true }
24
+ ]
25
+ },
26
+
27
+ "outputFormat": {
28
+ "description": "Captured values bound to variables.",
29
+ "structure": {
30
+ "captured_value": { "description": "The value captured by the capture DSL" }
31
+ }
32
+ },
33
+
34
+ "rubyOnly": true,
35
+ "parsletCompatible": true,
36
+ "implementations": {
37
+ "ruby": { "basic": "basic.rb" }
38
+ }
39
+ }
@@ -0,0 +1,35 @@
1
+ # A small example on how to parse common types of comments. The example
2
+ # started out with parser code from Stephen Waits.
3
+
4
+ $:.unshift File.dirname(__FILE__) + "/../lib"
5
+
6
+ require 'pp'
7
+ require 'parsanol/parslet'
8
+ require 'parsanol/convenience'
9
+
10
+ class ALanguage < Parsanol::Parser
11
+ root(:lines)
12
+
13
+ rule(:lines) { line.repeat }
14
+ rule(:line) { spaces >> expression.repeat >> newline }
15
+ rule(:newline) { str("\n") >> str("\r").maybe }
16
+
17
+ rule(:expression) { (str('a').as(:a) >> spaces).as(:exp) }
18
+
19
+ rule(:spaces) { space.repeat }
20
+ rule(:space) { multiline_comment | line_comment | str(' ') }
21
+
22
+ rule(:line_comment) { (str('//') >> (newline.absent? >> any).repeat).as(:line) }
23
+ rule(:multiline_comment) { (str('/*') >> (str('*/').absent? >> any).repeat >> str('*/')).as(:multi) }
24
+ end
25
+
26
+ code = %q(
27
+ a
28
+ // line comment
29
+ a a a // line comment
30
+ a /* inline comment */ a
31
+ /* multiline
32
+ comment */
33
+ )
34
+
35
+ pp ALanguage.new.parse_with_debug(code)