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,391 @@
1
+ require 'spec_helper'
2
+
3
+ describe Parsanol::ResultBuilder do
4
+ let(:context) { Parsanol::Atoms::Context.new }
5
+
6
+ describe '.for' do
7
+ it 'creates RepetitionBuilder for :repetition' do
8
+ builder = described_class.for(:repetition, context)
9
+ expect(builder).to be_a(Parsanol::RepetitionBuilder)
10
+ end
11
+
12
+ it 'creates SequenceBuilder for :sequence' do
13
+ builder = described_class.for(:sequence, context)
14
+ expect(builder).to be_a(Parsanol::SequenceBuilder)
15
+ end
16
+
17
+ it 'creates HashBuilder for :hash' do
18
+ builder = described_class.for(:hash, context)
19
+ expect(builder).to be_a(Parsanol::HashBuilder)
20
+ end
21
+
22
+ it 'raises error for unknown type' do
23
+ expect { described_class.for(:unknown, context) }.to raise_error(ArgumentError, /Unknown builder type/)
24
+ end
25
+
26
+ it 'passes options to builder' do
27
+ builder = described_class.for(:repetition, context, tag: :custom, estimated_size: 20)
28
+ expect(builder.instance_variable_get(:@tag)).to eq(:custom)
29
+ end
30
+ end
31
+
32
+ describe Parsanol::RepetitionBuilder do
33
+ let(:builder) { described_class.new(context, estimated_size: 5) }
34
+
35
+ describe '#initialize' do
36
+ it 'acquires buffer from context' do
37
+ buffer = builder.instance_variable_get(:@buffer)
38
+ expect(buffer).to be_a(Parsanol::Buffer)
39
+ end
40
+
41
+ it 'pushes default tag to buffer' do
42
+ buffer = builder.instance_variable_get(:@buffer)
43
+ expect(buffer[0]).to eq(:repetition)
44
+ end
45
+
46
+ it 'supports custom tags' do
47
+ custom_builder = described_class.new(context, tag: :custom)
48
+ buffer = custom_builder.instance_variable_get(:@buffer)
49
+ expect(buffer[0]).to eq(:custom)
50
+ end
51
+
52
+ it 'uses estimated_size for buffer allocation' do
53
+ # Buffer should be acquired with capacity for tag + elements
54
+ expect(context.buffer_pool).to receive(:acquire).with(size: 6).and_call_original
55
+ described_class.new(context, estimated_size: 5)
56
+ end
57
+ end
58
+
59
+ describe '#add_element' do
60
+ it 'adds element to buffer' do
61
+ builder.add_element("a")
62
+ buffer = builder.instance_variable_get(:@buffer)
63
+ expect(buffer[1]).to eq("a")
64
+ end
65
+
66
+ it 'adds multiple elements' do
67
+ builder.add_element("a")
68
+ builder.add_element("b")
69
+ builder.add_element("c")
70
+ buffer = builder.instance_variable_get(:@buffer)
71
+ expect(buffer.size).to eq(4) # tag + 3 elements
72
+ expect(buffer[1]).to eq("a")
73
+ expect(buffer[2]).to eq("b")
74
+ expect(buffer[3]).to eq("c")
75
+ end
76
+
77
+ it 'returns self for chaining' do
78
+ result = builder.add_element("a")
79
+ expect(result).to eq(builder)
80
+ end
81
+
82
+ it 'can chain multiple adds' do
83
+ builder.add_element("a").add_element("b").add_element("c")
84
+ buffer = builder.instance_variable_get(:@buffer)
85
+ expect(buffer.size).to eq(4)
86
+ end
87
+ end
88
+
89
+ describe '#build' do
90
+ it 'returns LazyResult' do
91
+ builder.add_element("a")
92
+ builder.add_element("b")
93
+ result = builder.build
94
+
95
+ expect(result).to be_a(Parsanol::LazyResult)
96
+ end
97
+
98
+ it 'constructs repetition with tag' do
99
+ builder.add_element("a")
100
+ builder.add_element("b")
101
+ result = builder.build
102
+
103
+ expect(result.to_a).to eq([:repetition, "a", "b"])
104
+ end
105
+
106
+ it 'handles empty repetition' do
107
+ result = builder.build
108
+ expect(result.to_a).to eq([:repetition])
109
+ end
110
+
111
+ it 'uses custom tags' do
112
+ custom_builder = described_class.new(context, tag: :custom, estimated_size: 3)
113
+ custom_builder.add_element("x")
114
+ result = custom_builder.build
115
+
116
+ expect(result.to_a).to eq([:custom, "x"])
117
+ end
118
+
119
+ it 'handles large repetitions' do
120
+ 100.times { |i| builder.add_element(i) }
121
+ result = builder.build
122
+
123
+ expect(result.size).to eq(101) # tag + 100 elements
124
+ expect(result[0]).to eq(:repetition)
125
+ expect(result[100]).to eq(99)
126
+ end
127
+ end
128
+
129
+ describe '#release' do
130
+ it 'releases buffer back to pool' do
131
+ builder.add_element("test")
132
+ buffer = builder.instance_variable_get(:@buffer)
133
+
134
+ expect(context.buffer_pool).to receive(:release).with(buffer).and_call_original
135
+ builder.release
136
+ end
137
+
138
+ it 'clears buffer reference' do
139
+ builder.add_element("test")
140
+ builder.release
141
+
142
+ expect(builder.instance_variable_get(:@buffer)).to be_nil
143
+ end
144
+
145
+ it 'handles double release safely' do
146
+ builder.release
147
+ expect { builder.release }.not_to raise_error
148
+ end
149
+ end
150
+ end
151
+
152
+ describe Parsanol::SequenceBuilder do
153
+ let(:builder) { described_class.new(context, size: 3) }
154
+
155
+ describe '#initialize' do
156
+ it 'acquires buffer from context' do
157
+ buffer = builder.instance_variable_get(:@buffer)
158
+ expect(buffer).to be_a(Parsanol::Buffer)
159
+ end
160
+
161
+ it 'pushes :sequence tag to buffer' do
162
+ buffer = builder.instance_variable_get(:@buffer)
163
+ expect(buffer[0]).to eq(:sequence)
164
+ end
165
+
166
+ it 'uses size for buffer allocation' do
167
+ expect(context.buffer_pool).to receive(:acquire).with(size: 4).and_call_original
168
+ described_class.new(context, size: 3)
169
+ end
170
+ end
171
+
172
+ describe '#add_element' do
173
+ it 'adds element to buffer' do
174
+ builder.add_element("a")
175
+ buffer = builder.instance_variable_get(:@buffer)
176
+ expect(buffer[1]).to eq("a")
177
+ end
178
+
179
+ it 'adds multiple elements' do
180
+ builder.add_element("a")
181
+ builder.add_element("b")
182
+ buffer = builder.instance_variable_get(:@buffer)
183
+ expect(buffer.size).to eq(3) # tag + 2 elements
184
+ expect(buffer[1]).to eq("a")
185
+ expect(buffer[2]).to eq("b")
186
+ end
187
+
188
+ it 'skips nil values' do
189
+ builder.add_element("a")
190
+ builder.add_element(nil)
191
+ builder.add_element("b")
192
+ buffer = builder.instance_variable_get(:@buffer)
193
+ expect(buffer.size).to eq(3) # tag + 2 non-nil elements
194
+ expect(buffer[1]).to eq("a")
195
+ expect(buffer[2]).to eq("b")
196
+ end
197
+
198
+ it 'returns self for chaining' do
199
+ result = builder.add_element("a")
200
+ expect(result).to eq(builder)
201
+ end
202
+ end
203
+
204
+ describe '#build' do
205
+ it 'returns LazyResult' do
206
+ builder.add_element("a")
207
+ result = builder.build
208
+
209
+ expect(result).to be_a(Parsanol::LazyResult)
210
+ end
211
+
212
+ it 'constructs sequence with tag' do
213
+ builder.add_element("a")
214
+ builder.add_element("b")
215
+ result = builder.build
216
+
217
+ expect(result.to_a).to eq([:sequence, "a", "b"])
218
+ end
219
+
220
+ it 'handles empty sequence' do
221
+ result = builder.build
222
+ expect(result.to_a).to eq([:sequence])
223
+ end
224
+
225
+ it 'excludes nil values from result' do
226
+ builder.add_element("a")
227
+ builder.add_element(nil)
228
+ builder.add_element("b")
229
+ result = builder.build
230
+
231
+ expect(result.to_a).to eq([:sequence, "a", "b"])
232
+ end
233
+ end
234
+
235
+ describe '#release' do
236
+ it 'releases buffer back to pool' do
237
+ builder.add_element("test")
238
+ buffer = builder.instance_variable_get(:@buffer)
239
+
240
+ expect(context.buffer_pool).to receive(:release).with(buffer).and_call_original
241
+ builder.release
242
+ end
243
+
244
+ it 'clears buffer reference' do
245
+ builder.add_element("test")
246
+ builder.release
247
+
248
+ expect(builder.instance_variable_get(:@buffer)).to be_nil
249
+ end
250
+ end
251
+ end
252
+
253
+ describe Parsanol::HashBuilder do
254
+ let(:builder) { described_class.new(context) }
255
+
256
+ describe '#initialize' do
257
+ it 'initializes empty hash' do
258
+ hash = builder.instance_variable_get(:@hash)
259
+ expect(hash).to eq({})
260
+ end
261
+ end
262
+
263
+ describe '#add_pair' do
264
+ it 'adds key-value pair' do
265
+ builder.add_pair(:key1, "value1")
266
+ hash = builder.instance_variable_get(:@hash)
267
+ expect(hash).to eq({ key1: "value1" })
268
+ end
269
+
270
+ it 'adds multiple pairs' do
271
+ builder.add_pair(:key1, "value1")
272
+ builder.add_pair(:key2, "value2")
273
+ hash = builder.instance_variable_get(:@hash)
274
+ expect(hash).to eq({ key1: "value1", key2: "value2" })
275
+ end
276
+
277
+ it 'returns self for chaining' do
278
+ result = builder.add_pair(:key, "value")
279
+ expect(result).to eq(builder)
280
+ end
281
+
282
+ it 'can chain multiple adds' do
283
+ builder.add_pair(:a, 1).add_pair(:b, 2).add_pair(:c, 3)
284
+ hash = builder.instance_variable_get(:@hash)
285
+ expect(hash).to eq({ a: 1, b: 2, c: 3 })
286
+ end
287
+
288
+ it 'overwrites existing keys' do
289
+ builder.add_pair(:key, "old")
290
+ builder.add_pair(:key, "new")
291
+ hash = builder.instance_variable_get(:@hash)
292
+ expect(hash).to eq({ key: "new" })
293
+ end
294
+ end
295
+
296
+ describe '#build' do
297
+ it 'returns hash directly' do
298
+ builder.add_pair(:key1, "value1")
299
+ builder.add_pair(:key2, "value2")
300
+ result = builder.build
301
+
302
+ expect(result).to be_a(Hash)
303
+ expect(result).to eq({ key1: "value1", key2: "value2" })
304
+ end
305
+
306
+ it 'handles empty hash' do
307
+ result = builder.build
308
+ expect(result).to eq({})
309
+ end
310
+
311
+ it 'supports complex values' do
312
+ builder.add_pair(:array, ["a", "b"])
313
+ builder.add_pair(:hash, { nested: true })
314
+ builder.add_pair(:number, 42)
315
+ result = builder.build
316
+
317
+ expect(result).to eq({
318
+ array: ["a", "b"],
319
+ hash: { nested: true },
320
+ number: 42
321
+ })
322
+ end
323
+ end
324
+
325
+ describe '#release' do
326
+ it 'clears hash reference' do
327
+ builder.add_pair(:test, "value")
328
+ builder.release
329
+
330
+ expect(builder.instance_variable_get(:@hash)).to be_nil
331
+ end
332
+
333
+ it 'handles double release safely' do
334
+ builder.release
335
+ expect { builder.release }.not_to raise_error
336
+ end
337
+ end
338
+ end
339
+
340
+ describe 'integration with Context' do
341
+ it 'builders use context buffer pool' do
342
+ repetition_builder = Parsanol::RepetitionBuilder.new(context, estimated_size: 5)
343
+ sequence_builder = Parsanol::SequenceBuilder.new(context, size: 3)
344
+
345
+ # Both should acquire from same pool
346
+ stats = context.buffer_pool.statistics
347
+ # Statistics should have size class keys (2, 4, 8, etc.)
348
+ expect(stats.keys).to include(8) # Size 8 is standard size class
349
+ end
350
+
351
+ it 'releases return buffers to pool' do
352
+ builder = Parsanol::RepetitionBuilder.new(context, estimated_size: 5)
353
+ builder.add_element("test")
354
+
355
+ stats_before = context.buffer_pool.statistics
356
+ builder.release
357
+ stats_after = context.buffer_pool.statistics
358
+
359
+ # Buffer should be released (stats should reflect this)
360
+ expect(stats_after).to be_a(Hash)
361
+ end
362
+ end
363
+
364
+ describe 'memory efficiency' do
365
+ it 'reuses buffers across multiple builders' do
366
+ # Create and release first builder
367
+ builder1 = Parsanol::RepetitionBuilder.new(context, estimated_size: 5)
368
+ builder1.add_element("a")
369
+ buffer1 = builder1.instance_variable_get(:@buffer)
370
+ builder1.release
371
+
372
+ # Create second builder - should reuse buffer
373
+ builder2 = Parsanol::RepetitionBuilder.new(context, estimated_size: 5)
374
+ buffer2 = builder2.instance_variable_get(:@buffer)
375
+
376
+ # Buffers come from same size class, demonstrating reuse
377
+ expect(buffer1.capacity).to eq(buffer2.capacity)
378
+ end
379
+
380
+ it 'builders handle growth when capacity exceeded' do
381
+ builder = Parsanol::RepetitionBuilder.new(context, estimated_size: 2)
382
+
383
+ # Add more elements than initial capacity
384
+ 10.times { |i| builder.add_element(i) }
385
+
386
+ result = builder.build
387
+ expect(result.size).to eq(11) # tag + 10 elements
388
+ expect(result.to_a).to eq([:repetition, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
389
+ end
390
+ end
391
+ end
@@ -0,0 +1,54 @@
1
+ require 'spec_helper'
2
+ require 'parsanol/rig/rspec'
3
+
4
+ describe 'rspec integration' do
5
+ include Parsanol
6
+ subject { str('example') }
7
+
8
+ it { should parse('example') }
9
+ it { should_not parse('foo') }
10
+ it { should parse('example').as('example') }
11
+ it { should_not parse('foo').as('example') }
12
+ it { should_not parse('example').as('foo') }
13
+
14
+ it { str('foo').as(:bar).should parse('foo').as({:bar => 'foo'}) }
15
+ it { str('foo').as(:bar).should_not parse('foo').as({:b => 'f'}) }
16
+
17
+ it 'accepts a block to assert more specific details about the parsing output' do
18
+ str('foo').as(:bar).should(parse('foo').as { |output|
19
+ output.should have_key(:bar)
20
+ output.values.first.should == 'foo'
21
+ })
22
+ end
23
+
24
+ # Uncomment to test error messages manually:
25
+ # it { str('foo').should parse('foo', :trace => true).as('bar') }
26
+ # it { str('foo').should parse('food', :trace => true) }
27
+ # it { str('foo').should_not parse('foo', :trace => true).as('foo') }
28
+ # it { str('foo').should_not parse('foo', :trace => true) }
29
+ # it 'accepts a block to assert more specific details about the parsing output' do
30
+ # str('foo').as(:bar).should(parse('foo', :trace => true).as { |output|
31
+ # output.should_not have_key(:bar)
32
+ # })
33
+ # end
34
+
35
+ end
36
+
37
+ describe 'rspec3 syntax' do
38
+ include Parsanol
39
+
40
+ let(:s) { str('example') }
41
+
42
+ it { expect(s).to parse('example') }
43
+ it { expect(s).not_to parse('foo') }
44
+ it { expect(s).to parse('example').as('example') }
45
+ it { expect(s).not_to parse('foo').as('example') }
46
+
47
+ it { expect(s).not_to parse('example').as('foo') }
48
+
49
+ # Uncomment to test error messages manually:
50
+ # it { expect(str('foo')).to parse('foo', :trace => true).as('bar') }
51
+ # it { expect(str('foo')).to parse('food', :trace => true) }
52
+ # it { expect(str('foo')).not_to parse('foo', :trace => true).as('foo') }
53
+ # it { expect(str('foo')).not_to parse('foo', :trace => true) }
54
+ end
@@ -0,0 +1,207 @@
1
+ require 'spec_helper'
2
+
3
+ describe Parsanol::Rope do
4
+ describe '#append' do
5
+ it 'appends strings' do
6
+ rope = described_class.new
7
+ rope.append('hello')
8
+ rope.append(' ')
9
+ rope.append('world')
10
+ expect(rope.to_s).to eq('hello world')
11
+ end
12
+
13
+ it 'appends Slices' do
14
+ rope = described_class.new
15
+ rope.append(Parsanol::Slice.new(0, 'hello'))
16
+ rope.append(Parsanol::Slice.new(5, ' world'))
17
+ expect(rope.to_s).to eq('hello world')
18
+ end
19
+
20
+ it 'appends mixed strings and Slices' do
21
+ rope = described_class.new
22
+ rope.append('hello')
23
+ rope.append(Parsanol::Slice.new(5, ' '))
24
+ rope.append('world')
25
+ expect(rope.to_s).to eq('hello world')
26
+ end
27
+
28
+ it 'returns self for chaining' do
29
+ rope = described_class.new
30
+ result = rope.append('a')
31
+ expect(result).to equal(rope)
32
+ end
33
+
34
+ it 'allows chaining multiple appends' do
35
+ rope = described_class.new
36
+ rope.append('a').append('b').append('c')
37
+ expect(rope.to_s).to eq('abc')
38
+ end
39
+
40
+ it 'freezes after to_s' do
41
+ rope = described_class.new.append('test')
42
+ rope.to_s
43
+ expect { rope.append('more') }.to raise_error(FrozenError)
44
+ end
45
+
46
+ it 'raises FrozenError with descriptive message' do
47
+ rope = described_class.new.append('test')
48
+ rope.to_s
49
+ expect { rope.append('more') }.to raise_error(FrozenError, /frozen Rope/)
50
+ end
51
+
52
+ it 'handles empty string segments' do
53
+ rope = described_class.new
54
+ rope.append('hello')
55
+ rope.append('')
56
+ rope.append('world')
57
+ expect(rope.to_s).to eq('helloworld')
58
+ end
59
+ end
60
+
61
+ describe '#to_s' do
62
+ it 'joins all segments' do
63
+ rope = described_class.new
64
+ rope.append('a').append('b').append('c')
65
+ expect(rope.to_s).to eq('abc')
66
+ end
67
+
68
+ it 'handles empty rope' do
69
+ rope = described_class.new
70
+ expect(rope.to_s).to eq('')
71
+ end
72
+
73
+ it 'handles single segment' do
74
+ rope = described_class.new.append('test')
75
+ expect(rope.to_s).to eq('test')
76
+ end
77
+
78
+ it 'joins Slice objects correctly' do
79
+ rope = described_class.new
80
+ rope.append(Parsanol::Slice.new(0, 'first'))
81
+ rope.append(Parsanol::Slice.new(5, 'second'))
82
+ expect(rope.to_s).to eq('firstsecond')
83
+ end
84
+
85
+ it 'can be called multiple times (idempotent)' do
86
+ rope = described_class.new.append('test')
87
+ result1 = rope.to_s
88
+ result2 = rope.to_s
89
+ expect(result1).to eq('test')
90
+ expect(result2).to eq('test')
91
+ end
92
+
93
+ it 'freezes the rope' do
94
+ rope = described_class.new.append('test')
95
+ expect(rope.to_s).to eq('test')
96
+ expect { rope.append('more') }.to raise_error(FrozenError)
97
+ end
98
+ end
99
+
100
+ describe '#empty?' do
101
+ it 'returns true for new rope' do
102
+ rope = described_class.new
103
+ expect(rope.empty?).to be true
104
+ end
105
+
106
+ it 'returns false after append' do
107
+ rope = described_class.new.append('x')
108
+ expect(rope.empty?).to be false
109
+ end
110
+
111
+ it 'returns false after appending empty string' do
112
+ rope = described_class.new.append('')
113
+ expect(rope.empty?).to be false
114
+ end
115
+
116
+ it 'returns false after appending Slice' do
117
+ rope = described_class.new.append(Parsanol::Slice.new(0, 'x'))
118
+ expect(rope.empty?).to be false
119
+ end
120
+ end
121
+
122
+ describe '#size' do
123
+ it 'returns 0 for empty rope' do
124
+ rope = described_class.new
125
+ expect(rope.size).to eq(0)
126
+ end
127
+
128
+ it 'estimates total size for strings' do
129
+ rope = described_class.new
130
+ rope.append('hello').append('world')
131
+ expect(rope.size).to eq(10)
132
+ end
133
+
134
+ it 'estimates total size for Slices' do
135
+ rope = described_class.new
136
+ rope.append(Parsanol::Slice.new(0, 'hello'))
137
+ rope.append(Parsanol::Slice.new(5, 'world'))
138
+ expect(rope.size).to eq(10)
139
+ end
140
+
141
+ it 'estimates total size for mixed segments' do
142
+ rope = described_class.new
143
+ rope.append('hello')
144
+ rope.append(Parsanol::Slice.new(5, ' '))
145
+ rope.append('world')
146
+ expect(rope.size).to eq(11)
147
+ end
148
+
149
+ it 'handles empty string segments' do
150
+ rope = described_class.new
151
+ rope.append('hello')
152
+ rope.append('')
153
+ rope.append('world')
154
+ expect(rope.size).to eq(10)
155
+ end
156
+ end
157
+
158
+ describe '.from_string' do
159
+ it 'creates rope from string' do
160
+ rope = described_class.from_string('test')
161
+ expect(rope.to_s).to eq('test')
162
+ end
163
+
164
+ it 'handles empty string' do
165
+ rope = described_class.from_string('')
166
+ expect(rope.empty?).to be true
167
+ expect(rope.to_s).to eq('')
168
+ end
169
+
170
+ it 'creates a new rope instance each time' do
171
+ rope1 = described_class.from_string('test')
172
+ rope2 = described_class.from_string('test')
173
+ expect(rope1).not_to equal(rope2)
174
+ end
175
+
176
+ it 'allows further appends' do
177
+ rope = described_class.from_string('hello')
178
+ rope.append(' world')
179
+ expect(rope.to_s).to eq('hello world')
180
+ end
181
+ end
182
+
183
+ describe 'integration scenarios' do
184
+ it 'handles complex concatenation patterns' do
185
+ rope = described_class.new
186
+ 100.times { |i| rope.append(i.to_s) }
187
+ result = rope.to_s
188
+ expected = (0...100).map(&:to_s).join
189
+ expect(result).to eq(expected)
190
+ end
191
+
192
+ it 'handles Unicode strings' do
193
+ rope = described_class.new
194
+ rope.append('Hello')
195
+ rope.append(' ')
196
+ rope.append('世界')
197
+ expect(rope.to_s).to eq('Hello 世界')
198
+ end
199
+
200
+ it 'handles multi-byte characters in size calculation' do
201
+ rope = described_class.new
202
+ rope.append('Hello ')
203
+ rope.append('世界')
204
+ expect(rope.size).to eq(8) # 6 ASCII + 2 multi-byte chars (counted as chars, not bytes)
205
+ end
206
+ end
207
+ end
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+
3
+ describe Parsanol::Scope do
4
+ let(:scope) { described_class.new }
5
+
6
+ describe 'simple store/retrieve' do
7
+ before(:each) { scope[:foo] = :bar }
8
+ it "allows storing objects" do
9
+ scope[:obj] = 42
10
+ end
11
+ it "raises on access of empty slots" do
12
+ expect {
13
+ scope[:empty]
14
+ }.to raise_error(Parsanol::Scope::NotFound)
15
+ end
16
+ it "allows retrieval of stored values" do
17
+ scope[:foo].should == :bar
18
+ end
19
+ end
20
+
21
+ describe 'scoping' do
22
+ before(:each) { scope[:depth] = 1 }
23
+ before(:each) { scope.push }
24
+
25
+ let(:depth) { scope[:depth] }
26
+ subject { depth }
27
+
28
+ it { should == 1 }
29
+ describe 'after a push' do
30
+ before(:each) { scope.push }
31
+ it { should == 1 }
32
+
33
+ describe 'and reassign' do
34
+ before(:each) { scope[:depth] = 2 }
35
+
36
+ it { should == 2 }
37
+
38
+ describe 'and a pop' do
39
+ before(:each) { scope.pop }
40
+ it { should == 1 }
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end