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,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Compatibility Test Helper
4
+ #
5
+ # This helper allows running tests against both Parslet and Parsanol::Parslet
6
+ # to verify behavioral compatibility.
7
+ #
8
+ # Usage:
9
+ # PARSANOL_BACKEND=parslet bundle exec rspec spec/parslet_imported/
10
+ # PARSANOL_BACKEND=parsanol bundle exec rspec spec/parslet_imported/
11
+ #
12
+ # Default is to use Parsanol::Parslet
13
+
14
+ module ParsletCompatibilityHelper
15
+ def use_original_parslet?
16
+ ENV['PARSANOL_BACKEND'] == 'parslet'
17
+ end
18
+
19
+ def parslet_module
20
+ @parslet_module ||= begin
21
+ if use_original_parslet?
22
+ require 'parslet'
23
+ Parslet
24
+ else
25
+ require 'parsanol/parslet'
26
+ Parsanol::Parslet
27
+ end
28
+ end
29
+ end
30
+
31
+ # Normalize results for comparison
32
+ # Parslet returns Slice objects, Parsanol returns strings/hashes
33
+ def normalize_result(obj)
34
+ case obj
35
+ when defined?(Parslet::Slice) && Parslet::Slice
36
+ obj.to_s
37
+ when Hash
38
+ obj.transform_values { |v| normalize_result(v) }
39
+ when Array
40
+ obj.map { |v| normalize_result(v) }
41
+ else
42
+ obj
43
+ end
44
+ end
45
+
46
+ # Parse and normalize result for comparison
47
+ def parslet_parse(parser, input)
48
+ result = parser.parse(input)
49
+ normalize_result(result)
50
+ rescue => e
51
+ e
52
+ end
53
+
54
+ # Check if two results are equivalent
55
+ def results_equivalent?(result1, result2)
56
+ normalize_result(result1) == normalize_result(result2)
57
+ end
58
+ end
59
+
60
+ RSpec.configure do |config|
61
+ config.include ParsletCompatibilityHelper
62
+
63
+ config.before(:suite) do
64
+ # Pre-load the appropriate module
65
+ if ENV['PARSANOL_BACKEND'] == 'parslet'
66
+ puts "Running tests with original Parslet"
67
+ require 'parslet'
68
+ else
69
+ puts "Running tests with Parsanol::Parslet compatibility layer"
70
+ require 'parsanol/parslet'
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "benchmark"
4
+ require "parslet"
5
+ require "parslet/native"
6
+
7
+ puts "=" * 60
8
+ puts "Parsanol Batch Parsing Benchmark"
9
+ puts "=" * 60
10
+
11
+ # First ensure native extension is loaded
12
+ unless Parsanol::Native.available?
13
+ puts "ERROR: Native extension not available. Run 'rake compile' first."
14
+ exit 1
15
+ end
16
+
17
+ class SimpleParser < Parsanol::Parser
18
+ rule(:comma) { str(",") >> str(" ").maybe }
19
+ rule(:word) { match(/[a-z]/).repeat(1) }
20
+ rule(:alnum) { match(/[a-z0-9]/).repeat(1) }
21
+
22
+ rule(:value) { (word | alnum).as(:v) }
23
+ rule(:list) { value >> (comma >> value).repeat }
24
+
25
+ root(:list)
26
+ end
27
+
28
+ parser = SimpleParser.new
29
+
30
+ # Create test inputs
31
+ inputs = (1..50).map { |i| "item#{i}, item#{i+1}, item#{i+2}" }
32
+
33
+ puts "\nTest: 50 inputs, 10 items each"
34
+ puts "Input count: #{inputs.length}"
35
+ puts "Total chars: #{inputs.sum(&:length)}"
36
+
37
+ # ============================================================================
38
+ # Test 1: Individual parsing (current approach)
39
+ # ============================================================================
40
+ puts "\n" + "-" * 60
41
+ puts "Test 1: Individual parsing (parse_parslet_compatible)"
42
+ puts "-" * 60
43
+
44
+ Parsanol::Native.clear_cache
45
+
46
+ individual_times = []
47
+ inputs.each do |input|
48
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
49
+ result = Parsanol::Native.parse_parslet_compatible(parser, input)
50
+ elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond) - start
51
+ individual_times << elapsed
52
+ end
53
+
54
+ puts "Total time: #{individual_times.sum.round(0)} μs"
55
+ puts "Average per parse: #{(individual_times.sum / individual_times.length).round(2)} μs"
56
+ puts "Cache: #{Parsanol::Native.cache_stats}"
57
+
58
+ # ============================================================================
59
+ # Test 2: Batch parsing with transform
60
+ # ============================================================================
61
+ puts "\n" + "-" * 60
62
+ puts "Test 2: Batch parsing with transform (parse_batch_with_transform)"
63
+ puts "-" * 60
64
+
65
+ Parsanol::Native.clear_cache
66
+
67
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
68
+ results = Parsanol::Native.parse_batch_with_transform(parser, inputs)
69
+ elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond) - start
70
+
71
+ puts "Total time: #{elapsed.round(0)} μs"
72
+ puts "Average per parse: #{(elapsed / inputs.length).round(2)} μs"
73
+ puts "Results count: #{results.length}"
74
+ puts "Cache: #{Parsanol::Native.cache_stats}"
75
+
76
+ # ============================================================================
77
+ # Test 3: Raw parsing (no transform)
78
+ # ============================================================================
79
+ puts "\n" + "-" * 60
80
+ puts "Test 3: Raw parsing (parse_raw - no transformation)"
81
+ puts "-" * 60
82
+
83
+ Parsanol::Native.clear_cache
84
+
85
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
86
+ results = Parsanol::Native.parse_batch(parser, inputs)
87
+ elapsed_raw = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond) - start
88
+
89
+ puts "Total time: #{elapsed_raw.round(0)} μs"
90
+ puts "Average per parse: #{(elapsed_raw / inputs.length).round(2)} μs"
91
+ puts "Results count: #{results.length}"
92
+
93
+ # ============================================================================
94
+ # Comparison
95
+ # ============================================================================
96
+ puts "\n" + "=" * 60
97
+ puts "Comparison"
98
+ puts "=" * 60
99
+
100
+ total_individual = individual_times.sum
101
+ speedup_with_transform = (total_individual / elapsed).round(2)
102
+ speedup_raw = (total_individual / elapsed_raw).round(2)
103
+
104
+ puts "\nIndividual: #{total_individual.round(0)} μs"
105
+ puts "Batch + transform: #{elapsed.round(0)} μs"
106
+ puts "Batch raw: #{elapsed_raw.round(0)} μs"
107
+
108
+ puts "\nSpeedup (batch vs individual):"
109
+ puts " With transform: #{speedup_with_transform}x faster"
110
+ puts " Raw (no transform): #{speedup_raw}x faster"
111
+
112
+ puts "\n" + "=" * 60
113
+ puts "Analysis"
114
+ puts "=" * 60
115
+
116
+ if speedup_with_transform > 1.5
117
+ puts "✓ Batch parsing is #{speedup_with_transform}x faster"
118
+ else
119
+ puts "⚠ Batch parsing improvement: #{speedup_with_transform}x"
120
+ end
121
+
122
+ if speedup_raw > speedup_with_transform
123
+ transform_overhead = ((elapsed - elapsed_raw) / elapsed * 100).round(1)
124
+ puts " Transformation adds #{transform_overhead}% overhead"
125
+ end
126
+
127
+ puts "\n" + "=" * 60
128
+ puts "Benchmark complete"
129
+ puts "=" * 60
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "benchmark"
4
+ require "parslet"
5
+ require "parslet/native"
6
+
7
+ puts "=" * 70
8
+ puts "Parsanol Complete Optimization Summary"
9
+ puts "=" * 70
10
+
11
+ # First ensure native extension is loaded
12
+ unless Parsanol::Native.available?
13
+ puts "ERROR: Native extension not available. Run 'rake compile' first."
14
+ exit 1
15
+ end
16
+
17
+ class SimpleParser < Parsanol::Parser
18
+ rule(:comma) { str(",") >> str(" ") }
19
+ rule(:word) { match(/[a-z]/).repeat(1) }
20
+
21
+ rule(:value) { word.as(:v) }
22
+ rule(:list) { value >> (comma >> value).repeat }
23
+
24
+ root(:list)
25
+ end
26
+
27
+ parser = SimpleParser.new
28
+ test_input = "one, two, three, four, five"
29
+
30
+ # Clear all caches
31
+ Parsanol::Native.clear_cache
32
+
33
+ puts "\n" + "-" * 70
34
+ puts "1. Cold Cache (first parse)"
35
+ puts "-" * 70
36
+
37
+ # Profile if available (native extension method)
38
+ has_profiling = Parsanol::Native.respond_to?(:profile_reset)
39
+ Parsanol::Native.profile_reset if has_profiling
40
+
41
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
42
+ result = Parsanol::Native.parse_parslet_compatible(parser, test_input)
43
+ cold_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond) - start
44
+
45
+ if has_profiling
46
+ profile = Parsanol::Native.profile_stats
47
+ puts "Time: #{profile["total_parse_us"]} μs"
48
+ puts "Grammar JSON: #{profile["grammar_parse_us"]} μs"
49
+ else
50
+ puts "Time: #{cold_time} μs"
51
+ end
52
+ puts "Cache: #{Parsanol::Native.cache_stats}"
53
+
54
+ puts "\n" + "-" * 70
55
+ puts "2. Warm Cache (repeated parsing - grammar already cached)"
56
+ puts "-" * 70
57
+
58
+ times = []
59
+ 20.times do
60
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
61
+ Parsanol::Native.parse_parslet_compatible(parser, test_input)
62
+ elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond) - start
63
+ times << elapsed
64
+ end
65
+
66
+ avg_warm = times.sum / times.length
67
+ puts "Average time: #{avg_warm.round(2)} μs"
68
+ puts "Min: #{times.min.round(2)} μs, Max: #{times.max.round(2)} μs"
69
+ speedup_cold_warm = cold_time > 0 && avg_warm > 0 ? (cold_time.to_f / avg_warm).round(0) : 0
70
+ puts "Speedup (cold vs warm): #{speedup_cold_warm}x"
71
+
72
+ puts "\n" + "-" * 70
73
+ puts "3. Batch Parsing (50 inputs)"
74
+ puts "-" * 70
75
+
76
+ # Use simple alphabetic inputs (Rust parser has issues with compound character classes)
77
+ words = %w[one two three four five six seven eight nine ten]
78
+ inputs = (0...50).map { |i| "#{words[i % 10]}, #{words[(i+1) % 10]}, #{words[(i+2) % 10]}" }
79
+
80
+ # Individual
81
+ Parsanol::Native.clear_cache
82
+ individual_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
83
+ inputs.each { |i| Parsanol::Native.parse_parslet_compatible(parser, i) }
84
+ individual_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond) - individual_start
85
+
86
+ # Batch with transform
87
+ batch_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
88
+ results = Parsanol::Native.parse_batch_with_transform(parser, inputs)
89
+ batch_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond) - batch_start
90
+
91
+ # Batch raw (no transform)
92
+ Parsanol::Native.clear_cache
93
+ batch_raw_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
94
+ raw_results = Parsanol::Native.parse_batch_inputs(parser, inputs)
95
+ batch_raw_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond) - batch_raw_start
96
+
97
+ puts "Individual: #{(individual_time / 1000.0).round(2)} ms"
98
+ puts "Batch + transform: #{(batch_time / 1000.0).round(2)} ms"
99
+ puts "Batch raw: #{(batch_raw_time / 1000.0).round(2)} ms"
100
+ puts ""
101
+ puts "Speedup (individual vs batch + transform): #{(individual_time / batch_time).round(1)}x"
102
+ puts "Speedup (individual vs batch raw): #{(individual_time / batch_raw_time).round(1)}x"
103
+
104
+ puts "\n" + "=" * 70
105
+ puts "SUMMARY"
106
+ puts "=" * 70
107
+
108
+ cold_time_ms = cold_time / 1000.0 # Convert to ms
109
+ warm_time_ms = avg_warm / 1000.0 # Convert to ms
110
+ speedup_warm = cold_time_ms > 0 && warm_time_ms > 0 ? (cold_time_ms / warm_time_ms).round(0) : 0
111
+
112
+ puts <<~SUMMARY
113
+
114
+ COLD CACHE (first parse):
115
+ Time: #{cold_time_ms.round(2)} ms
116
+
117
+ WARM CACHE (repeated parsing):
118
+ Time: #{warm_time_ms.round(4)} ms
119
+ Speedup: #{speedup_warm}x faster
120
+
121
+ BATCH (50 inputs, with transform):
122
+ Time: #{(batch_time / 1000.0).round(2)} ms
123
+ Speedup: #{(individual_time / batch_time).round(1)}x faster than individual
124
+
125
+ OPTIMIZATIONS APPLIED:
126
+ ✓ Two-level grammar caching
127
+ ✓ Single-key hash optimization
128
+ ✓ Array slicing optimization
129
+ ✓ Batch transformation API
130
+ ✓ Symbol key caching
131
+ ✓ Frozen string constants
132
+ ✓ Optimized flatten_sequence/flatten_repetition
133
+
134
+ EXPRESSIR BENEFITS:
135
+ ✓ NO native code changes needed
136
+ ✓ Automatic performance improvement
137
+ ✓ Reduced memory allocations
138
+
139
+ SUMMARY
140
+
141
+ puts "=" * 70
142
+ puts "Benchmark complete"
143
+ puts "=" * 70
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "benchmark"
4
+ require "parslet"
5
+ require "parslet/native"
6
+
7
+ puts "=" * 60
8
+ puts "Parsanol Grammar Caching Analysis"
9
+ puts "=" * 60
10
+
11
+ # First ensure native extension is loaded
12
+ unless Parsanol::Native.available?
13
+ puts "ERROR: Native extension not available. Run 'rake compile' first."
14
+ exit 1
15
+ end
16
+
17
+ class SimpleParser < Parsanol::Parser
18
+ rule(:comma) { str(",") >> str(" ").maybe }
19
+ rule(:word) { match(/[a-z]/).repeat(1) }
20
+ rule(:alnum) { match(/[a-z0-9]/).repeat(1) }
21
+
22
+ rule(:value) { (word | alnum).as(:v) }
23
+ rule(:list) { value >> (comma >> value).repeat }
24
+
25
+ root(:list)
26
+ end
27
+
28
+ parser = SimpleParser.new
29
+
30
+ # Test input
31
+ test_input = "one, two, three, four, five, six, seven, eight, nine, ten"
32
+ large_input = (1..100).map { |i| "word" }.join(", ")
33
+
34
+ # ============================================================================
35
+ # Test 1: Without grammar caching (parse_parslet_compatible)
36
+ # ============================================================================
37
+ puts "\n" + "-" * 60
38
+ puts "Test 1: parse_parslet_compatible (NO caching)"
39
+ puts "-" * 60
40
+
41
+ Parsanol::Native.profile_reset
42
+ 100.times { Parsanol::Native.parse_parslet_compatible(parser, test_input) }
43
+ profile_no_cache = Parsanol::Native.profile_stats
44
+
45
+ puts "\nTiming (microseconds):"
46
+ puts " Total: #{profile_no_cache["total_parse_us"]} us"
47
+ puts " Grammar JSON: #{profile_no_cache["grammar_parse_us"]} us"
48
+ puts " PEG match: #{profile_no_cache["peg_match_us"]} us"
49
+
50
+ # ============================================================================
51
+ # Test 2: With grammar caching (parse_with_grammar)
52
+ # ============================================================================
53
+ puts "\n" + "-" * 60
54
+ puts "Test 2: parse_with_grammar (cached grammar)"
55
+ puts "-" * 60
56
+
57
+ # Pre-serialize grammar ONCE
58
+ grammar_json = Parsanol::Native.send(:serialize_grammar, parser)
59
+ puts "Grammar JSON size: #{grammar_json.length} chars"
60
+
61
+ Parsanol::Native.profile_reset
62
+ 100.times { Parsanol::Native.parse(grammar_json, test_input) }
63
+ profile_cached = Parsanol::Native.profile_stats
64
+
65
+ puts "\nTiming (microseconds):"
66
+ puts " Total: #{profile_cached["total_parse_us"]} us"
67
+ puts " Grammar JSON: #{profile_cached["grammar_parse_us"]} us"
68
+ puts " PEG match: #{profile_cached["peg_match_us"]} us"
69
+
70
+ # ============================================================================
71
+ # Comparison
72
+ # ============================================================================
73
+ puts "\n" + "=" * 60
74
+ puts "Comparison"
75
+ puts "=" * 60
76
+
77
+ total_no_cache = profile_no_cache["total_parse_us"].to_i
78
+ total_cached = profile_cached["total_parse_us"].to_i
79
+
80
+ if total_no_cache > 0 && total_cached > 0
81
+ improvement = ((total_no_cache - total_cached).to_f / total_no_cache * 100).round(1)
82
+ speedup = (total_no_cache.to_f / total_cached).round(2)
83
+
84
+ puts "\nTotal time (100 parses):"
85
+ puts " Without caching: #{total_no_cache} us"
86
+ puts " With caching: #{total_cached} us"
87
+ puts " Improvement: #{improvement}%"
88
+ puts " Speedup: #{speedup}x"
89
+
90
+ puts "\nGrammar JSON parsing:"
91
+ puts " Without caching: #{profile_no_cache["grammar_parse_us"]} us"
92
+ puts " With caching: #{profile_cached["grammar_parse_us"]} us"
93
+
94
+ puts "\nCache performance:"
95
+ puts " Hits: #{profile_cached["cache_hits"]}"
96
+ puts " Misses: #{profile_cached["cache_misses"]}"
97
+ puts " Hit rate: #{profile_cached["cache_hit_rate"]}%"
98
+ else
99
+ puts "\nWARNING: No timing data available"
100
+ end
101
+
102
+ # ============================================================================
103
+ # Large input test with caching
104
+ # ============================================================================
105
+ puts "\n" + "-" * 60
106
+ puts "Large Input Test (cached grammar)"
107
+ puts "-" * 60
108
+
109
+ Parsanol::Native.profile_reset
110
+ 20.times { Parsanol::Native.parse(grammar_json, large_input) }
111
+ profile_large = Parsanol::Native.profile_stats
112
+
113
+ puts "\nTiming (#{large_input.length} chars, 20 parses):"
114
+ puts " Total: #{profile_large["total_parse_us"]} us"
115
+ puts " Grammar JSON: #{profile_large["grammar_parse_us"]} us"
116
+ puts " PEG match: #{profile_large["peg_match_us"]} us"
117
+ puts " Cache hit rate: #{profile_large["cache_hit_rate"]}%"
118
+
119
+ puts "\n" + "=" * 60
120
+ puts "Analysis complete"
121
+ puts "=" * 60
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "benchmark"
4
+ require "parslet"
5
+
6
+ # Test the grammar caching performance using the native parser
7
+ # This benchmark compares parse times for repeated parsing with the same grammar
8
+
9
+ class SimpleParser < Parsanol::Parser
10
+ rule(:comma) { str(",") >> str(" ").maybe }
11
+ rule(:word) { match(/[a-z]/).repeat(1) }
12
+ rule(:alnum) { match(/[a-z0-9]/).repeat(1) }
13
+
14
+ rule(:value) { (word | alnum).as(:v) }
15
+ rule(:list) { value >> (comma >> value).repeat }
16
+
17
+ root(:list)
18
+ end
19
+
20
+ puts "=" * 60
21
+ puts "Parsanol Grammar Caching Benchmark"
22
+ puts "=" * 60
23
+
24
+ parser = SimpleParser.new
25
+
26
+ # Test input - simple list of values
27
+ test_input = "one, two, three, four, five, six, seven, eight, nine, ten"
28
+
29
+ # Warm-up run
30
+ puts "\nWarming up..."
31
+ 10.times { parser.parse(test_input) }
32
+
33
+ # Benchmark: First parse (no cache)
34
+ puts "\n--- First Parse (no cache) ---"
35
+ first_time = Benchmark.realtime do
36
+ parser.parse(test_input)
37
+ end
38
+ puts "First parse: #{(first_time * 1000).round(2)} ms"
39
+
40
+ # Benchmark: Cached parses
41
+ puts "\n--- Cached Parses (100 iterations) ---"
42
+ cached_times = []
43
+ 100.times do
44
+ time = Benchmark.realtime do
45
+ parser.parse(test_input)
46
+ end
47
+ cached_times << time
48
+ end
49
+
50
+ avg_cached = cached_times.sum / cached_times.length
51
+ min_cached = cached_times.min
52
+ max_cached = cached_times.max
53
+
54
+ puts "Average cached parse: #{(avg_cached * 1000).round(2)} ms"
55
+ puts "Min cached parse: #{(min_cached * 1000).round(2)} ms"
56
+ puts "Max cached parse: #{(max_cached * 1000).round(2)} ms"
57
+
58
+ # Calculate improvement
59
+ improvement = ((first_time - avg_cached) / first_time * 100).round(1)
60
+ puts "\nFirst-to-cached improvement: #{improvement}% faster"
61
+
62
+ # Larger input test
63
+ puts "\n--- Larger Input Test ---"
64
+ # Use pure word input to avoid number/word parsing issues
65
+ large_input = (1..100).map { |i| "word" }.join(", ")
66
+
67
+ # Warm up
68
+ 3.times { parser.parse(large_input) }
69
+
70
+ large_times = 20.times.map do
71
+ Benchmark.realtime { parser.parse(large_input) }
72
+ end
73
+
74
+ avg_large = large_times.sum / large_times.length
75
+ puts "Input size: #{large_input.length} chars"
76
+ puts "Average parse: #{(avg_large * 1000).round(2)} ms"
77
+
78
+ puts "\n" + "=" * 60
79
+ puts "Benchmark complete"
80
+ puts "=" * 60