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,282 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'ResultBuilder Integration' do
4
+ include Parsanol
5
+
6
+ let(:context) { Parsanol::Atoms::Context.new }
7
+
8
+ describe 'RepetitionBuilder infrastructure' do
9
+ it 'constructs repetition results using buffers' do
10
+ builder = Parsanol::RepetitionBuilder.new(context, estimated_size: 3)
11
+ builder.add_element('a')
12
+ builder.add_element('b')
13
+ builder.add_element('c')
14
+ result = builder.build
15
+
16
+ # Should be a LazyResult
17
+ expect(result).to be_a(Parsanol::LazyResult)
18
+ expect(result.to_a).to eq([:repetition, 'a', 'b', 'c'])
19
+ end
20
+
21
+ it 'handles variable length results' do
22
+ builder = Parsanol::RepetitionBuilder.new(context, estimated_size: 2)
23
+
24
+ # Add more than estimated
25
+ 10.times { |i| builder.add_element(i) }
26
+ result = builder.build
27
+
28
+ expect(result.size).to eq(11) # tag + 10 elements
29
+ expect(result.to_a[0]).to eq(:repetition)
30
+ expect(result.to_a[10]).to eq(9)
31
+ end
32
+
33
+ it 'builds empty repetitions' do
34
+ builder = Parsanol::RepetitionBuilder.new(context)
35
+ result = builder.build
36
+
37
+ expect(result.to_a).to eq([:repetition])
38
+ end
39
+
40
+ it 'supports custom tags' do
41
+ builder = Parsanol::RepetitionBuilder.new(context, tag: :my_list)
42
+ builder.add_element('x')
43
+ result = builder.build
44
+
45
+ expect(result.to_a).to eq([:my_list, 'x'])
46
+ end
47
+ end
48
+
49
+ describe 'SequenceBuilder infrastructure' do
50
+ it 'constructs sequence results using buffers' do
51
+ builder = Parsanol::SequenceBuilder.new(context, size: 3)
52
+ builder.add_element('a')
53
+ builder.add_element('b')
54
+ builder.add_element('c')
55
+ result = builder.build
56
+
57
+ expect(result).to be_a(Parsanol::LazyResult)
58
+ expect(result.to_a).to eq([:sequence, 'a', 'b', 'c'])
59
+ end
60
+
61
+ it 'filters nil values automatically' do
62
+ builder = Parsanol::SequenceBuilder.new(context, size: 4)
63
+ builder.add_element('a')
64
+ builder.add_element(nil)
65
+ builder.add_element('b')
66
+ builder.add_element(nil)
67
+ builder.add_element('c')
68
+ result = builder.build
69
+
70
+ # Nils should be excluded
71
+ expect(result.to_a).to eq([:sequence, 'a', 'b', 'c'])
72
+ end
73
+
74
+ it 'handles empty sequences' do
75
+ builder = Parsanol::SequenceBuilder.new(context)
76
+ result = builder.build
77
+
78
+ expect(result.to_a).to eq([:sequence])
79
+ end
80
+ end
81
+
82
+ describe 'HashBuilder infrastructure' do
83
+ it 'constructs hash directly without arrays' do
84
+ builder = Parsanol::HashBuilder.new(context)
85
+ builder.add_pair(:name, 'John')
86
+ builder.add_pair(:age, 30)
87
+ result = builder.build
88
+
89
+ expect(result).to be_a(Hash)
90
+ expect(result).to eq({ name: 'John', age: 30 })
91
+ end
92
+
93
+ it 'handles complex values' do
94
+ builder = Parsanol::HashBuilder.new(context)
95
+ builder.add_pair(:array, ['a', 'b', 'c'])
96
+ builder.add_pair(:nested, { key: 'value' })
97
+ result = builder.build
98
+
99
+ expect(result[:array]).to eq(['a', 'b', 'c'])
100
+ expect(result[:nested]).to eq({ key: 'value' })
101
+ end
102
+
103
+ it 'overwrites duplicate keys' do
104
+ builder = Parsanol::HashBuilder.new(context)
105
+ builder.add_pair(:key, 'old')
106
+ builder.add_pair(:key, 'new')
107
+ result = builder.build
108
+
109
+ expect(result).to eq({ key: 'new' })
110
+ end
111
+ end
112
+
113
+ describe 'nested builder usage' do
114
+ it 'builds nested repetition-sequence structures' do
115
+ # Outer repetition
116
+ outer_builder = Parsanol::RepetitionBuilder.new(context, estimated_size: 3)
117
+
118
+ # Inner sequences
119
+ 3.times do
120
+ inner_builder = Parsanol::SequenceBuilder.new(context, size: 2)
121
+ inner_builder.add_element('a')
122
+ inner_builder.add_element('b')
123
+ outer_builder.add_element(inner_builder.build)
124
+ end
125
+
126
+ result = outer_builder.build
127
+ expect(result.size).to eq(4) # tag + 3 sequences
128
+ expect(result[0]).to eq(:repetition)
129
+
130
+ # Each element should be a sequence
131
+ result.to_a[1..3].each do |elem|
132
+ expect(elem).to be_a(Parsanol::LazyResult)
133
+ expect(elem.to_a).to eq([:sequence, 'a', 'b'])
134
+ end
135
+ end
136
+
137
+ it 'builds repetition with hash elements' do
138
+ builder = Parsanol::RepetitionBuilder.new(context, estimated_size: 2)
139
+
140
+ hash_builder1 = Parsanol::HashBuilder.new(context)
141
+ hash_builder1.add_pair(:x, 1)
142
+ builder.add_element(hash_builder1.build)
143
+
144
+ hash_builder2 = Parsanol::HashBuilder.new(context)
145
+ hash_builder2.add_pair(:x, 2)
146
+ builder.add_element(hash_builder2.build)
147
+
148
+ result = builder.build
149
+ expect(result.to_a).to eq([:repetition, { x: 1 }, { x: 2 }])
150
+ end
151
+ end
152
+
153
+ describe 'buffer lifecycle management' do
154
+ it 'releases buffers properly' do
155
+ builder = Parsanol::RepetitionBuilder.new(context, estimated_size: 5)
156
+ builder.add_element('test')
157
+
158
+ # Get buffer reference
159
+ buffer = builder.instance_variable_get(:@buffer)
160
+ expect(buffer).not_to be_nil
161
+
162
+ # Release should clear reference
163
+ builder.release
164
+ expect(builder.instance_variable_get(:@buffer)).to be_nil
165
+ end
166
+
167
+ it 'buffers are reused from pool' do
168
+ # Create and release first builder
169
+ builder1 = Parsanol::RepetitionBuilder.new(context, estimated_size: 8)
170
+ builder1.add_element('a')
171
+ buffer1_capacity = builder1.instance_variable_get(:@buffer).capacity
172
+ builder1.release
173
+
174
+ # Create second builder with same size
175
+ builder2 = Parsanol::RepetitionBuilder.new(context, estimated_size: 8)
176
+ buffer2_capacity = builder2.instance_variable_get(:@buffer).capacity
177
+
178
+ # Should get buffer from same size class
179
+ expect(buffer2_capacity).to eq(buffer1_capacity)
180
+ end
181
+
182
+ it 'handles builder release on failure' do
183
+ builder = Parsanol::RepetitionBuilder.new(context, estimated_size: 3)
184
+ builder.add_element('test')
185
+
186
+ # Simulate failure scenario - release should work
187
+ expect { builder.release }.not_to raise_error
188
+
189
+ # Buffer should be cleared
190
+ expect(builder.instance_variable_get(:@buffer)).to be_nil
191
+ end
192
+ end
193
+
194
+ describe 'performance characteristics' do
195
+ it 'defers materialization with LazyResult' do
196
+ builder = Parsanol::RepetitionBuilder.new(context, estimated_size: 100)
197
+ 100.times { |i| builder.add_element(i) }
198
+ result = builder.build
199
+
200
+ # Result is lazy
201
+ expect(result).to be_a(Parsanol::LazyResult)
202
+ expect(result.instance_variable_get(:@materialized)).to be_nil
203
+
204
+ # Accessing size doesn't materialize
205
+ expect(result.size).to eq(101)
206
+ expect(result.instance_variable_get(:@materialized)).to be_nil
207
+
208
+ # Accessing array materializes
209
+ result.to_a
210
+ expect(result.instance_variable_get(:@materialized)).not_to be_nil
211
+ end
212
+
213
+ it 'handles large structures efficiently' do
214
+ builder = Parsanol::RepetitionBuilder.new(context, estimated_size: 1000)
215
+
216
+ 1000.times { |i| builder.add_element(i) }
217
+ result = builder.build
218
+
219
+ expect(result.size).to eq(1001)
220
+ expect(result.to_a[0]).to eq(:repetition)
221
+ expect(result.to_a[-1]).to eq(999)
222
+ end
223
+
224
+ it 'caches materialized results' do
225
+ builder = Parsanol::RepetitionBuilder.new(context, estimated_size: 50)
226
+ 50.times { |i| builder.add_element(i) }
227
+ result = builder.build
228
+
229
+ # First materialization
230
+ array1 = result.to_a
231
+
232
+ # Second call returns cached
233
+ array2 = result.to_a
234
+ expect(array2.object_id).to eq(array1.object_id)
235
+ end
236
+ end
237
+
238
+ describe 'factory method' do
239
+ it 'creates appropriate builder types' do
240
+ rep_builder = Parsanol::ResultBuilder.for(:repetition, context, estimated_size: 5)
241
+ expect(rep_builder).to be_a(Parsanol::RepetitionBuilder)
242
+
243
+ seq_builder = Parsanol::ResultBuilder.for(:sequence, context, size: 3)
244
+ expect(seq_builder).to be_a(Parsanol::SequenceBuilder)
245
+
246
+ hash_builder = Parsanol::ResultBuilder.for(:hash, context)
247
+ expect(hash_builder).to be_a(Parsanol::HashBuilder)
248
+ end
249
+
250
+ it 'passes options correctly' do
251
+ builder = Parsanol::ResultBuilder.for(:repetition, context, tag: :custom, estimated_size: 10)
252
+ expect(builder.instance_variable_get(:@tag)).to eq(:custom)
253
+ end
254
+ end
255
+
256
+ describe 'compatibility with LazyResult' do
257
+ it 'builders produce results compatible with existing code' do
258
+ builder = Parsanol::RepetitionBuilder.new(context, estimated_size: 3)
259
+ builder.add_element('a')
260
+ builder.add_element('b')
261
+ result = builder.build
262
+
263
+ # Should work as array
264
+ expect(result.size).to eq(3)
265
+ expect(result[0]).to eq(:repetition)
266
+ expect(result.empty?).to be false
267
+
268
+ # Should support enumerable
269
+ mapped = result.map { |x| x }
270
+ expect(mapped).to be_a(Array)
271
+ end
272
+
273
+ it 'results are comparable to arrays' do
274
+ builder = Parsanol::RepetitionBuilder.new(context, estimated_size: 2)
275
+ builder.add_element('x')
276
+ result = builder.build
277
+
278
+ expect(result).to eq([:repetition, 'x'])
279
+ expect([:repetition, 'x']).to eq(result)
280
+ end
281
+ end
282
+ end
@@ -0,0 +1,188 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Rope + StringView Integration' do
4
+ let(:source_str) { 'hello world this is a test' }
5
+
6
+ describe 'Rope with StringView segments' do
7
+ it 'builds rope from StringView segments' do
8
+ rope = Parsanol::Rope.new
9
+
10
+ # Create StringView segments (zero-copy)
11
+ view1 = Parsanol::StringView.new(source_str, offset: 0, length: 5)
12
+ view2 = Parsanol::StringView.new(source_str, offset: 6, length: 5)
13
+
14
+ rope.append(view1)
15
+ rope.append(' ')
16
+ rope.append(view2)
17
+
18
+ expect(rope.to_s).to eq('hello world')
19
+ end
20
+
21
+ it 'builds rope from mixed StringView and String segments' do
22
+ rope = Parsanol::Rope.new
23
+
24
+ view1 = Parsanol::StringView.new(source_str, offset: 0, length: 5)
25
+
26
+ rope.append(view1)
27
+ rope.append(' from ')
28
+ rope.append('rope')
29
+
30
+ expect(rope.to_s).to eq('hello from rope')
31
+ end
32
+
33
+ it 'builds rope from Slices containing StringViews' do
34
+ rope = Parsanol::Rope.new
35
+
36
+ # Create Slices with StringView (as Source.consume does)
37
+ view1 = Parsanol::StringView.new(source_str, offset: 0, length: 5)
38
+ slice1 = Parsanol::Slice.new(0, view1)
39
+
40
+ view2 = Parsanol::StringView.new(source_str, offset: 6, length: 5)
41
+ slice2 = Parsanol::Slice.new(6, view2)
42
+
43
+ rope.append(slice1)
44
+ rope.append(' ')
45
+ rope.append(slice2)
46
+
47
+ expect(rope.to_s).to eq('hello world')
48
+ end
49
+ end
50
+
51
+ describe 'Slice.from_rope with StringView' do
52
+ it 'converts rope to slice' do
53
+ rope = Parsanol::Rope.new
54
+ rope.append('hello')
55
+ rope.append(' ')
56
+ rope.append('world')
57
+
58
+ slice = Parsanol::Slice.from_rope(rope, 0)
59
+
60
+ expect(slice.to_s).to eq('hello world')
61
+ expect(slice.offset).to eq(0)
62
+ end
63
+
64
+ it 'converts rope with StringView segments to slice' do
65
+ rope = Parsanol::Rope.new
66
+
67
+ view1 = Parsanol::StringView.new(source_str, offset: 0, length: 5)
68
+ view2 = Parsanol::StringView.new(source_str, offset: 6, length: 5)
69
+
70
+ rope.append(view1)
71
+ rope.append(' ')
72
+ rope.append(view2)
73
+
74
+ slice = Parsanol::Slice.from_rope(rope, 0)
75
+
76
+ expect(slice.to_s).to eq('hello world')
77
+ expect(slice.str).to eq('hello world')
78
+ end
79
+
80
+ it 'preserves line cache when converting rope to slice' do
81
+ line_cache = double('line_cache')
82
+ allow(line_cache).to receive(:line_and_column).with(0).and_return([1, 1])
83
+
84
+ rope = Parsanol::Rope.new.append('test')
85
+ slice = Parsanol::Slice.from_rope(rope, 0, line_cache)
86
+
87
+ expect(slice.line_and_column).to eq([1, 1])
88
+ end
89
+ end
90
+
91
+ describe 'Rope with UTF-8 StringView segments' do
92
+ let(:utf8_str) { 'Hello 世界 test' }
93
+
94
+ it 'handles UTF-8 StringView segments correctly' do
95
+ rope = Parsanol::Rope.new
96
+
97
+ # UTF-8 segment
98
+ view1 = Parsanol::StringView.new(utf8_str, offset: 0, length: 6)
99
+ view2 = Parsanol::StringView.new(utf8_str, offset: 6, length: 6)
100
+
101
+ rope.append(view1)
102
+ rope.append(view2)
103
+
104
+ result = rope.to_s
105
+ expect(result).to eq('Hello 世界')
106
+ expect(result.encoding).to eq(Encoding::UTF_8)
107
+ end
108
+
109
+ it 'calculates size correctly for UTF-8 content' do
110
+ rope = Parsanol::Rope.new
111
+
112
+ view1 = Parsanol::StringView.new(utf8_str, offset: 0, length: 6)
113
+ rope.append(view1)
114
+
115
+ # Size based on string length, not byte length
116
+ expect(rope.size).to eq(6)
117
+ end
118
+ end
119
+
120
+ describe 'Performance characteristics' do
121
+ it 'defers materialization until to_s called' do
122
+ rope = Parsanol::Rope.new
123
+
124
+ # Create views (no string materialization yet)
125
+ view1 = Parsanol::StringView.new(source_str, offset: 0, length: 5)
126
+ view2 = Parsanol::StringView.new(source_str, offset: 6, length: 5)
127
+
128
+ rope.append(view1)
129
+ rope.append(view2)
130
+
131
+ # Verify views haven't materialized yet
132
+ expect(view1.instance_variable_get(:@materialized)).to be_nil
133
+ expect(view2.instance_variable_get(:@materialized)).to be_nil
134
+
135
+ # Materialize only on to_s
136
+ result = rope.to_s
137
+
138
+ # Now views are materialized as part of joining
139
+ expect(result).to eq('helloworld')
140
+ end
141
+
142
+ it 'handles large number of StringView segments efficiently' do
143
+ rope = Parsanol::Rope.new
144
+
145
+ # Append 100 small segments
146
+ 100.times do |i|
147
+ offset = i * 2
148
+ view = Parsanol::StringView.new('ab' * 100, offset: offset, length: 2)
149
+ rope.append(view)
150
+ end
151
+
152
+ result = rope.to_s
153
+ expect(result.length).to eq(200)
154
+ end
155
+ end
156
+
157
+ describe 'Edge cases' do
158
+ it 'handles empty StringView segments' do
159
+ rope = Parsanol::Rope.new
160
+
161
+ view1 = Parsanol::StringView.new(source_str, offset: 0, length: 0)
162
+ rope.append(view1)
163
+ rope.append('test')
164
+
165
+ expect(rope.to_s).to eq('test')
166
+ end
167
+
168
+ it 'handles rope with only StringView segments' do
169
+ rope = Parsanol::Rope.new
170
+
171
+ view1 = Parsanol::StringView.new(source_str, offset: 0, length: 5)
172
+ view2 = Parsanol::StringView.new(source_str, offset: 6, length: 5)
173
+ view3 = Parsanol::StringView.new(source_str, offset: 12, length: 4)
174
+
175
+ rope.append(view1).append(view2).append(view3)
176
+
177
+ expect(rope.to_s).to eq('helloworldthis')
178
+ end
179
+
180
+ it 'handles empty rope' do
181
+ rope = Parsanol::Rope.new
182
+ slice = Parsanol::Slice.from_rope(rope, 0)
183
+
184
+ expect(slice.to_s).to eq('')
185
+ expect(slice.size).to eq(0)
186
+ end
187
+ end
188
+ end
@@ -0,0 +1,63 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Slice Pooling Integration" do
4
+ it "source has a slice_pool" do
5
+ source = Parsanol::Source.new("hello world")
6
+ expect(source.slice_pool).to be_a(Parsanol::Pools::SlicePool)
7
+ end
8
+
9
+ it "source.consume uses pooled slices" do
10
+ source = Parsanol::Source.new("hello")
11
+
12
+ # Consume should use the pool
13
+ slice1 = source.consume(1)
14
+ expect(slice1).to be_a(Parsanol::Slice)
15
+ expect(slice1.to_s).to eq("h")
16
+
17
+ # Pool should show usage (created + reused)
18
+ stats = source.slice_pool.statistics
19
+ total_usage = stats[:created] + stats[:reused]
20
+ expect(total_usage).to be > 0
21
+ end
22
+
23
+ it "source.slice helper creates pooled slices" do
24
+ source = Parsanol::Source.new("test")
25
+
26
+ slice = source.slice(0, "test")
27
+ expect(slice).to be_a(Parsanol::Slice)
28
+ expect(slice.to_s).to eq("test")
29
+
30
+ stats = source.slice_pool.statistics
31
+ total_usage = stats[:created] + stats[:reused]
32
+ expect(total_usage).to be > 0
33
+ end
34
+
35
+ it "reuses slices during repetitive parsing" do
36
+ parser = Class.new(Parsanol::Parser) do
37
+ root :letters
38
+ rule(:letters) { str('a').repeat(10) }
39
+ end.new
40
+
41
+ result = parser.parse("aaaaaaaaaa")
42
+
43
+ # Verify the parser works with pooling
44
+ # The parse succeeded which means pooling worked
45
+ expect(result).to be_a(Parsanol::Slice)
46
+ expect(result.to_s).to eq("aaaaaaaaaa")
47
+ end
48
+
49
+ it "handles complex parsing with pooling" do
50
+ parser = Class.new(Parsanol::Parser) do
51
+ root :expression
52
+ rule(:expression) {
53
+ (str('x') | str('y')).repeat(5) >>
54
+ str('!')
55
+ }
56
+ end.new
57
+
58
+ result = parser.parse("xyxyx!")
59
+ # Result is flattened, so verify parse succeeded
60
+ expect(result).to be_a(Parsanol::Slice)
61
+ expect(result.to_s).to eq("xyxyx!")
62
+ end
63
+ end
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe 'StringView Integration' do
6
+ include Parsanol
7
+
8
+ describe 'basic parsing with StringView' do
9
+ it 'parses simple string' do
10
+ parser = str('hello')
11
+ result = parser.parse('hello')
12
+
13
+ expect(result).to be_a(Parsanol::Slice)
14
+ expect(result.to_s).to eq('hello')
15
+ expect(result.offset).to eq(0)
16
+ end
17
+
18
+ it 'parses sequences' do
19
+ parser = str('a') >> str('b') >> str('c')
20
+ result = parser.parse('abc')
21
+
22
+ # Sequences get flattened to a single slice by default
23
+ expect(result.to_s).to eq('abc')
24
+ end
25
+
26
+ it 'parses alternatives' do
27
+ parser = str('hello') | str('world')
28
+
29
+ result1 = parser.parse('hello')
30
+ expect(result1.to_s).to eq('hello')
31
+
32
+ result2 = parser.parse('world')
33
+ expect(result2.to_s).to eq('world')
34
+ end
35
+
36
+ it 'parses character classes' do
37
+ parser = match['a-z'].repeat(5, 5)
38
+ result = parser.parse('hello')
39
+
40
+ # Repetitions with same min/max get flattened
41
+ expect(result.to_s).to eq('hello')
42
+ end
43
+
44
+ it 'parses small repetitions' do
45
+ parser = str('a').repeat(3, 3)
46
+ result = parser.parse('aaa')
47
+
48
+ # Repetitions with same min/max get flattened
49
+ expect(result.to_s).to eq('aaa')
50
+ end
51
+ end
52
+
53
+ describe 'backward compatibility' do
54
+ it 'Slice#str materializes string from StringView' do
55
+ parser = str('test')
56
+ result = parser.parse('test')
57
+
58
+ expect(result.str).to eq('test')
59
+ expect(result.str).to be_a(String)
60
+ end
61
+
62
+ it 'Slice#to_s works with StringView' do
63
+ parser = str('test')
64
+ result = parser.parse('test')
65
+
66
+ expect(result.to_s).to eq('test')
67
+ end
68
+
69
+ it 'Slice comparison works with StringView' do
70
+ parser = str('test')
71
+ result = parser.parse('test')
72
+
73
+ expect(result).to eq('test')
74
+ expect(result == 'test').to be true
75
+ end
76
+
77
+ it 'Slice concatenation works with StringView' do
78
+ parser = str('a').as(:a) >> str('b').as(:b)
79
+ result = parser.parse('ab')
80
+
81
+ # Access via hash keys
82
+ concatenated = result[:a] + result[:b]
83
+ expect(concatenated.to_s).to eq('ab')
84
+ end
85
+ end
86
+
87
+ describe 'UTF-8 support' do
88
+ it 'handles UTF-8 strings correctly' do
89
+ parser = str('世界')
90
+ result = parser.parse('世界')
91
+
92
+ expect(result.to_s).to eq('世界')
93
+ end
94
+
95
+ it 'handles mixed ASCII and UTF-8' do
96
+ parser = str('hello').as(:en) >> str('世界').as(:jp)
97
+ result = parser.parse('hello世界')
98
+
99
+ expect(result[:en].to_s).to eq('hello')
100
+ expect(result[:jp].to_s).to eq('世界')
101
+ end
102
+ end
103
+
104
+ describe 'memory efficiency' do
105
+ it 'caches materialized strings' do
106
+ parser = str('test')
107
+ result = parser.parse('test')
108
+
109
+ # Calling str multiple times should return same object
110
+ str1 = result.str
111
+ str2 = result.str
112
+ expect(str1.object_id).to eq(str2.object_id)
113
+ end
114
+ end
115
+
116
+ describe 'line and column tracking' do
117
+ it 'tracks position correctly' do
118
+ parser = str('hello')
119
+ result = parser.parse('hello')
120
+
121
+ expect(result.line_and_column).to eq([1, 1])
122
+ expect(result.offset).to eq(0)
123
+ end
124
+ end
125
+ end