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,132 @@
1
+ $:.unshift File.dirname(__FILE__) + "/../lib"
2
+
3
+ require 'parsanol/parslet'
4
+ require 'parsanol/convenience'
5
+
6
+ # This example demonstrates tree error reporting in a real life example.
7
+ # Originally contributed to Parslet, ported to Parsanol as an example.
8
+
9
+ def prettify(str)
10
+ puts " "*3 + " "*4 + "." + " "*4 + "10" + " "*3 + "." + " "*4 + "20"
11
+ str.lines.each_with_index do |line, index|
12
+ printf "%02d %s\n",
13
+ index+1,
14
+ line.chomp
15
+ end
16
+ end
17
+
18
+ class Parser < Parsanol::Parser
19
+
20
+ # commons
21
+
22
+ rule(:space) { match('[ \t]').repeat(1) }
23
+ rule(:space?) { space.maybe }
24
+
25
+ rule(:newline) { match('[\r\n]') }
26
+
27
+ rule(:comment) { str('#') >> match('[^\r\n]').repeat }
28
+
29
+ rule(:line_separator) {
30
+ (space? >> ((comment.maybe >> newline) | str(';')) >> space?).repeat(1)
31
+ }
32
+
33
+ rule(:blank) { line_separator | space }
34
+ rule(:blank?) { blank.maybe }
35
+
36
+ rule(:identifier) { match('[a-zA-Z0-9_]').repeat(1) }
37
+
38
+ # res_statement
39
+
40
+ rule(:reference) {
41
+ (str('@').repeat(1,2) >> identifier).as(:reference)
42
+ }
43
+
44
+ rule(:res_action_or_link) {
45
+ str('.').as(:dot) >> (identifier >> str('?').maybe ).as(:name) >> str('()')
46
+ }
47
+
48
+ rule(:res_actions) {
49
+ (
50
+ reference
51
+ ).as(:resources) >>
52
+ (
53
+ res_action_or_link.as(:res_action)
54
+ ).repeat(0).as(:res_actions)
55
+ }
56
+
57
+ rule(:res_statement) {
58
+ res_actions >>
59
+ (str(':') >> identifier.as(:name)).maybe.as(:res_field)
60
+ }
61
+
62
+ # expression
63
+
64
+ rule(:expression) {
65
+ res_statement
66
+ }
67
+
68
+ # body
69
+
70
+ rule(:body) {
71
+ (line_separator >> (block | expression)).repeat(1).as(:body) >>
72
+ line_separator
73
+ }
74
+
75
+ # blocks
76
+
77
+ rule(:begin_block) {
78
+ (str('concurrent').as(:type) >> space).maybe.as(:pre) >>
79
+ str('begin').as(:begin) >>
80
+ body >>
81
+ str('end')
82
+ }
83
+
84
+ rule(:define_block) {
85
+ str('define').as(:define) >> space >>
86
+ identifier.as(:name) >> str('()') >>
87
+ body >>
88
+ str('end')
89
+ }
90
+
91
+ rule(:block) {
92
+ define_block | begin_block
93
+ }
94
+
95
+ # root
96
+
97
+ rule(:radix) {
98
+ line_separator.maybe >> block >> line_separator.maybe
99
+ }
100
+
101
+ root(:radix)
102
+ end
103
+
104
+
105
+ ds = [
106
+ %{
107
+ define f()
108
+ @res.name
109
+ end
110
+ },
111
+ %{
112
+ define f()
113
+ begin
114
+ @res.name
115
+ end
116
+ end
117
+ }
118
+ ]
119
+
120
+ ds.each do |d|
121
+
122
+ puts '-' * 80
123
+ prettify(d)
124
+
125
+ parser = Parser.new
126
+
127
+ begin
128
+ parser.parse_with_debug(d)
129
+ end
130
+ end
131
+
132
+ puts '-' * 80
@@ -0,0 +1,122 @@
1
+ # Error Reporting - Ruby Implementation
2
+
3
+ ## How to Run
4
+
5
+ ```bash
6
+ cd parsanol-ruby/example/error-reporting
7
+ ruby basic.rb
8
+ ```
9
+
10
+ ## Code Walkthrough
11
+
12
+ ### Common Rules
13
+
14
+ Whitespace and comments are handled uniformly:
15
+
16
+ ```ruby
17
+ rule(:space) { match('[ \t]').repeat(1) }
18
+ rule(:space?) { space.maybe }
19
+ rule(:newline) { match('[\r\n]') }
20
+ rule(:comment) { str('#') >> match('[^\r\n]').repeat }
21
+ ```
22
+
23
+ Separating common rules improves readability.
24
+
25
+ ### Line Separator Rule
26
+
27
+ Complex line ending handles comments:
28
+
29
+ ```ruby
30
+ rule(:line_separator) {
31
+ (space? >> ((comment.maybe >> newline) | str(';')) >> space?).repeat(1)
32
+ }
33
+ ```
34
+
35
+ Lines can end with newline, semicolon, or comment followed by newline.
36
+
37
+ ### Block Structure
38
+
39
+ Define and begin blocks share body structure:
40
+
41
+ ```ruby
42
+ rule(:begin_block) {
43
+ (str('concurrent').as(:type) >> space).maybe.as(:pre) >>
44
+ str('begin').as(:begin) >>
45
+ body >>
46
+ str('end')
47
+ }
48
+
49
+ rule(:define_block) {
50
+ str('define').as(:define) >> space >>
51
+ identifier.as(:name) >> str('()') >>
52
+ body >>
53
+ str('end')
54
+ }
55
+ ```
56
+
57
+ Both have opening keyword, content, and closing `end`.
58
+
59
+ ### Body Rule
60
+
61
+ Bodies contain expressions or nested blocks:
62
+
63
+ ```ruby
64
+ rule(:body) {
65
+ (line_separator >> (block | expression)).repeat(1).as(:body) >>
66
+ line_separator
67
+ }
68
+ ```
69
+
70
+ Recursive structure allows arbitrary nesting.
71
+
72
+ ### parse_with_debug
73
+
74
+ The example uses debug parsing:
75
+
76
+ ```ruby
77
+ parser.parse_with_debug(d)
78
+ ```
79
+
80
+ This method prints detailed error information when parsing fails.
81
+
82
+ ### Prettify Helper
83
+
84
+ Error context is displayed with line numbers:
85
+
86
+ ```ruby
87
+ def prettify(str)
88
+ puts " "*3 + " "*4 + "." + " "*4 + "10" + " "*3 + "." + " "*4 + "20"
89
+ str.lines.each_with_index do |line, index|
90
+ printf "%02d %s\n", index+1, line.chomp
91
+ end
92
+ end
93
+ ```
94
+
95
+ Column markers help identify error positions.
96
+
97
+ ## Output Types
98
+
99
+ ```ruby
100
+ # Successful parse:
101
+ {:define=>"define", :name=>"f", :body=>[...]}
102
+
103
+ # Parse failure with debug:
104
+ # Displays:
105
+ # - Expected token at position
106
+ # - Line and column information
107
+ # - What was found vs. expected
108
+ ```
109
+
110
+ ## Design Decisions
111
+
112
+ ### Why Separate Line Separator?
113
+
114
+ Line endings in this language can include comments. A dedicated rule handles the complexity.
115
+
116
+ ### Why parse_with_debug?
117
+
118
+ `parse_with_debug` provides developer-friendly error output, essential for language tooling.
119
+
120
+ ### Why Multiple Test Cases?
121
+
122
+ The example tests both simple and nested structures to demonstrate error reporting at different nesting levels.
@@ -0,0 +1,284 @@
1
+ # Expression Evaluator - Ruby Implementation
2
+ #
3
+ # A complete expression parser with operator precedence, variables,
4
+ # and function calls. Demonstrates building a practical calculator.
5
+ #
6
+ # Run with: ruby example/expression-evaluator/basic.rb
7
+
8
+ $:.unshift File.dirname(__FILE__) + "/../lib"
9
+
10
+ require 'parsanol/parslet'
11
+
12
+ # Expression parser with full operator precedence
13
+ class ExpressionParser < Parsanol::Parser
14
+ root :expression
15
+
16
+ # Comparison (lowest precedence)
17
+ rule(:expression) { comparison }
18
+
19
+ rule(:comparison) {
20
+ addition.as(:left) >>
21
+ ((match('==|!=|<=|>=|<>').as(:op) | str('<') | str('>')).as(:op) >>
22
+ addition.as(:right)).repeat(1) |
23
+ addition
24
+ }
25
+
26
+ # Addition/subtraction
27
+ rule(:addition) {
28
+ multiplication.as(:left) >>
29
+ (match('[+-]').as(:op) >> multiplication.as(:right)).repeat(1) |
30
+ multiplication
31
+ }
32
+
33
+ # Multiplication/division/modulo
34
+ rule(:multiplication) {
35
+ power.as(:left) >>
36
+ (match('[*/%]').as(:op) >> power.as(:right)).repeat(1) |
37
+ power
38
+ }
39
+
40
+ # Exponentiation (right associative)
41
+ rule(:power) {
42
+ unary.as(:left) >>
43
+ (str('^').as(:op) >> power.as(:right)).maybe |
44
+ unary
45
+ }
46
+
47
+ # Unary operators
48
+ rule(:unary) {
49
+ (str('-').as(:op) >> unary.as(:operand)).as(:unary) |
50
+ (str('!').as(:op) >> unary.as(:operand)).as(:unary) |
51
+ primary
52
+ }
53
+
54
+ # Primary: number, function call, variable, or parenthesized expression
55
+ rule(:primary) {
56
+ funcall |
57
+ number |
58
+ variable |
59
+ lparen >> expression >> rparen
60
+ }
61
+
62
+ rule(:number) {
63
+ (match('[0-9]').repeat(1) >> str('.') >> match('[0-9]').repeat(1) |
64
+ match('[0-9]').repeat(1)).as(:number) >> space?
65
+ }
66
+
67
+ rule(:variable) {
68
+ (match('[a-zA-Z_]') >> match('[a-zA-Z0-9_]').repeat).as(:variable) >> space?
69
+ }
70
+
71
+ rule(:funcall) {
72
+ (match('[a-zA-Z_]') >> match('[a-zA-Z0-9_]').repeat).as(:name) >>
73
+ lparen >>
74
+ arglist.as(:args) >>
75
+ rparen
76
+ }
77
+
78
+ rule(:arglist) {
79
+ (expression >> (comma >> expression).repeat).maybe
80
+ }
81
+
82
+ rule(:lparen) { str('(') >> space? }
83
+ rule(:rparen) { str(')') >> space? }
84
+ rule(:comma) { str(',') >> space? }
85
+ rule(:space?) { match('\s').repeat }
86
+ end
87
+
88
+ # AST node classes
89
+ Number = Struct.new(:value) do
90
+ def eval(_ctx)
91
+ value
92
+ end
93
+ end
94
+
95
+ Variable = Struct.new(:name) do
96
+ def eval(ctx)
97
+ ctx.variables.fetch(name) { raise "Unknown variable: #{name}" }
98
+ end
99
+ end
100
+
101
+ BinaryOp = Struct.new(:left, :op, :right) do
102
+ def eval(ctx)
103
+ l = left.eval(ctx)
104
+ r = right.eval(ctx)
105
+
106
+ case op
107
+ when '+' then l + r
108
+ when '-' then l - r
109
+ when '*' then l * r
110
+ when '/' then l / r
111
+ when '%' then l % r
112
+ when '^' then l ** r
113
+ when '==' then l == r ? 1.0 : 0.0
114
+ when '!=' then l != r ? 1.0 : 0.0
115
+ when '<' then l < r ? 1.0 : 0.0
116
+ when '>' then l > r ? 1.0 : 0.0
117
+ when '<=' then l <= r ? 1.0 : 0.0
118
+ when '>=' then l >= r ? 1.0 : 0.0
119
+ else raise "Unknown operator: #{op}"
120
+ end
121
+ end
122
+ end
123
+
124
+ UnaryOp = Struct.new(:op, :operand) do
125
+ def eval(ctx)
126
+ v = operand.eval(ctx)
127
+ case op
128
+ when '-' then -v
129
+ when '!' then v == 0 ? 1.0 : 0.0
130
+ else raise "Unknown unary operator: #{op}"
131
+ end
132
+ end
133
+ end
134
+
135
+ FunctionCall = Struct.new(:name, :args) do
136
+ def eval(ctx)
137
+ arg_values = args.map { |a| a.eval(ctx) }
138
+
139
+ if ctx.functions.key?(name)
140
+ ctx.functions[name].call(arg_values)
141
+ else
142
+ raise "Unknown function: #{name}"
143
+ end
144
+ end
145
+ end
146
+
147
+ # Transform parse tree to AST
148
+ class ExpressionTransform < Parsanol::Transform
149
+ rule(number: simple(:n)) { Number.new(n.to_s.to_f) }
150
+ rule(variable: simple(:v)) { Variable.new(v.to_s) }
151
+
152
+ rule(name: simple(:n), args: simple(:a)) {
153
+ FunctionCall.new(n.to_s, a.is_a?(Array) ? a : [a])
154
+ }
155
+ rule(name: simple(:n), args: sequence(:a)) {
156
+ FunctionCall.new(n.to_s, a)
157
+ }
158
+
159
+ rule(left: simple(:l)) { l }
160
+
161
+ rule(left: simple(:l), op: simple(:o), right: simple(:r)) {
162
+ BinaryOp.new(l, o.to_s, r)
163
+ }
164
+
165
+ rule(unary: { op: simple(:o), operand: simple(:e) }) {
166
+ UnaryOp.new(o.to_s, e)
167
+ }
168
+ end
169
+
170
+ # Evaluation context with variables and functions
171
+ class EvalContext
172
+ attr_accessor :variables, :functions
173
+
174
+ def initialize
175
+ @variables = {
176
+ 'PI' => Math::PI,
177
+ 'E' => Math::E
178
+ }
179
+
180
+ @functions = {
181
+ 'sin' => ->(args) { Math.sin(args[0] || 0) },
182
+ 'cos' => ->(args) { Math.cos(args[0] || 0) },
183
+ 'tan' => ->(args) { Math.tan(args[0] || 0) },
184
+ 'sqrt' => ->(args) { Math.sqrt(args[0] || 0) },
185
+ 'abs' => ->(args) { (args[0] || 0).abs },
186
+ 'floor' => ->(args) { (args[0] || 0).floor },
187
+ 'ceil' => ->(args) { (args[0] || 0).ceil },
188
+ 'round' => ->(args) { (args[0] || 0).round },
189
+ 'min' => ->(args) { [args[0] || 0, args[1] || 0].min },
190
+ 'max' => ->(args) { [args[0] || 0, args[1] || 0].max },
191
+ 'log' => ->(args) { Math.log(args[0] || 1) },
192
+ 'exp' => ->(args) { Math.exp(args[0] || 0) }
193
+ }
194
+ end
195
+
196
+ def set(name, value)
197
+ @variables[name] = value
198
+ end
199
+ end
200
+
201
+ # Evaluate an expression string
202
+ def evaluate(str, ctx = EvalContext.new)
203
+ parser = ExpressionParser.new
204
+ transform = ExpressionTransform.new
205
+
206
+ tree = parser.parse(str)
207
+ ast = transform.apply(tree)
208
+
209
+ # Handle BinaryOp chains with multiple ops
210
+ if ast.is_a?(Array) && ast.length == 1
211
+ ast = ast.first
212
+ end
213
+
214
+ # Reduce left-associative chains
215
+ while ast.is_a?(Hash) && ast.key?(:left)
216
+ left = ast[:left]
217
+ if left.is_a?(Hash) && left.key?(:left)
218
+ # Nested chain - flatten
219
+ inner = evaluate_helper(left, ctx)
220
+ ast = BinaryOp.new(inner, ast[:op], ast[:right])
221
+ else
222
+ ast = BinaryOp.new(left, ast[:op], ast[:right])
223
+ end
224
+ end
225
+
226
+ ast.eval(ctx)
227
+ rescue => e
228
+ "Error: #{e.message}"
229
+ end
230
+
231
+ def evaluate_helper(node, ctx)
232
+ return node unless node.is_a?(Hash)
233
+
234
+ if node.key?(:left)
235
+ left = evaluate_helper(node[:left], ctx)
236
+ right = evaluate_helper(node[:right], ctx)
237
+ BinaryOp.new(left, node[:op], right)
238
+ else
239
+ node
240
+ end
241
+ end
242
+
243
+ # Main demo
244
+ if __FILE__ == $0
245
+ ctx = EvalContext.new
246
+ ctx.set('x', 10.0)
247
+ ctx.set('y', 5.0)
248
+
249
+ puts "Expression Evaluator Example"
250
+ puts "=" * 40
251
+ puts
252
+ puts "Variables: x = #{ctx.variables['x']}, y = #{ctx.variables['y']}"
253
+ puts "Constants: PI = #{ctx.variables['PI']}, E = #{ctx.variables['E']}"
254
+ puts
255
+
256
+ expressions = [
257
+ "1 + 2 * 3",
258
+ "(1 + 2) * 3",
259
+ "2 ^ 3 ^ 2",
260
+ "x + y",
261
+ "x * y - 5",
262
+ "sin(PI / 2)",
263
+ "sqrt(16)",
264
+ "max(x, y)",
265
+ "x > y",
266
+ "min(sin(0), cos(0))"
267
+ ]
268
+
269
+ printf "%-25s | %s\n", "Expression", "Result"
270
+ puts "-" * 40
271
+
272
+ expressions.each do |expr|
273
+ result = evaluate(expr, ctx)
274
+ printf "%-25s | %s\n", expr, result
275
+ end
276
+
277
+ # Command line argument
278
+ if ARGV.length > 0
279
+ expr = ARGV.join(' ')
280
+ puts
281
+ puts "Evaluating: #{expr}"
282
+ puts "Result: #{evaluate(expr, ctx)}"
283
+ end
284
+ end
@@ -0,0 +1,138 @@
1
+ # Expression Evaluator - Ruby Implementation
2
+
3
+ ## How to Run
4
+
5
+ ```bash
6
+ cd parsanol-ruby/example/expression-evaluator
7
+ ruby basic.rb "1+2*3"
8
+ ```
9
+
10
+ ## Code Walkthrough
11
+
12
+ ### Operator Precedence Hierarchy
13
+
14
+ The parser uses layered rules for precedence:
15
+
16
+ ```ruby
17
+ rule(:expression) { comparison }
18
+ rule(:comparison) { addition >> (op >> addition).repeat | addition }
19
+ rule(:addition) { multiplication >> (op >> multiplication).repeat | multiplication }
20
+ rule(:multiplication) { power >> (op >> power).repeat | power }
21
+ rule(:power) { unary >> (str('^') >> power).maybe | unary }
22
+ ```
23
+
24
+ Lower precedence operators are higher in the rule hierarchy. Comparison is tried first, falling through to addition, then multiplication, then power.
25
+
26
+ ### Right Associativity for Exponentiation
27
+
28
+ Power uses `.maybe` for right associativity:
29
+
30
+ ```ruby
31
+ rule(:power) {
32
+ unary.as(:left) >>
33
+ (str('^').as(:op) >> power.as(:right)).maybe |
34
+ unary
35
+ }
36
+ ```
37
+
38
+ Recursive reference to `power` on the right creates `2^(3^2)` instead of `(2^3)^2`.
39
+
40
+ ### Unary Operators
41
+
42
+ Unary minus and logical not prefix expressions:
43
+
44
+ ```ruby
45
+ rule(:unary) {
46
+ (str('-').as(:op) >> unary.as(:operand)).as(:unary) |
47
+ (str('!').as(:op) >> unary.as(:operand)).as(:unary) |
48
+ primary
49
+ }
50
+ ```
51
+
52
+ Recursive definition allows `--5` (double negation).
53
+
54
+ ### Function Calls
55
+
56
+ Functions have name followed by parenthesized arguments:
57
+
58
+ ```ruby
59
+ rule(:funcall) {
60
+ (match('[a-zA-Z_]') >> match('[a-zA-Z0-9_]').repeat).as(:name) >>
61
+ lparen >>
62
+ arglist.as(:args) >>
63
+ rparen
64
+ }
65
+
66
+ rule(:arglist) {
67
+ (expression >> (comma >> expression).repeat).maybe
68
+ }
69
+ ```
70
+
71
+ Arguments are comma-separated expressions; empty arglists are valid.
72
+
73
+ ### AST Node Classes
74
+
75
+ Ruby structs represent AST nodes with evaluation logic:
76
+
77
+ ```ruby
78
+ BinaryOp = Struct.new(:left, :op, :right) do
79
+ def eval(ctx)
80
+ l = left.eval(ctx)
81
+ r = right.eval(ctx)
82
+ case op
83
+ when '+' then l + r
84
+ when '-' then l - r
85
+ # ...
86
+ end
87
+ end
88
+ end
89
+ ```
90
+
91
+ Each node type implements `eval(context)` for recursive evaluation.
92
+
93
+ ### Evaluation Context
94
+
95
+ Context holds variables and functions:
96
+
97
+ ```ruby
98
+ class EvalContext
99
+ attr_accessor :variables, :functions
100
+
101
+ def initialize
102
+ @variables = { 'PI' => Math::PI, 'E' => Math::E }
103
+ @functions = {
104
+ 'sin' => ->(args) { Math.sin(args[0] || 0) },
105
+ 'cos' => ->(args) { Math.cos(args[0] || 0) },
106
+ # ...
107
+ }
108
+ end
109
+ end
110
+ ```
111
+
112
+ Functions are Ruby lambdas that receive argument arrays.
113
+
114
+ ## Output Types
115
+
116
+ ```ruby
117
+ Number.new(42.0) # Numeric literal
118
+ Variable.new("x") # Variable reference
119
+ BinaryOp.new(Number.new(1), "+", Number.new(2)) # Binary operation
120
+ UnaryOp.new("-", Number.new(5)) # Unary negation
121
+ FunctionCall.new("sin", [Variable.new("x")]) # Function call
122
+ ```
123
+
124
+ After `eval`: returns Float result.
125
+
126
+ ## Design Decisions
127
+
128
+ ### Why Layered Rules for Precedence?
129
+
130
+ Layered rules naturally express precedence in PEG parsers. Each layer only "sees" operators at its level, preventing incorrect bindings.
131
+
132
+ ### Why Structs with eval Methods?
133
+
134
+ Structs are lightweight and can define instance methods. Embedding `eval` in each node enables clean recursive evaluation without visitor patterns.
135
+
136
+ ### Why Lambda for Functions?
137
+
138
+ Ruby lambdas are first-class and can be stored in a hash. This allows easy extension of the function library without modifying the evaluator.