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,193 @@
1
+ require 'spec_helper'
2
+ require 'parsanol/parslet'
3
+
4
+ # Integration tests for Phase 1.3: Array Buffer Pooling
5
+ # Verifies that ArrayPool is properly integrated into Context and used by atoms
6
+ describe "Array Pooling Integration" do
7
+ describe "Context integration" do
8
+ it "has an array_pool" do
9
+ context = Parsanol::Atoms::Context.new
10
+ expect(context.array_pool).to be_a(Parsanol::Pools::ArrayPool)
11
+ end
12
+
13
+ it "provides acquire_array helper" do
14
+ context = Parsanol::Atoms::Context.new
15
+ array = context.acquire_array
16
+ expect(array).to be_a(Array)
17
+ expect(array).to be_empty
18
+ end
19
+
20
+ it "provides release_array helper" do
21
+ context = Parsanol::Atoms::Context.new
22
+ array = context.acquire_array
23
+ array << 1 << 2 << 3
24
+ result = context.release_array(array)
25
+ expect(result).to be true
26
+ end
27
+
28
+ it "clears arrays on release" do
29
+ context = Parsanol::Atoms::Context.new
30
+ array = context.acquire_array
31
+ array << 1 << 2 << 3
32
+ context.release_array(array)
33
+
34
+ # Next acquisition should get a cleared array
35
+ array2 = context.acquire_array
36
+ expect(array2).to be_empty
37
+ end
38
+ end
39
+
40
+ describe "Repetition parsing with array pooling" do
41
+ it "reuses arrays during simple repetition" do
42
+ parser = Class.new(Parsanol::Parser) do
43
+ root :items
44
+ rule(:items) { str('x').repeat(5) }
45
+ end.new
46
+
47
+ result = parser.parse("xxxxx")
48
+ expect(result.to_s).to eq("xxxxx")
49
+ end
50
+
51
+ it "reuses arrays during repetition with min/max" do
52
+ parser = Class.new(Parsanol::Parser) do
53
+ root :items
54
+ rule(:items) { str('a').repeat(2, 4) }
55
+ end.new
56
+
57
+ result = parser.parse("aaa")
58
+ expect(result.to_s).to eq("aaa")
59
+ end
60
+
61
+ it "handles nested repetitions with pooling" do
62
+ parser = Class.new(Parsanol::Parser) do
63
+ root :nested
64
+ rule(:nested) { (str('x') >> str('x')).repeat(3) }
65
+ end.new
66
+
67
+ result = parser.parse("xxxxxx")
68
+ expect(result.to_s).to eq("xxxxxx")
69
+ end
70
+
71
+ it "handles maybe (repeat(0,1)) with pooling" do
72
+ parser = Class.new(Parsanol::Parser) do
73
+ root :optional
74
+ rule(:optional) { str('a').maybe >> str('b') }
75
+ end.new
76
+
77
+ expect(parser.parse("b").to_s).to eq("b")
78
+ expect(parser.parse("ab").to_s).to eq("ab")
79
+ end
80
+ end
81
+
82
+ describe "Sequence parsing with array pooling" do
83
+ it "reuses arrays during simple sequence" do
84
+ parser = Class.new(Parsanol::Parser) do
85
+ root :sequence
86
+ rule(:sequence) { str('a') >> str('b') >> str('c') }
87
+ end.new
88
+
89
+ result = parser.parse("abc")
90
+ expect(result.to_s).to eq("abc")
91
+ end
92
+
93
+ it "reuses arrays during longer sequences" do
94
+ parser = Class.new(Parsanol::Parser) do
95
+ root :sequence
96
+ rule(:sequence) { str('a') >> str('b') >> str('c') >> str('d') >> str('e') }
97
+ end.new
98
+
99
+ result = parser.parse("abcde")
100
+ expect(result.to_s).to eq("abcde")
101
+ end
102
+
103
+ it "handles nested sequences with pooling" do
104
+ parser = Class.new(Parsanol::Parser) do
105
+ root :nested
106
+ rule(:nested) { (str('a') >> str('b')) >> (str('c') >> str('d')) }
107
+ end.new
108
+
109
+ result = parser.parse("abcd")
110
+ expect(result.to_s).to eq("abcd")
111
+ end
112
+ end
113
+
114
+ describe "Complex parsing with array pooling" do
115
+ it "handles mixed repetitions and sequences" do
116
+ parser = Class.new(Parsanol::Parser) do
117
+ root :mixed
118
+ rule(:mixed) { (str('x') >> str('y')).repeat(3) }
119
+ end.new
120
+
121
+ result = parser.parse("xyxyxy")
122
+ expect(result.to_s).to eq("xyxyxy")
123
+ end
124
+
125
+ it "handles repetition of sequences" do
126
+ parser = Class.new(Parsanol::Parser) do
127
+ root :items
128
+ rule(:items) { item.repeat(3) }
129
+ rule(:item) { str('a') >> str('b') >> space? }
130
+ rule(:space?) { str(' ').maybe }
131
+ end.new
132
+
133
+ result = parser.parse("ab ab ab")
134
+ expect(result.to_s).to eq("ab ab ab")
135
+ end
136
+ end
137
+
138
+ describe "Array pool statistics" do
139
+ it "shows pool reuse during parsing" do
140
+ # Create a context and verify pool behavior
141
+ context = Parsanol::Atoms::Context.new
142
+ pool = context.array_pool
143
+
144
+ # ArrayPool is preallocated, so we need to exhaust the pool first
145
+ # to see newly created arrays. For this test, just verify the pool exists
146
+ # and can acquire/release arrays correctly.
147
+
148
+ # Acquire an array
149
+ arr1 = context.acquire_array
150
+ expect(arr1).to be_a(Array)
151
+ expect(arr1).to be_empty
152
+
153
+ # Release it back
154
+ arr1 << 1 << 2 << 3
155
+ result = context.release_array(arr1)
156
+ expect(result).to be true
157
+
158
+ # Acquire again - should get a cleared array
159
+ arr2 = context.acquire_array
160
+ expect(arr2).to be_empty
161
+
162
+ # Verify pool statistics exist
163
+ stats = pool.statistics
164
+ expect(stats).to have_key(:created)
165
+ expect(stats).to have_key(:reused)
166
+ expect(stats).to have_key(:size)
167
+ expect(stats).to have_key(:utilization)
168
+ end
169
+ end
170
+
171
+ describe "Array structure preservation" do
172
+ it "maintains [:repetition, ...] structure" do
173
+ parser = Class.new(Parsanol::Parser) do
174
+ root :items
175
+ rule(:items) { str('x').repeat(3) }
176
+ end.new
177
+
178
+ result = parser.parse("xxx")
179
+ # Result should be a Parsanol::Slice or properly tagged array
180
+ expect(result.to_s).to eq("xxx")
181
+ end
182
+
183
+ it "maintains [:sequence, ...] structure" do
184
+ parser = Class.new(Parsanol::Parser) do
185
+ root :seq
186
+ rule(:seq) { str('a') >> str('b') }
187
+ end.new
188
+
189
+ result = parser.parse("ab")
190
+ expect(result.to_s).to eq("ab")
191
+ end
192
+ end
193
+ end
@@ -0,0 +1,324 @@
1
+ require 'spec_helper'
2
+ require 'parsanol/parslet'
3
+
4
+ # Integration tests for Phase 2.1: Fixed-Size Buffer Pre-allocation
5
+ # Verifies that BufferPool is properly integrated into Context and accessible
6
+ describe "Buffer Allocation Integration" do
7
+ describe "Context integration" do
8
+ it "has a buffer_pool" do
9
+ context = Parsanol::Atoms::Context.new
10
+ expect(context.buffer_pool).to be_a(Parsanol::Pools::BufferPool)
11
+ end
12
+
13
+ it "provides acquire_buffer helper" do
14
+ context = Parsanol::Atoms::Context.new
15
+ buffer = context.acquire_buffer(size: 8)
16
+
17
+ expect(buffer).to be_a(Parsanol::Buffer)
18
+ expect(buffer.capacity).to be >= 8
19
+ expect(buffer).to be_empty
20
+ end
21
+
22
+ it "provides release_buffer helper" do
23
+ context = Parsanol::Atoms::Context.new
24
+ buffer = context.acquire_buffer(size: 8)
25
+ buffer.push("a")
26
+ buffer.push("b")
27
+
28
+ result = context.release_buffer(buffer)
29
+ expect(result).to be true
30
+ end
31
+
32
+ it "clears buffers on release" do
33
+ context = Parsanol::Atoms::Context.new
34
+
35
+ # Acquire and use buffer
36
+ buffer = context.acquire_buffer(size: 4)
37
+ buffer.push("a")
38
+ buffer.push("b")
39
+ buffer.push("c")
40
+ context.release_buffer(buffer)
41
+
42
+ # Next acquisition should get a cleared buffer
43
+ buffer2 = context.acquire_buffer(size: 4)
44
+ expect(buffer2).to be_empty
45
+ expect(buffer2.size).to eq(0)
46
+ end
47
+
48
+ it "reuses buffers across acquire/release cycles" do
49
+ context = Parsanol::Atoms::Context.new
50
+
51
+ # First cycle
52
+ buffer1 = context.acquire_buffer(size: 8)
53
+ buffer1_id = buffer1.object_id
54
+ context.release_buffer(buffer1)
55
+
56
+ # Second cycle - should reuse same buffer
57
+ buffer2 = context.acquire_buffer(size: 8)
58
+ expect(buffer2.object_id).to eq(buffer1_id)
59
+ end
60
+ end
61
+
62
+ describe "Buffer size class selection" do
63
+ let(:context) { Parsanol::Atoms::Context.new }
64
+
65
+ it "selects appropriate size classes" do
66
+ test_cases = [
67
+ [2, 2],
68
+ [3, 4],
69
+ [5, 8],
70
+ [10, 16],
71
+ [20, 32],
72
+ [50, 64]
73
+ ]
74
+
75
+ test_cases.each do |requested, expected|
76
+ buffer = context.acquire_buffer(size: requested)
77
+ expect(buffer.capacity).to eq(expected),
78
+ "size #{requested} should get capacity #{expected}, got #{buffer.capacity}"
79
+ context.release_buffer(buffer)
80
+ end
81
+ end
82
+
83
+ it "handles standard size classes" do
84
+ Parsanol::Pools::BufferPool::SIZE_CLASSES.each do |size|
85
+ buffer = context.acquire_buffer(size: size)
86
+ expect(buffer.capacity).to eq(size)
87
+ context.release_buffer(buffer)
88
+ end
89
+ end
90
+ end
91
+
92
+ describe "Buffer pool statistics" do
93
+ it "tracks buffer creation and reuse" do
94
+ context = Parsanol::Atoms::Context.new
95
+ pool = context.buffer_pool
96
+
97
+ # First acquire - creates new buffer
98
+ buffer1 = context.acquire_buffer(size: 8)
99
+ context.release_buffer(buffer1)
100
+
101
+ # Second acquire - reuses buffer
102
+ buffer2 = context.acquire_buffer(size: 8)
103
+ context.release_buffer(buffer2)
104
+
105
+ # Check statistics
106
+ stats = pool.statistics
107
+ expect(stats).to be_a(Hash)
108
+ expect(stats[8]).to include(
109
+ :created,
110
+ :reused,
111
+ :released,
112
+ :utilization
113
+ )
114
+
115
+ expect(stats[8][:created]).to eq(1)
116
+ expect(stats[8][:reused]).to eq(1)
117
+ expect(stats[8][:utilization]).to be > 0
118
+ end
119
+
120
+ it "tracks statistics per size class" do
121
+ context = Parsanol::Atoms::Context.new
122
+ pool = context.buffer_pool
123
+
124
+ # Use different size classes
125
+ buffer2 = context.acquire_buffer(size: 2)
126
+ buffer4 = context.acquire_buffer(size: 4)
127
+ buffer8 = context.acquire_buffer(size: 8)
128
+
129
+ context.release_buffer(buffer2)
130
+ context.release_buffer(buffer4)
131
+ context.release_buffer(buffer8)
132
+
133
+ stats = pool.statistics
134
+ expect(stats[2][:created]).to eq(1)
135
+ expect(stats[4][:created]).to eq(1)
136
+ expect(stats[8][:created]).to eq(1)
137
+ end
138
+ end
139
+
140
+ describe "Buffer lifecycle" do
141
+ it "maintains buffer capacity across reuse" do
142
+ context = Parsanol::Atoms::Context.new
143
+
144
+ # Create buffer with capacity 8
145
+ buffer = context.acquire_buffer(size: 8)
146
+ expect(buffer.capacity).to eq(8)
147
+
148
+ # Fill it
149
+ 8.times { |i| buffer.push(i) }
150
+ expect(buffer.size).to eq(8)
151
+
152
+ # Release and reacquire
153
+ context.release_buffer(buffer)
154
+ buffer2 = context.acquire_buffer(size: 8)
155
+
156
+ # Capacity should be preserved, size reset
157
+ expect(buffer2.capacity).to eq(8)
158
+ expect(buffer2.size).to eq(0)
159
+ end
160
+
161
+ it "handles buffer growth gracefully" do
162
+ context = Parsanol::Atoms::Context.new
163
+
164
+ # Acquire small buffer
165
+ buffer = context.acquire_buffer(size: 4)
166
+ expect(buffer.capacity).to eq(4)
167
+
168
+ # Grow beyond capacity
169
+ 10.times { |i| buffer.push(i) }
170
+ expect(buffer.size).to eq(10)
171
+ expect(buffer.capacity).to be > 4
172
+
173
+ # Verify contents
174
+ expect(buffer.to_a).to eq((0..9).to_a)
175
+ end
176
+ end
177
+
178
+ describe "Multiple contexts" do
179
+ it "each context has independent buffer pool" do
180
+ context1 = Parsanol::Atoms::Context.new
181
+ context2 = Parsanol::Atoms::Context.new
182
+
183
+ pool1 = context1.buffer_pool
184
+ pool2 = context2.buffer_pool
185
+
186
+ expect(pool1.object_id).not_to eq(pool2.object_id)
187
+
188
+ # Activity in one doesn't affect the other
189
+ buffer1 = context1.acquire_buffer(size: 8)
190
+ context1.release_buffer(buffer1)
191
+
192
+ stats1 = pool1.statistics[8]
193
+ stats2 = pool2.statistics[8]
194
+
195
+ expect(stats1[:created]).to eq(1)
196
+ expect(stats2[:created]).to eq(0)
197
+ end
198
+ end
199
+
200
+ describe "Buffer operations" do
201
+ let(:context) { Parsanol::Atoms::Context.new }
202
+
203
+ it "supports push operations" do
204
+ buffer = context.acquire_buffer(size: 4)
205
+
206
+ buffer.push("a")
207
+ buffer.push("b")
208
+
209
+ expect(buffer.size).to eq(2)
210
+ expect(buffer.to_a).to eq(["a", "b"])
211
+
212
+ context.release_buffer(buffer)
213
+ end
214
+
215
+ it "supports array conversion" do
216
+ buffer = context.acquire_buffer(size: 8)
217
+
218
+ elements = ["x", "y", "z"]
219
+ elements.each { |e| buffer.push(e) }
220
+
221
+ result = buffer.to_a
222
+ expect(result).to eq(elements)
223
+ expect(result).to be_a(Array)
224
+
225
+ context.release_buffer(buffer)
226
+ end
227
+
228
+ it "supports indexing" do
229
+ buffer = context.acquire_buffer(size: 8)
230
+
231
+ buffer.push("a")
232
+ buffer.push("b")
233
+ buffer.push("c")
234
+
235
+ expect(buffer[0]).to eq("a")
236
+ expect(buffer[1]).to eq("b")
237
+ expect(buffer[2]).to eq("c")
238
+
239
+ buffer[1] = "x"
240
+ expect(buffer[1]).to eq("x")
241
+
242
+ context.release_buffer(buffer)
243
+ end
244
+
245
+ it "supports empty? check" do
246
+ buffer = context.acquire_buffer(size: 4)
247
+
248
+ expect(buffer.empty?).to be true
249
+
250
+ buffer.push("a")
251
+ expect(buffer.empty?).to be false
252
+
253
+ buffer.clear!
254
+ expect(buffer.empty?).to be true
255
+
256
+ context.release_buffer(buffer)
257
+ end
258
+ end
259
+
260
+ describe "Pool capacity management" do
261
+ it "handles pool overflow gracefully" do
262
+ context = Parsanol::Atoms::Context.new
263
+ pool = context.buffer_pool
264
+
265
+ # Note: BufferPool has pool_size of 100 by default
266
+ # We'll just verify the pool can handle multiple buffer cycles
267
+
268
+ 10.times do
269
+ buffer = context.acquire_buffer(size: 8)
270
+ buffer.push("data")
271
+ context.release_buffer(buffer)
272
+ end
273
+
274
+ stats = pool.statistics[8]
275
+ expect(stats[:released]).to be >= 10
276
+ end
277
+ end
278
+
279
+ describe "Performance characteristics" do
280
+ it "buffer reuse reduces allocations" do
281
+ context = Parsanol::Atoms::Context.new
282
+ pool = context.buffer_pool
283
+
284
+ # Clear statistics
285
+ pool.clear!
286
+
287
+ # Perform multiple acquire/release cycles
288
+ cycles = 20
289
+ cycles.times do
290
+ buffer = context.acquire_buffer(size: 8)
291
+ buffer.push("x")
292
+ context.release_buffer(buffer)
293
+ end
294
+
295
+ stats = pool.statistics[8]
296
+
297
+ # Should have high reuse rate
298
+ # First acquire creates, rest reuse
299
+ expect(stats[:created]).to be <= 2
300
+ expect(stats[:reused]).to be >= (cycles - 2)
301
+ expect(stats[:utilization]).to be > 80.0
302
+ end
303
+ end
304
+
305
+ describe "Context pool coexistence" do
306
+ it "buffer_pool works alongside array_pool" do
307
+ context = Parsanol::Atoms::Context.new
308
+
309
+ # Both pools should exist
310
+ expect(context.array_pool).to be_a(Parsanol::Pools::ArrayPool)
311
+ expect(context.buffer_pool).to be_a(Parsanol::Pools::BufferPool)
312
+
313
+ # Both should be functional
314
+ array = context.acquire_array
315
+ buffer = context.acquire_buffer(size: 4)
316
+
317
+ expect(array).to be_a(Array)
318
+ expect(buffer).to be_a(Parsanol::Buffer)
319
+
320
+ context.release_array(array)
321
+ context.release_buffer(buffer)
322
+ end
323
+ end
324
+ end
@@ -0,0 +1,184 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'parsanol/parslet'
5
+
6
+ describe "Position Pooling Integration" do
7
+ describe "Source integration" do
8
+ let(:source) { Parsanol::Source.new("hello\nworld\n") }
9
+
10
+ it "has a position_pool" do
11
+ expect(source.position_pool).to be_a(Parsanol::Pools::PositionPool)
12
+ end
13
+
14
+ it "creates pooled positions" do
15
+ pos1 = source.position(0)
16
+ expect(pos1).to be_a(Parsanol::Position)
17
+ expect(pos1.bytepos).to eq(0)
18
+ end
19
+
20
+ it "position method works without arguments" do
21
+ source.bytepos = 5
22
+ pos = source.position
23
+ expect(pos.bytepos).to eq(5)
24
+ end
25
+
26
+ it "positions can be reused from pool" do
27
+ pos1 = source.position(0)
28
+ id1 = pos1.object_id
29
+
30
+ # Release back to pool (simulated by creating another position)
31
+ source.position_pool.release(pos1)
32
+
33
+ # Get another position - should reuse the same object
34
+ pos2 = source.position(10)
35
+ expect(pos2.object_id).to eq(id1)
36
+ expect(pos2.bytepos).to eq(10)
37
+ end
38
+ end
39
+
40
+ describe "Error reporting with pooled positions" do
41
+ def catch_failed_parse
42
+ yield
43
+ nil
44
+ rescue Parsanol::ParseFailed => e
45
+ e
46
+ end
47
+
48
+ it "generates error messages correctly" do
49
+ parser = Class.new(Parsanol::Parser) do
50
+ root :num
51
+ rule(:num) { match('[0-9]').repeat(1) }
52
+ end.new
53
+
54
+ error = catch_failed_parse { parser.parse("abc") }
55
+ expect(error).to be_a(Parsanol::ParseFailed)
56
+ expect(error.message).to include("line 1")
57
+ end
58
+
59
+ it "handles multi-line input with correct line numbers" do
60
+ parser = Class.new(Parsanol::Parser) do
61
+ root :lines
62
+ rule(:lines) { line.repeat }
63
+ rule(:line) { match('[0-9]').repeat(1) >> str("\n") }
64
+ end.new
65
+
66
+ error = catch_failed_parse { parser.parse("123\n456\nabc\n") }
67
+ expect(error).to be_a(Parsanol::ParseFailed)
68
+ expect(error.message).to include("line 3")
69
+ end
70
+ end
71
+
72
+ describe "Pool statistics and reuse" do
73
+ it "shows position reuse in pool statistics" do
74
+ source = Parsanol::Source.new("test input for pooling")
75
+ pool = source.position_pool
76
+
77
+ # Create multiple positions
78
+ pos1 = source.position(0)
79
+ pos2 = source.position(5)
80
+ pos3 = source.position(10)
81
+
82
+ # Check that positions were created
83
+ stats = pool.statistics
84
+ expect(stats[:created]).to be >= 3
85
+
86
+ # Release and reuse
87
+ pool.release(pos1)
88
+ pool.release(pos2)
89
+ pool.release(pos3)
90
+
91
+ pos4 = source.position(15)
92
+ pos5 = source.position(20)
93
+
94
+ # Should show reuse
95
+ stats = pool.statistics
96
+ expect(stats[:reused]).to be >= 2
97
+ expect(stats[:utilization]).to be > 0
98
+ end
99
+
100
+ it "pool handles many position creations efficiently" do
101
+ source = Parsanol::Source.new("a" * 1000)
102
+ pool = source.position_pool
103
+
104
+ # Create many positions
105
+ 100.times do |i|
106
+ pos = source.position(i)
107
+ pool.release(pos) if i % 2 == 0 # Release half of them
108
+ end
109
+
110
+ stats = pool.statistics
111
+ # Verify pool is being used
112
+ expect(stats[:created]).to be > 0
113
+ expect(stats[:released]).to be > 0
114
+
115
+ # Check that utilization is reasonable
116
+ # We release 50 and create 50 more in the next phase
117
+ expect(stats[:utilization]).to be >= 0
118
+ end
119
+ end
120
+
121
+ describe "Position object correctness" do
122
+ it "positions maintain correct byte and character positions" do
123
+ # Test with ASCII
124
+ source = Parsanol::Source.new("hello world")
125
+ pos = source.position(6)
126
+
127
+ expect(pos.bytepos).to eq(6)
128
+ expect(pos.charpos).to eq(6) # ASCII: byte == char
129
+ end
130
+
131
+ it "positions work with UTF-8 strings" do
132
+ # Test with UTF-8
133
+ source = Parsanol::Source.new("café")
134
+ pos = source.position(4) # After 'caf'
135
+
136
+ expect(pos.bytepos).to eq(4)
137
+ # charpos calculation may differ based on encoding
138
+ expect(pos.charpos).to be_a(Integer)
139
+ end
140
+
141
+ it "positions track source string correctly" do
142
+ input = "test string"
143
+ source = Parsanol::Source.new(input)
144
+ pos = source.position(5)
145
+
146
+ # Position should reference the original source string
147
+ expect(pos.string).to eq(input)
148
+ end
149
+ end
150
+
151
+ describe "Integration with existing parser" do
152
+ class SimpleParser < Parsanol::Parser
153
+ root :document
154
+ rule(:document) { word.repeat.as(:words) }
155
+ rule(:word) { match('[a-z]').repeat(1).as(:word) >> space.maybe }
156
+ rule(:space) { match('\s').repeat(1) }
157
+ end
158
+
159
+ it "parser works correctly with position pooling" do
160
+ parser = SimpleParser.new
161
+ result = parser.parse("hello world")
162
+
163
+ expect(result).to eq({
164
+ words: [
165
+ { word: "hello" },
166
+ { word: "world" }
167
+ ]
168
+ })
169
+ end
170
+
171
+ it "parser generates errors with position information" do
172
+ parser = SimpleParser.new
173
+
174
+ error = begin
175
+ parser.parse("hello 123")
176
+ rescue Parsanol::ParseFailed => e
177
+ e
178
+ end
179
+
180
+ expect(error).to be_a(Parsanol::ParseFailed)
181
+ expect(error.message).to match(/line \d+ char \d+/)
182
+ end
183
+ end
184
+ end