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,356 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Parsanol::Pools::ArrayPool do
6
+ describe '#initialize' do
7
+ it 'creates a pool with default size' do
8
+ pool = described_class.new
9
+ expect(pool.size).to eq(1000)
10
+ end
11
+
12
+ it 'creates a pool with specified size' do
13
+ pool = described_class.new(size: 500)
14
+ expect(pool.size).to eq(500)
15
+ end
16
+
17
+ it 'pre-allocates Array objects by default' do
18
+ pool = described_class.new(size: 10)
19
+ stats = pool.statistics
20
+ expect(stats[:available]).to eq(10)
21
+ end
22
+
23
+ it 'can disable pre-allocation' do
24
+ pool = described_class.new(size: 10, preallocate: false)
25
+ stats = pool.statistics
26
+ expect(stats[:available]).to eq(0)
27
+ end
28
+ end
29
+
30
+ describe '#acquire' do
31
+ it 'returns an Array instance' do
32
+ pool = described_class.new(size: 10, preallocate: false)
33
+ array = pool.acquire
34
+ expect(array).to be_a(Array)
35
+ end
36
+
37
+ it 'returns an empty array' do
38
+ pool = described_class.new(size: 10, preallocate: true)
39
+ array = pool.acquire
40
+ expect(array).to be_empty
41
+ end
42
+
43
+ it 'reuses arrays from the pool' do
44
+ pool = described_class.new(size: 10, preallocate: true)
45
+ array1 = pool.acquire
46
+ array1_id = array1.object_id
47
+ pool.release(array1)
48
+
49
+ array2 = pool.acquire
50
+ expect(array2.object_id).to eq(array1_id)
51
+ end
52
+
53
+ it 'creates new arrays when pool is empty' do
54
+ pool = described_class.new(size: 2, preallocate: false)
55
+ array1 = pool.acquire
56
+ array2 = pool.acquire
57
+
58
+ stats = pool.statistics
59
+ expect(stats[:created]).to eq(2)
60
+ end
61
+ end
62
+
63
+ describe '#release' do
64
+ it 'returns array to pool' do
65
+ pool = described_class.new(size: 10, preallocate: false)
66
+ array = pool.acquire
67
+
68
+ pool.release(array)
69
+
70
+ stats = pool.statistics
71
+ expect(stats[:available]).to eq(1)
72
+ end
73
+
74
+ it 'clears array before pooling' do
75
+ pool = described_class.new(size: 10, preallocate: false)
76
+ array = pool.acquire
77
+ array << 1 << 2 << 3
78
+
79
+ pool.release(array)
80
+
81
+ # Array should be empty
82
+ expect(array).to be_empty
83
+ end
84
+
85
+ it 'ensures released arrays are reused empty' do
86
+ pool = described_class.new(size: 10, preallocate: false)
87
+
88
+ # First use
89
+ array1 = pool.acquire
90
+ array1 << "a" << "b" << "c"
91
+ pool.release(array1)
92
+
93
+ # Reuse - should be empty
94
+ array2 = pool.acquire
95
+ expect(array2).to be_empty
96
+ expect(array2.object_id).to eq(array1.object_id)
97
+ end
98
+
99
+ it 'discards arrays when pool is full' do
100
+ pool = described_class.new(size: 1, preallocate: true)
101
+
102
+ new_array = [1, 2, 3]
103
+ result = pool.release(new_array)
104
+
105
+ expect(result).to be false
106
+ stats = pool.statistics
107
+ expect(stats[:discarded]).to eq(1)
108
+ end
109
+
110
+ it 'handles arrays with mixed content types' do
111
+ pool = described_class.new(size: 10, preallocate: false)
112
+
113
+ array = pool.acquire
114
+ array << 1 << "string" << :symbol << { key: 'value' }
115
+
116
+ pool.release(array)
117
+
118
+ expect(array).to be_empty
119
+ end
120
+ end
121
+
122
+ describe 'integration with parsing patterns' do
123
+ it 'works for collecting repetition results' do
124
+ pool = described_class.new(size: 5, preallocate: false)
125
+
126
+ # Simulate repetition collection
127
+ result = pool.acquire
128
+ 5.times { |i| result << "item#{i}" }
129
+
130
+ expect(result.size).to eq(5)
131
+ expect(result.first).to eq("item0")
132
+ expect(result.last).to eq("item4")
133
+
134
+ pool.release(result)
135
+
136
+ stats = pool.statistics
137
+ expect(stats[:available]).to eq(1)
138
+ end
139
+
140
+ it 'works for building sequence results' do
141
+ pool = described_class.new(size: 5, preallocate: false)
142
+
143
+ # Simulate sequence building
144
+ sequence = pool.acquire
145
+ sequence << { a: 1 }
146
+ sequence << { b: 2 }
147
+ sequence << { c: 3 }
148
+
149
+ expect(sequence).to eq([{ a: 1 }, { b: 2 }, { c: 3 }])
150
+
151
+ pool.release(sequence)
152
+ end
153
+
154
+ it 'works for accumulating alternatives' do
155
+ pool = described_class.new(size: 5, preallocate: false)
156
+
157
+ # Simulate alternative accumulation
158
+ alternatives = pool.acquire
159
+ alternatives << :choice1
160
+ alternatives << :choice2
161
+
162
+ expect(alternatives).to eq([:choice1, :choice2])
163
+
164
+ pool.release(alternatives)
165
+ end
166
+ end
167
+
168
+ describe 'performance characteristics' do
169
+ it 'shows high reuse rate with pooling' do
170
+ pool = described_class.new(size: 50, preallocate: false)
171
+
172
+ # Create initial arrays
173
+ arrays = 30.times.map do |i|
174
+ arr = pool.acquire
175
+ arr << i
176
+ arr
177
+ end
178
+
179
+ # Release all
180
+ arrays.each { |a| pool.release(a) }
181
+
182
+ # Acquire again - should all be reused
183
+ 30.times { pool.acquire }
184
+
185
+ stats = pool.statistics
186
+ expect(stats[:created]).to eq(30)
187
+ expect(stats[:reused]).to eq(30)
188
+ expect(stats[:utilization]).to eq(50.0)
189
+ end
190
+
191
+ it 'handles rapid acquire/release cycles' do
192
+ pool = described_class.new(size: 10, preallocate: false)
193
+
194
+ 100.times do |i|
195
+ array = pool.acquire
196
+ array << i
197
+ pool.release(array)
198
+ end
199
+
200
+ stats = pool.statistics
201
+ # Should have high reuse, low creation
202
+ expect(stats[:created]).to be <= 10
203
+ expect(stats[:reused]).to be >= 90
204
+ end
205
+
206
+ it 'minimizes allocations with pre-allocation' do
207
+ pool = described_class.new(size: 50, preallocate: true)
208
+
209
+ # Use pre-allocated arrays
210
+ 50.times { pool.acquire }
211
+
212
+ stats = pool.statistics
213
+ expect(stats[:created]).to eq(0) # No new allocations
214
+ expect(stats[:reused]).to eq(50) # All from pool
215
+ end
216
+ end
217
+
218
+ describe 'memory efficiency' do
219
+ it 'reuses array memory for different contents' do
220
+ pool = described_class.new(size: 5, preallocate: false)
221
+
222
+ # First use
223
+ array1 = pool.acquire
224
+ array1 << 1 << 2 << 3
225
+ original_id = array1.object_id
226
+ pool.release(array1)
227
+
228
+ # Second use - different content
229
+ array2 = pool.acquire
230
+ array2 << "a" << "b"
231
+
232
+ expect(array2.object_id).to eq(original_id)
233
+ expect(array2).to eq(["a", "b"])
234
+ end
235
+
236
+ it 'prevents memory leaks from unreleased arrays' do
237
+ pool = described_class.new(size: 5, preallocate: false)
238
+
239
+ # Simulate forgetting to release
240
+ 10.times { pool.acquire }
241
+
242
+ stats = pool.statistics
243
+ expect(stats[:created]).to eq(10)
244
+ expect(stats[:available]).to eq(0)
245
+
246
+ # Pool size unchanged (no leak in pool itself)
247
+ expect(pool.size).to eq(5)
248
+ end
249
+ end
250
+
251
+ describe 'statistics' do
252
+ it 'tracks array-specific operations' do
253
+ pool = described_class.new(size: 5, preallocate: false)
254
+
255
+ # Create 3 arrays
256
+ a1 = pool.acquire
257
+ a2 = pool.acquire
258
+ a3 = pool.acquire
259
+
260
+ # Add content
261
+ a1 << 1
262
+ a2 << 2
263
+ a3 << 3
264
+
265
+ # Release 2
266
+ pool.release(a1)
267
+ pool.release(a2)
268
+
269
+ # Reuse 1
270
+ pool.acquire
271
+
272
+ stats = pool.statistics
273
+ expect(stats[:created]).to eq(3)
274
+ expect(stats[:reused]).to eq(1)
275
+ expect(stats[:released]).to eq(2)
276
+ expect(stats[:available]).to eq(1)
277
+ end
278
+ end
279
+
280
+ describe 'edge cases' do
281
+ it 'handles empty arrays' do
282
+ pool = described_class.new(size: 5, preallocate: false)
283
+ array = pool.acquire
284
+
285
+ expect(array).to be_empty
286
+ pool.release(array)
287
+ expect(array).to be_empty
288
+ end
289
+
290
+ it 'handles large arrays' do
291
+ pool = described_class.new(size: 5, preallocate: false)
292
+ array = pool.acquire
293
+
294
+ 1000.times { |i| array << i }
295
+ expect(array.size).to eq(1000)
296
+
297
+ pool.release(array)
298
+ expect(array).to be_empty
299
+ end
300
+
301
+ it 'handles nested arrays' do
302
+ pool = described_class.new(size: 5, preallocate: false)
303
+ array = pool.acquire
304
+
305
+ array << [1, 2, 3]
306
+ array << [4, 5, 6]
307
+
308
+ expect(array).to eq([[1, 2, 3], [4, 5, 6]])
309
+
310
+ pool.release(array)
311
+ expect(array).to be_empty
312
+ end
313
+
314
+ it 'handles arrays with nil values' do
315
+ pool = described_class.new(size: 5, preallocate: false)
316
+ array = pool.acquire
317
+
318
+ array << nil << nil << 1
319
+
320
+ expect(array).to eq([nil, nil, 1])
321
+
322
+ pool.release(array)
323
+ expect(array).to be_empty
324
+ end
325
+ end
326
+
327
+ describe 'concurrent usage simulation' do
328
+ it 'handles multiple acquire/release patterns' do
329
+ pool = described_class.new(size: 10, preallocate: false)
330
+
331
+ # Pattern 1: Short-lived arrays
332
+ 5.times do
333
+ arr = pool.acquire
334
+ arr << :temp
335
+ pool.release(arr)
336
+ end
337
+
338
+ # Pattern 2: Long-lived arrays
339
+ long_lived = 3.times.map { pool.acquire }
340
+
341
+ # Pattern 3: More short-lived
342
+ 5.times do
343
+ arr = pool.acquire
344
+ arr << :temp2
345
+ pool.release(arr)
346
+ end
347
+
348
+ # Release long-lived
349
+ long_lived.each { |arr| pool.release(arr) }
350
+
351
+ stats = pool.statistics
352
+ expect(stats[:created]).to be <= 10
353
+ expect(stats[:reused]).to be > 0
354
+ end
355
+ end
356
+ end
@@ -0,0 +1,365 @@
1
+ require 'spec_helper'
2
+
3
+ describe Parsanol::Pools::BufferPool do
4
+ let(:pool) { described_class.new(pool_size: 10) }
5
+
6
+ describe '#initialize' do
7
+ it 'creates pools for all size classes' do
8
+ described_class::SIZE_CLASSES.each do |size|
9
+ expect(pool.pools[size]).to be_a(Array)
10
+ expect(pool.pools[size]).to be_empty
11
+ end
12
+ end
13
+
14
+ it 'initializes statistics for all size classes' do
15
+ described_class::SIZE_CLASSES.each do |size|
16
+ expect(pool.stats[size]).to include(
17
+ created: 0,
18
+ reused: 0,
19
+ released: 0,
20
+ discarded: 0
21
+ )
22
+ end
23
+ end
24
+
25
+ it 'uses custom pool size' do
26
+ custom_pool = described_class.new(pool_size: 50)
27
+ expect(custom_pool.instance_variable_get(:@pool_size)).to eq(50)
28
+ end
29
+ end
30
+
31
+ describe '#acquire' do
32
+ it 'returns buffer with appropriate capacity' do
33
+ buffer = pool.acquire(size: 5)
34
+
35
+ expect(buffer).to be_a(Parsanol::Buffer)
36
+ expect(buffer.capacity).to be >= 5
37
+ end
38
+
39
+ it 'selects appropriate size class' do
40
+ # Request size 5 should get size class 8
41
+ buffer = pool.acquire(size: 5)
42
+ expect(buffer.capacity).to eq(8)
43
+
44
+ # Request size 10 should get size class 16
45
+ buffer = pool.acquire(size: 10)
46
+ expect(buffer.capacity).to eq(16)
47
+ end
48
+
49
+ it 'creates new buffer when pool is empty' do
50
+ buffer = pool.acquire(size: 4)
51
+
52
+ expect(pool.stats[4][:created]).to eq(1)
53
+ expect(pool.stats[4][:reused]).to eq(0)
54
+ end
55
+
56
+ it 'reuses buffer when available in pool' do
57
+ # First acquire and release
58
+ buffer1 = pool.acquire(size: 4)
59
+ pool.release(buffer1)
60
+
61
+ # Second acquire should reuse
62
+ buffer2 = pool.acquire(size: 4)
63
+
64
+ expect(buffer2.object_id).to eq(buffer1.object_id)
65
+ expect(pool.stats[4][:reused]).to eq(1)
66
+ end
67
+
68
+ it 'handles power-of-2 size class selection' do
69
+ test_cases = [
70
+ [1, 2],
71
+ [2, 2],
72
+ [3, 4],
73
+ [4, 4],
74
+ [5, 8],
75
+ [8, 8],
76
+ [9, 16],
77
+ [16, 16],
78
+ [17, 32],
79
+ [32, 32],
80
+ [33, 64],
81
+ [64, 64]
82
+ ]
83
+
84
+ test_cases.each do |requested, expected|
85
+ buffer = pool.acquire(size: requested)
86
+ expect(buffer.capacity).to eq(expected),
87
+ "size #{requested} should get capacity #{expected}, got #{buffer.capacity}"
88
+ end
89
+ end
90
+
91
+ it 'handles sizes larger than standard classes' do
92
+ # Size 100 should get next power of 2 (128)
93
+ buffer = pool.acquire(size: 100)
94
+ expect(buffer.capacity).to eq(128)
95
+ end
96
+ end
97
+
98
+ describe '#release' do
99
+ it 'returns buffer to appropriate pool' do
100
+ buffer = pool.acquire(size: 4)
101
+ buffer.push("a")
102
+ buffer.push("b")
103
+
104
+ result = pool.release(buffer)
105
+
106
+ expect(result).to be true
107
+ expect(pool.pools[4].size).to eq(1)
108
+ expect(pool.stats[4][:released]).to eq(1)
109
+ end
110
+
111
+ it 'clears buffer before returning to pool' do
112
+ buffer = pool.acquire(size: 4)
113
+ buffer.push("a")
114
+ buffer.push("b")
115
+
116
+ pool.release(buffer)
117
+
118
+ # Acquire again and verify it's cleared
119
+ buffer2 = pool.acquire(size: 4)
120
+ expect(buffer2.size).to eq(0)
121
+ expect(buffer2.empty?).to be true
122
+ end
123
+
124
+ it 'discards buffer when pool is full' do
125
+ # Acquire 11 buffers (one more than pool size)
126
+ buffers = []
127
+ 11.times do
128
+ buffer = pool.acquire(size: 4)
129
+ buffers << buffer
130
+ end
131
+
132
+ # Release all 11 - first 10 should succeed, 11th should be discarded
133
+ released_count = 0
134
+ discarded_count = 0
135
+
136
+ buffers.each do |buffer|
137
+ if pool.release(buffer)
138
+ released_count += 1
139
+ else
140
+ discarded_count += 1
141
+ end
142
+ end
143
+
144
+ expect(released_count).to eq(10)
145
+ expect(discarded_count).to eq(1)
146
+ expect(pool.stats[4][:discarded]).to eq(1)
147
+ end
148
+
149
+ it 'discards buffer with non-standard size class' do
150
+ # Create buffer with non-standard capacity
151
+ buffer = Parsanol::Buffer.new(capacity: 100)
152
+ result = pool.release(buffer)
153
+
154
+ expect(result).to be false
155
+ end
156
+ end
157
+
158
+ describe '#statistics' do
159
+ before do
160
+ # Create some activity
161
+ 3.times { pool.acquire(size: 2) }
162
+
163
+ buffer = pool.acquire(size: 4)
164
+ pool.release(buffer)
165
+
166
+ buffer = pool.acquire(size: 4) # This should be reused
167
+ end
168
+
169
+ it 'returns statistics for all size classes' do
170
+ stats = pool.statistics
171
+
172
+ described_class::SIZE_CLASSES.each do |size|
173
+ expect(stats).to have_key(size)
174
+ end
175
+ end
176
+
177
+ it 'includes all statistic fields' do
178
+ stats = pool.statistics[2]
179
+
180
+ expect(stats).to include(
181
+ :available,
182
+ :created,
183
+ :reused,
184
+ :released,
185
+ :discarded,
186
+ :utilization
187
+ )
188
+ end
189
+
190
+ it 'calculates utilization correctly' do
191
+ stats = pool.statistics
192
+
193
+ # Size 2: 3 created, 0 reused => 0% utilization
194
+ expect(stats[2][:utilization]).to eq(0.0)
195
+
196
+ # Size 4: 1 created, 1 reused => 50% utilization
197
+ # (first acquire creates, release adds to pool, second acquire reuses)
198
+ expect(stats[4][:utilization]).to eq(50.0)
199
+ end
200
+
201
+ it 'shows available count' do
202
+ stats = pool.statistics
203
+
204
+ # Size 4 has 0 available (one released, one re-acquired)
205
+ expect(stats[4][:available]).to eq(0)
206
+ end
207
+
208
+ it 'tracks created count' do
209
+ stats = pool.statistics
210
+
211
+ expect(stats[2][:created]).to eq(3)
212
+ expect(stats[4][:created]).to eq(1)
213
+ end
214
+
215
+ it 'tracks reused count' do
216
+ stats = pool.statistics
217
+
218
+ expect(stats[2][:reused]).to eq(0)
219
+ expect(stats[4][:reused]).to eq(1)
220
+ end
221
+ end
222
+
223
+ describe '#clear!' do
224
+ before do
225
+ # Create some activity
226
+ buffer = pool.acquire(size: 4)
227
+ pool.release(buffer)
228
+
229
+ buffer = pool.acquire(size: 8)
230
+ pool.release(buffer)
231
+ end
232
+
233
+ it 'clears all pools' do
234
+ pool.clear!
235
+
236
+ described_class::SIZE_CLASSES.each do |size|
237
+ expect(pool.pools[size]).to be_empty
238
+ end
239
+ end
240
+
241
+ it 'resets all statistics' do
242
+ pool.clear!
243
+
244
+ described_class::SIZE_CLASSES.each do |size|
245
+ expect(pool.stats[size]).to eq(
246
+ created: 0,
247
+ reused: 0,
248
+ released: 0,
249
+ discarded: 0
250
+ )
251
+ end
252
+ end
253
+ end
254
+
255
+ describe 'buffer reuse lifecycle' do
256
+ it 'efficiently reuses buffers across acquire/release cycles' do
257
+ # First cycle
258
+ buffer1 = pool.acquire(size: 8)
259
+ buffer1.push("a")
260
+ pool.release(buffer1)
261
+
262
+ # Second cycle - should reuse same buffer
263
+ buffer2 = pool.acquire(size: 8)
264
+ expect(buffer2.object_id).to eq(buffer1.object_id)
265
+ expect(buffer2.empty?).to be true
266
+
267
+ buffer2.push("b")
268
+ pool.release(buffer2)
269
+
270
+ # Third cycle - should still reuse
271
+ buffer3 = pool.acquire(size: 8)
272
+ expect(buffer3.object_id).to eq(buffer1.object_id)
273
+ expect(buffer3.empty?).to be true
274
+
275
+ stats = pool.statistics[8]
276
+ expect(stats[:created]).to eq(1)
277
+ expect(stats[:reused]).to eq(2)
278
+ end
279
+ end
280
+
281
+ describe 'size class selection edge cases' do
282
+ it 'handles exact size class match' do
283
+ described_class::SIZE_CLASSES.each do |size|
284
+ buffer = pool.acquire(size: size)
285
+ expect(buffer.capacity).to eq(size)
286
+ end
287
+ end
288
+
289
+ it 'handles sizes between classes' do
290
+ # 3 is between 2 and 4, should get 4
291
+ buffer = pool.acquire(size: 3)
292
+ expect(buffer.capacity).to eq(4)
293
+
294
+ # 10 is between 8 and 16, should get 16
295
+ buffer = pool.acquire(size: 10)
296
+ expect(buffer.capacity).to eq(16)
297
+ end
298
+
299
+ it 'handles zero and negative sizes' do
300
+ buffer = pool.acquire(size: 0)
301
+ expect(buffer.capacity).to eq(2) # Smallest class
302
+
303
+ buffer = pool.acquire(size: -5)
304
+ expect(buffer.capacity).to eq(2) # Smallest class
305
+ end
306
+
307
+ it 'handles very large sizes' do
308
+ buffer = pool.acquire(size: 1000)
309
+ expect(buffer.capacity).to be >= 1000
310
+ expect(buffer.capacity).to eq(1024) # Next power of 2
311
+ end
312
+ end
313
+
314
+ describe 'pool capacity management' do
315
+ it 'respects pool size limit per class' do
316
+ buffers = []
317
+
318
+ # Acquire and release more than pool_size
319
+ 15.times do
320
+ buffer = pool.acquire(size: 4)
321
+ buffers << buffer
322
+ end
323
+
324
+ # Release all
325
+ released_count = 0
326
+ discarded_count = 0
327
+
328
+ buffers.each do |buffer|
329
+ if pool.release(buffer)
330
+ released_count += 1
331
+ else
332
+ discarded_count += 1
333
+ end
334
+ end
335
+
336
+ expect(released_count).to eq(10) # pool_size
337
+ expect(discarded_count).to eq(5) # overflow
338
+ expect(pool.pools[4].size).to eq(10)
339
+ end
340
+ end
341
+
342
+ describe 'multiple size classes' do
343
+ it 'manages different size classes independently' do
344
+ # Acquire from different classes
345
+ buffer2 = pool.acquire(size: 2)
346
+ buffer4 = pool.acquire(size: 4)
347
+ buffer8 = pool.acquire(size: 8)
348
+
349
+ pool.release(buffer2)
350
+ pool.release(buffer4)
351
+ pool.release(buffer8)
352
+
353
+ # Each size class should have 1 buffer
354
+ expect(pool.pools[2].size).to eq(1)
355
+ expect(pool.pools[4].size).to eq(1)
356
+ expect(pool.pools[8].size).to eq(1)
357
+
358
+ # Statistics should be independent
359
+ stats = pool.statistics
360
+ expect(stats[2][:created]).to eq(1)
361
+ expect(stats[4][:created]).to eq(1)
362
+ expect(stats[8][:created]).to eq(1)
363
+ end
364
+ end
365
+ end