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,151 @@
1
+
2
+ # The base class for all your parsers. Use as follows:
3
+ #
4
+ # require 'parslet'
5
+ #
6
+ # class MyParser < Parsanol::Parser
7
+ # rule(:a) { str('a').repeat }
8
+ # root(:a)
9
+ # end
10
+ #
11
+ # pp MyParser.new.parse('aaaa') # => 'aaaa'
12
+ # pp MyParser.new.parse('bbbb') # => Parsanol::Atoms::ParseFailed:
13
+ # # Don't know what to do with bbbb at line 1 char 1.
14
+ #
15
+ # Parsanol::Parser is also a grammar atom. This means that you can mix full
16
+ # fledged parsers freely with small parts of a different parser.
17
+ #
18
+ # Example:
19
+ # class ParserA < Parsanol::Parser
20
+ # root :aaa
21
+ # rule(:aaa) { str('a').repeat(3,3) }
22
+ # end
23
+ # class ParserB < Parsanol::Parser
24
+ # root :expression
25
+ # rule(:expression) { str('b') >> ParserA.new >> str('b') }
26
+ # end
27
+ #
28
+ # In the above example, ParserB would parse something like 'baaab'.
29
+ #
30
+ class Parsanol::Parser < Parsanol::Atoms::Base
31
+ include Parsanol
32
+
33
+ class << self # class methods
34
+ # Define the parsers #root function. This is the place where you start
35
+ # parsing; if you have a rule for 'file' that describes what should be
36
+ # in a file, this would be your root declaration:
37
+ #
38
+ # class Parser
39
+ # root :file
40
+ # rule(:file) { ... }
41
+ # end
42
+ #
43
+ # #root declares a 'parse' function that works just like the parse
44
+ # function that you can call on a simple parslet, taking a string as input
45
+ # and producing parse output.
46
+ #
47
+ # In a way, #root is a shorthand for:
48
+ #
49
+ # def parse(str)
50
+ # your_parser_root.parse(str)
51
+ # end
52
+ #
53
+ def root(name)
54
+ undef_method :root if method_defined? :root
55
+ define_method(:root) do
56
+ self.send(name)
57
+ end
58
+ end
59
+ end
60
+
61
+ def try(source, context, consume_all)
62
+ root.try(source, context, consume_all)
63
+ end
64
+
65
+ def to_s_inner(prec)
66
+ root.to_s(prec)
67
+ end
68
+
69
+ # Unified parsing method with mode selection
70
+ #
71
+ # @param input [String] Input string to parse
72
+ # @param mode_or_options [Symbol, Hash] Parsing mode (:ruby, :native, :json) or options hash
73
+ # @param options [Hash] Additional options (if mode provided):
74
+ # - :reporter - Error reporter to use
75
+ # - :prefix - Allow prefix matching (default: false)
76
+ #
77
+ # @return [Hash, Array, String, Parsanol::Slice] Parsed result
78
+ # @raise [Parsanol::ParseFailed] If parsing fails
79
+ #
80
+ # @example Parse in Ruby mode (default)
81
+ # parser.parse("1+2")
82
+ # # => {:left=>"1", :op=>"+", :right=>"2"}
83
+ #
84
+ # @example Parse in native mode (faster, if extension available)
85
+ # parser.parse("1+2", mode: :native)
86
+ # # => {:left=>"1", :op=>"+", :right=>"2"}
87
+ #
88
+ # @example Parse in JSON mode (for cross-language use)
89
+ # parser.parse("1+2", mode: :json)
90
+ # # => '{"left":"1","op":"+","right":"2"}'
91
+ #
92
+ def parse(input, mode_or_options = {}, **options)
93
+ # Support both old API (parse(input, options)) and new API (parse(input, mode: :ruby))
94
+ if mode_or_options.is_a?(Hash)
95
+ # Old API: parse(input, options={})
96
+ # Merge keyword options into the options hash (handles parse(str, prefix: true))
97
+ merged_options = mode_or_options.merge(options)
98
+ super(input, merged_options)
99
+ else
100
+ # New API: parse(input, mode: :ruby, **options)
101
+ mode = mode_or_options
102
+ case mode
103
+ when :ruby
104
+ parse_ruby(input, options)
105
+ when :native
106
+ parse_native(input, options)
107
+ when :json
108
+ parse_json(input, options)
109
+ else
110
+ raise ArgumentError, "Unknown mode: #{mode}. Use :ruby, :native, or :json"
111
+ end
112
+ end
113
+ end
114
+
115
+ # Batch parsing with mode selection
116
+ #
117
+ # @param inputs [Array<String>] Array of input strings
118
+ # @param mode [Symbol] Parsing mode (:ruby, :native, or :json)
119
+ # @param options [Hash] Additional options
120
+ # @return [Array] Array of parsed results
121
+ #
122
+ def parse_batch(inputs, mode: :ruby, **options)
123
+ inputs.map { |input| parse(input, mode: mode, **options) }
124
+ end
125
+
126
+ private
127
+
128
+ def parse_ruby(input, options = {})
129
+ # Use the original Ruby parser from Base
130
+ super(input, options)
131
+ end
132
+
133
+ def parse_native(input, options = {})
134
+ # Use native if available, fallback to pure Ruby
135
+ if Parsanol::Native.available?
136
+ Parsanol::Native.parse_parslet_compatible(root, input)
137
+ else
138
+ parse_ruby(input, options)
139
+ end
140
+ end
141
+
142
+ def parse_json(input, options = {})
143
+ if Parsanol::Native.available?
144
+ grammar_json = Parsanol::Native.serialize_grammar(root)
145
+ result = Parsanol::Native.parse(grammar_json, input)
146
+ result.to_json
147
+ else
148
+ parse_ruby(input, options).to_json
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Parsanol::Parslet - Nested compatibility layer for original Parslet API
4
+ #
5
+ # This provides backwards compatibility for code that uses the original Parslet API.
6
+ # Instead of root-level Parslet constant, we use Parsanol::Parslet as a nested module.
7
+ #
8
+ # Usage:
9
+ # require 'parsanol/parslet'
10
+ #
11
+ # class MyParser < Parsanol::Parslet::Parser
12
+ # include Parsanol::Parslet
13
+ # rule(:foo) { str('foo') }
14
+ # root(:foo)
15
+ # end
16
+ #
17
+ # Migration from original Parslet:
18
+ # Before: require 'parslet'
19
+ # class MyParser < Parslet::Parser
20
+ # include Parslet
21
+ #
22
+ # After: require 'parsanol/parslet'
23
+ # class MyParser < Parsanol::Parslet::Parser
24
+ # include Parsanol::Parslet
25
+
26
+ require 'parsanol'
27
+
28
+ module Parsanol
29
+ module Parslet
30
+ # Include Parsanol to get all DSL methods (str, match, any, etc.)
31
+ include Parsanol
32
+
33
+ # Error class alias for compatibility
34
+ ParseFailed = Parsanol::ParseFailed
35
+
36
+ # Atoms namespace - aliases to Parsanol atoms
37
+ # These are the atoms explicitly loaded by lib/parsanol/atoms.rb
38
+ module Atoms
39
+ Base = ::Parsanol::Atoms::Base
40
+ Str = ::Parsanol::Atoms::Str
41
+ Re = ::Parsanol::Atoms::Re
42
+ Sequence = ::Parsanol::Atoms::Sequence
43
+ Alternative = ::Parsanol::Atoms::Alternative
44
+ Repetition = ::Parsanol::Atoms::Repetition
45
+ Named = ::Parsanol::Atoms::Named
46
+ Entity = ::Parsanol::Atoms::Entity
47
+ Lookahead = ::Parsanol::Atoms::Lookahead
48
+ Cut = ::Parsanol::Atoms::Cut
49
+ Capture = ::Parsanol::Atoms::Capture
50
+ Scope = ::Parsanol::Atoms::Scope
51
+ Dynamic = ::Parsanol::Atoms::Dynamic
52
+ Infix = ::Parsanol::Atoms::Infix
53
+ Ignored = ::Parsanol::Atoms::Ignored
54
+ ParseFailed = ::Parsanol::ParseFailed
55
+ end
56
+
57
+ # Class aliases
58
+ Parser = ::Parsanol::Parser
59
+ Transform = ::Parsanol::Transform
60
+ Cause = ::Parsanol::Cause
61
+ Slice = ::Parsanol::Slice
62
+ Source = ::Parsanol::Source
63
+ Pattern = ::Parsanol::Pattern
64
+ Context = ::Parsanol::Context
65
+
66
+ # Module functions for DSL (delegate to Parsanol)
67
+ extend self
68
+
69
+ def match(str = nil)
70
+ Parsanol.match(str)
71
+ end
72
+
73
+ def str(str)
74
+ Parsanol.str(str)
75
+ end
76
+
77
+ def any
78
+ Parsanol.any
79
+ end
80
+
81
+ def scope(&block)
82
+ Parsanol.scope(&block)
83
+ end
84
+
85
+ def dynamic(&block)
86
+ Parsanol.dynamic(&block)
87
+ end
88
+
89
+ def infix_expression(element, *operations, &reducer)
90
+ Parsanol.infix_expression(element, *operations, &reducer)
91
+ end
92
+
93
+ def exp(str)
94
+ Parsanol.exp(str)
95
+ end
96
+
97
+ def sequence(symbol)
98
+ Parsanol.sequence(symbol)
99
+ end
100
+
101
+ def simple(symbol)
102
+ Parsanol.simple(symbol)
103
+ end
104
+
105
+ def subtree(symbol)
106
+ Parsanol.subtree(symbol)
107
+ end
108
+
109
+ # Class method extensions for Parser
110
+ module ClassMethods
111
+ # Delegate rule definition to Parsanol's implementation
112
+ # This works for both classes and modules that include Parsanol::Parslet
113
+ def rule(name, opts = {}, &definition)
114
+ # Remove existing method if present
115
+ undef_method name if method_defined? name
116
+
117
+ # Define the rule method that memoizes the entity
118
+ define_method(name) do
119
+ @rules ||= {} # <name, rule> memoization
120
+ return @rules[name] if @rules.has_key?(name)
121
+
122
+ # Capture the self of the parser class along with the definition.
123
+ definition_closure = proc {
124
+ result = instance_eval(&definition)
125
+
126
+ # Apply optimizations if enabled (only for classes that support it)
127
+ if self.class.respond_to?(:optimize_rules?) && self.class.optimize_rules?
128
+ # Apply all optimizers: quantifiers, sequences, choices, and lookaheads
129
+ result = Parsanol::Optimizer.simplify_quantifiers(result)
130
+ result = Parsanol::Optimizer.simplify_sequences(result)
131
+ result = Parsanol::Optimizer.simplify_choices(result)
132
+ result = Parsanol::Optimizer.simplify_lookaheads(result)
133
+ end
134
+
135
+ result
136
+ }
137
+
138
+ @rules[name] = Parsanol::Atoms::Entity.new(name, opts[:label], &definition_closure)
139
+ end
140
+ end
141
+ end
142
+
143
+ # Extend with class methods when included
144
+ def self.included(base)
145
+ base.extend(ClassMethods)
146
+ end
147
+ end
148
+ end
Binary file
@@ -0,0 +1,49 @@
1
+
2
+ # Used internally for representing a bind placeholder in a Parsanol::Transform
3
+ # pattern. This is the superclass for all bindings.
4
+ #
5
+ # It defines the most permissive kind of bind, the one that matches any subtree
6
+ # whatever it looks like.
7
+ #
8
+ class Parsanol::Pattern::SubtreeBind < Struct.new(:symbol)
9
+ def variable_name
10
+ symbol
11
+ end
12
+
13
+ def inspect
14
+ "#{bind_type_name}(#{symbol.inspect})"
15
+ end
16
+
17
+ def can_bind?(subtree)
18
+ true
19
+ end
20
+
21
+ private
22
+ def bind_type_name
23
+ if md=self.class.name.match(/(\w+)Bind/)
24
+ md.captures.first.downcase
25
+ else
26
+ # This path should never be used, but since this is for inspection only,
27
+ # let's not raise.
28
+ 'unknown_bind'
29
+ end
30
+ end
31
+ end
32
+
33
+ # Binds a symbol to a simple subtree, one that is not either a sequence of
34
+ # elements or a collection of attributes.
35
+ #
36
+ class Parsanol::Pattern::SimpleBind < Parsanol::Pattern::SubtreeBind
37
+ def can_bind?(subtree)
38
+ not [Hash, Array].include?(subtree.class)
39
+ end
40
+ end
41
+
42
+ # Binds a symbol to a sequence of simple leafs ([element1, element2, ...])
43
+ #
44
+ class Parsanol::Pattern::SequenceBind < Parsanol::Pattern::SubtreeBind
45
+ def can_bind?(subtree)
46
+ subtree.kind_of?(Array) &&
47
+ (not subtree.any? { |el| [Hash, Array].include?(el.class) })
48
+ end
49
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Matches trees against expressions. Trees are formed by arrays and hashes
4
+ # for expressing membership and sequence. The leafs of the tree are other
5
+ # classes.
6
+ #
7
+ # A tree issued by the parslet library might look like this:
8
+ #
9
+ # {
10
+ # :function_call => {
11
+ # :name => 'foobar',
12
+ # :args => [1, 2, 3]
13
+ # }
14
+ # }
15
+ #
16
+ # A pattern that would match against this tree would be:
17
+ #
18
+ # { :function_call => { :name => simple(:name), :args => sequence(:args) }}
19
+ #
20
+ # Note that Parsanol::Pattern only matches at a given subtree; it wont try
21
+ # to match recursively. To do that, please use Parsanol::Transform.
22
+ #
23
+ class Parsanol::Pattern
24
+ def initialize(pattern)
25
+ @pattern = pattern
26
+ end
27
+
28
+ # Decides if the given subtree matches this pattern. Returns the bindings
29
+ # made on a successful match or nil if the match fails. If you specify
30
+ # bindings to be a hash, the mappings in it will be treated like bindings
31
+ # made during an attempted match.
32
+ #
33
+ # Pattern.new('a').match('a', :foo => 'bar') # => { :foo => 'bar' }
34
+ #
35
+ # @param subtree [String, Hash, Array] poro subtree returned by a parse
36
+ # @param bindings [Hash] variable bindings to be verified
37
+ # @return [Hash, nil] On success: variable bindings that allow a match. On
38
+ # failure: nil
39
+ #
40
+ def match(subtree, bindings=nil)
41
+ bindings = bindings && bindings.dup || Hash.new
42
+ return bindings if element_match(subtree, @pattern, bindings)
43
+ end
44
+
45
+ # Returns true if the tree element given by +tree+ matches the expression
46
+ # given by +exp+. This match must respect bindings already made in
47
+ # +bindings+. Note that bindings is carried along and modified.
48
+ #
49
+ # @api private
50
+ #
51
+ def element_match(tree, exp, bindings)
52
+ # p [:elm, tree, exp]
53
+ if tree.is_a?(Hash) && exp.is_a?(Hash)
54
+ return element_match_hash(tree, exp, bindings)
55
+ elsif tree.is_a?(Array) && exp.is_a?(Array)
56
+ return element_match_ary_single(tree, exp, bindings)
57
+ else
58
+ # If elements match exactly, then that is good enough in all cases
59
+ return true if exp === tree
60
+
61
+ # If exp is a bind variable: Check if the binding matches
62
+ if exp.respond_to?(:can_bind?) && exp.can_bind?(tree)
63
+ return element_match_binding(tree, exp, bindings)
64
+ end
65
+
66
+ # Otherwise: No match (we don't know anything about the element
67
+ # combination)
68
+ return false
69
+ end
70
+ end
71
+
72
+ # @api private
73
+ #
74
+ def element_match_binding(tree, exp, bindings)
75
+ var_name = exp.variable_name
76
+
77
+ # TODO test for the hidden :_ feature.
78
+ if var_name && bound_value = bindings[var_name]
79
+ return bound_value == tree
80
+ end
81
+
82
+ # New binding:
83
+ bindings.store var_name, tree
84
+
85
+ return true
86
+ end
87
+
88
+ # @api private
89
+ #
90
+ def element_match_ary_single(sequence, exp, bindings)
91
+ return false if sequence.size != exp.size
92
+
93
+ return sequence.zip(exp).all? { |elt, subexp|
94
+ element_match(elt, subexp, bindings) }
95
+ end
96
+
97
+ # @api private
98
+ #
99
+ def element_match_hash(tree, exp, bindings)
100
+ # Early failure when one hash is bigger than the other
101
+ return false unless exp.size == tree.size
102
+
103
+ # We iterate over expected pattern, since we demand that the keys that
104
+ # are there should be in tree as well.
105
+ exp.each do |expected_key, expected_value|
106
+ return false unless tree.has_key? expected_key
107
+
108
+ # Recurse into the value and stop early on failure
109
+ value = tree[expected_key]
110
+ return false unless element_match(value, expected_value, bindings)
111
+ end
112
+
113
+ return true
114
+ end
115
+ end
@@ -0,0 +1,220 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Parsanol
4
+ # Generic object pool for reducing garbage collection pressure.
5
+ #
6
+ # The ObjectPool class implements a simple object pooling strategy:
7
+ # - Objects are pre-allocated on initialization
8
+ # - Objects are reused instead of created new
9
+ # - Objects are reset before being returned to the pool
10
+ # - Pool size is bounded to prevent unbounded growth
11
+ #
12
+ # This reduces GC pressure by reusing objects instead of constantly
13
+ # creating and destroying them, which is particularly beneficial for
14
+ # frequently allocated objects like Slice instances.
15
+ #
16
+ # == Thread Safety
17
+ #
18
+ # This implementation is NOT thread-safe. If thread safety is required,
19
+ # wrap pool operations in a mutex or use thread-local pools.
20
+ #
21
+ # == Usage Example
22
+ #
23
+ # # Create a pool for Slice objects
24
+ # pool = Parsanol::ObjectPool.new(Parsanol::Slice, size: 1000)
25
+ #
26
+ # # Acquire an object from the pool
27
+ # slice = pool.acquire
28
+ # slice.instance_variable_set(:@bytepos, 0)
29
+ # slice.instance_variable_set(:@str, "hello")
30
+ #
31
+ # # Use the slice...
32
+ #
33
+ # # Return it to the pool for reuse
34
+ # pool.release(slice)
35
+ #
36
+ # == Object Reset Protocol
37
+ #
38
+ # Objects returned to the pool will have their reset! method called
39
+ # if they respond to it. This allows objects to clean up their state
40
+ # before being reused. If reset! is not defined, the object is still
41
+ # pooled but without automatic cleanup.
42
+ #
43
+ class ObjectPool
44
+ # @return [Integer] Maximum number of objects to keep in the pool
45
+ attr_reader :size
46
+
47
+ # @return [Hash] Statistics about pool usage
48
+ attr_reader :stats
49
+
50
+ # Initialize a new object pool.
51
+ #
52
+ # @param klass [Class] The class of objects to pool
53
+ # @param size [Integer] Maximum number of objects to keep in pool (default: 1000)
54
+ # @param preallocate [Boolean] Whether to pre-allocate objects on initialization (default: true)
55
+ #
56
+ # @example Create a pool with default settings
57
+ # pool = ObjectPool.new(Array, size: 1000)
58
+ #
59
+ # @example Create a pool without pre-allocation
60
+ # pool = ObjectPool.new(Array, size: 1000, preallocate: false)
61
+ #
62
+ def initialize(klass, size: 1000, preallocate: true)
63
+ @klass = klass
64
+ @size = size
65
+ @available = []
66
+ @stats = {
67
+ created: 0,
68
+ reused: 0,
69
+ released: 0,
70
+ discarded: 0
71
+ }
72
+
73
+ # Pre-allocate objects for efficiency if requested
74
+ # This reduces allocation overhead during initial parsing
75
+ preallocate(size) if preallocate && can_preallocate?
76
+ end
77
+
78
+ # Acquire an object from the pool.
79
+ #
80
+ # If the pool has available objects, one is returned (and considered "reused").
81
+ # If the pool is empty, a new object is created (and considered "created").
82
+ #
83
+ # @return [Object] An object instance from the pool or newly created
84
+ #
85
+ # @example Acquire from pool
86
+ # obj = pool.acquire
87
+ #
88
+ def acquire
89
+ if @available.empty?
90
+ @stats[:created] += 1
91
+ @klass.new
92
+ else
93
+ @stats[:reused] += 1
94
+ @available.pop
95
+ end
96
+ end
97
+
98
+ # Return an object to the pool for reuse.
99
+ #
100
+ # Before returning to the pool:
101
+ # 1. If object responds to reset!, that method is called to clean up state
102
+ # 2. If pool is at capacity, the object is discarded instead of pooled
103
+ #
104
+ # This ensures:
105
+ # - Objects are cleaned before reuse (no stale state)
106
+ # - Pool doesn't grow unbounded (respects size limit)
107
+ #
108
+ # @param obj [Object] The object to return to the pool
109
+ # @return [Boolean] true if object was returned to pool, false if discarded
110
+ #
111
+ # @example Return object to pool
112
+ # pool.release(obj)
113
+ #
114
+ def release(obj)
115
+ # Don't pool if we're at capacity - discard instead
116
+ if @available.size >= @size
117
+ @stats[:discarded] += 1
118
+ return false
119
+ end
120
+
121
+ # Reset object state if it supports the protocol
122
+ obj.reset! if obj.respond_to?(:reset!)
123
+
124
+ @stats[:released] += 1
125
+ @available.push(obj)
126
+ true
127
+ end
128
+
129
+ # Get current pool statistics.
130
+ #
131
+ # Statistics include:
132
+ # - size: Maximum pool capacity
133
+ # - available: Number of objects currently available in pool
134
+ # - created: Total number of new objects created
135
+ # - reused: Total number of times objects were reused from pool
136
+ # - released: Total number of objects returned to pool
137
+ # - discarded: Total number of objects discarded (pool was full)
138
+ # - utilization: Percentage of acquires that were reused (0-100)
139
+ #
140
+ # @return [Hash] Hash containing pool statistics
141
+ #
142
+ # @example Get statistics
143
+ # stats = pool.stats
144
+ # puts "Pool utilization: #{stats[:utilization]}%"
145
+ #
146
+ def statistics
147
+ total_acquires = @stats[:created] + @stats[:reused]
148
+ utilization = total_acquires.zero? ? 0.0 : (@stats[:reused].to_f / total_acquires * 100)
149
+
150
+ {
151
+ size: @size,
152
+ available: @available.size,
153
+ created: @stats[:created],
154
+ reused: @stats[:reused],
155
+ released: @stats[:released],
156
+ discarded: @stats[:discarded],
157
+ utilization: utilization.round(2)
158
+ }
159
+ end
160
+
161
+ # Clear all objects from the pool.
162
+ #
163
+ # This removes all pooled objects and resets statistics.
164
+ # Useful for testing or when you want to force fresh allocations.
165
+ #
166
+ # @return [void]
167
+ #
168
+ # @example Clear the pool
169
+ # pool.clear!
170
+ #
171
+ def clear!
172
+ @available.clear
173
+ @stats = {
174
+ created: 0,
175
+ reused: 0,
176
+ released: 0,
177
+ discarded: 0
178
+ }
179
+ end
180
+
181
+ private
182
+
183
+ # Check if the pooled class can be pre-allocated.
184
+ #
185
+ # Some classes require arguments to initialize and cannot be
186
+ # pre-allocated without those arguments. This method checks if
187
+ # the class has a zero-arity initialize method.
188
+ #
189
+ # @return [Boolean] true if class can be instantiated without arguments
190
+ #
191
+ def can_preallocate?
192
+ # Check if the class can be instantiated without arguments
193
+ # This is a heuristic - we try to create one instance to test
194
+ begin
195
+ @klass.new
196
+ true
197
+ rescue ArgumentError
198
+ # Class requires arguments, cannot pre-allocate
199
+ false
200
+ end
201
+ end
202
+
203
+ # Pre-allocate objects to fill the pool.
204
+ #
205
+ # This is called during initialization if preallocate: true is set.
206
+ # Pre-allocation reduces allocation overhead during initial parsing.
207
+ #
208
+ # @param count [Integer] Number of objects to pre-allocate
209
+ # @return [void]
210
+ #
211
+ def preallocate(count)
212
+ count.times do
213
+ @available.push(@klass.new)
214
+ end
215
+ # Adjust stats to reflect pre-allocation as "released" not "created"
216
+ # since these objects haven't been acquired yet
217
+ @stats[:released] = count
218
+ end
219
+ end
220
+ end