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.
- checksums.yaml +7 -0
- data/HISTORY.txt +25 -0
- data/LICENSE +23 -0
- data/README.adoc +643 -0
- data/Rakefile +189 -0
- data/example/balanced-parens/basic.rb +42 -0
- data/example/balanced-parens/basic.rb.md +86 -0
- data/example/balanced-parens/parens.rb +42 -0
- data/example/balanced-parens/ruby_transform.rb +162 -0
- data/example/big.erb +73 -0
- data/example/boolean-algebra/basic.rb +70 -0
- data/example/boolean-algebra/basic.rb.md +108 -0
- data/example/boolean-algebra/ruby_transform.rb +263 -0
- data/example/calculator/basic.rb +153 -0
- data/example/calculator/basic.rb.md +120 -0
- data/example/calculator/pattern.rb +153 -0
- data/example/calculator/ruby_transform.rb +156 -0
- data/example/calculator/ruby_transform.rb.md +32 -0
- data/example/calculator/serialized.rb +257 -0
- data/example/calculator/serialized.rb.md +32 -0
- data/example/calculator/transform.rb +153 -0
- data/example/calculator/zero_copy.rb +269 -0
- data/example/calculator/zero_copy.rb.md +36 -0
- data/example/capture/basic.rb +49 -0
- data/example/capture/basic.rb.md +106 -0
- data/example/capture/example.json +39 -0
- data/example/comments/basic.rb +35 -0
- data/example/comments/basic.rb.md +110 -0
- data/example/csv/ruby_transform.rb +148 -0
- data/example/csv/ruby_transform.rb.md +131 -0
- data/example/csv/serialized.rb +201 -0
- data/example/csv/serialized.rb.md +31 -0
- data/example/csv/zero_copy.rb +276 -0
- data/example/csv/zero_copy.rb.md +36 -0
- data/example/custom_atoms/indent_atom.rb +79 -0
- data/example/deepest-errors/basic.rb +131 -0
- data/example/deepest-errors/basic.rb.md +152 -0
- data/example/documentation/basic.rb +18 -0
- data/example/documentation/basic.rb.md +97 -0
- data/example/email/basic.rb +55 -0
- data/example/email/basic.rb.md +102 -0
- data/example/email/ruby_transform.rb +106 -0
- data/example/empty/basic.rb +13 -0
- data/example/empty/basic.rb.md +73 -0
- data/example/empty/example.json +38 -0
- data/example/erb/basic.rb +47 -0
- data/example/erb/basic.rb.md +103 -0
- data/example/erb/optimized.rb +42 -0
- data/example/error-reporting/basic.rb +132 -0
- data/example/error-reporting/basic.rb.md +122 -0
- data/example/expression-evaluator/basic.rb +284 -0
- data/example/expression-evaluator/basic.rb.md +138 -0
- data/example/ini/basic.rb +154 -0
- data/example/ini/basic.rb.md +129 -0
- data/example/ini/ruby_transform.rb +154 -0
- data/example/ip-address/basic.rb +125 -0
- data/example/ip-address/basic.rb.md +139 -0
- data/example/iso-6709/basic.rb +231 -0
- data/example/iso-6709/basic.rb.md +143 -0
- data/example/iso-8601/basic.rb +275 -0
- data/example/iso-8601/basic.rb.md +149 -0
- data/example/json/basic.rb +128 -0
- data/example/json/basic.rb.md +121 -0
- data/example/json/pattern.rb +128 -0
- data/example/json/ruby_transform.rb +200 -0
- data/example/json/ruby_transform.rb.md +32 -0
- data/example/json/serialized.rb +233 -0
- data/example/json/serialized.rb.md +31 -0
- data/example/json/transform.rb +128 -0
- data/example/json/zero_copy.rb +316 -0
- data/example/json/zero_copy.rb.md +36 -0
- data/example/local/basic.rb +34 -0
- data/example/local/basic.rb.md +91 -0
- data/example/local/example.json +38 -0
- data/example/markdown/basic.rb +287 -0
- data/example/markdown/basic.rb.md +160 -0
- data/example/markup/basic.rb +173 -0
- data/example/markup/basic.rb.md +118 -0
- data/example/mathn/basic.rb +47 -0
- data/example/mathn/basic.rb.md +96 -0
- data/example/mathn/example.json +39 -0
- data/example/minilisp/basic.rb +94 -0
- data/example/minilisp/basic.rb.md +133 -0
- data/example/modularity/basic.rb +47 -0
- data/example/modularity/basic.rb.md +152 -0
- data/example/nested-errors/basic.rb +132 -0
- data/example/nested-errors/basic.rb.md +157 -0
- data/example/output/boolean_algebra.out +4 -0
- data/example/output/calc.out +1 -0
- data/example/output/capture.out +3 -0
- data/example/output/comments.out +8 -0
- data/example/output/deepest_errors.out +54 -0
- data/example/output/documentation.err +4 -0
- data/example/output/documentation.out +1 -0
- data/example/output/email_parser.out +2 -0
- data/example/output/empty.err +1 -0
- data/example/output/erb.out +7 -0
- data/example/output/ignore.out +1 -0
- data/example/output/ignore_whitespace.out +1 -0
- data/example/output/ip_address.out +9 -0
- data/example/output/json.out +5 -0
- data/example/output/local.out +3 -0
- data/example/output/mathn.out +4 -0
- data/example/output/minilisp.out +5 -0
- data/example/output/modularity.out +0 -0
- data/example/output/nested_errors.out +54 -0
- data/example/output/optimized_erb.out +1 -0
- data/example/output/parens.out +8 -0
- data/example/output/prec_calc.out +5 -0
- data/example/output/readme.out +1 -0
- data/example/output/scopes.out +1 -0
- data/example/output/seasons.out +28 -0
- data/example/output/sentence.out +1 -0
- data/example/output/simple_xml.out +2 -0
- data/example/output/string_parser.out +3 -0
- data/example/prec-calc/basic.rb +71 -0
- data/example/prec-calc/basic.rb.md +114 -0
- data/example/readme/basic.rb +30 -0
- data/example/readme/basic.rb.md +80 -0
- data/example/scopes/basic.rb +15 -0
- data/example/scopes/basic.rb.md +73 -0
- data/example/scopes/example.json +38 -0
- data/example/seasons/basic.rb +46 -0
- data/example/seasons/basic.rb.md +117 -0
- data/example/seasons/example.json +40 -0
- data/example/sentence/basic.rb +36 -0
- data/example/sentence/basic.rb.md +81 -0
- data/example/sexp/ruby_transform.rb +180 -0
- data/example/sexp/ruby_transform.rb.md +143 -0
- data/example/simple-xml/basic.rb +54 -0
- data/example/simple-xml/basic.rb.md +125 -0
- data/example/simple.lit +3 -0
- data/example/string-literal/basic.rb +77 -0
- data/example/string-literal/basic.rb.md +128 -0
- data/example/test.lit +4 -0
- data/example/toml/basic.rb +226 -0
- data/example/toml/basic.rb.md +173 -0
- data/example/url/basic.rb +219 -0
- data/example/url/basic.rb.md +142 -0
- data/example/url/ruby_transform.rb +219 -0
- data/example/yaml/basic.rb +216 -0
- data/example/yaml/basic.rb.md +148 -0
- data/ext/parsanol_native/extconf.rb +4 -0
- data/lib/parsanol/accelerator/application.rb +62 -0
- data/lib/parsanol/accelerator/engine.rb +112 -0
- data/lib/parsanol/accelerator.rb +162 -0
- data/lib/parsanol/ast_visitor.rb +122 -0
- data/lib/parsanol/atoms/alternative.rb +97 -0
- data/lib/parsanol/atoms/base.rb +214 -0
- data/lib/parsanol/atoms/can_flatten.rb +192 -0
- data/lib/parsanol/atoms/capture.rb +41 -0
- data/lib/parsanol/atoms/context.rb +351 -0
- data/lib/parsanol/atoms/context_optimized.rb +42 -0
- data/lib/parsanol/atoms/custom.rb +110 -0
- data/lib/parsanol/atoms/cut.rb +62 -0
- data/lib/parsanol/atoms/dsl.rb +130 -0
- data/lib/parsanol/atoms/dynamic.rb +33 -0
- data/lib/parsanol/atoms/entity.rb +55 -0
- data/lib/parsanol/atoms/ignored.rb +28 -0
- data/lib/parsanol/atoms/infix.rb +121 -0
- data/lib/parsanol/atoms/lookahead.rb +64 -0
- data/lib/parsanol/atoms/named.rb +50 -0
- data/lib/parsanol/atoms/re.rb +61 -0
- data/lib/parsanol/atoms/repetition.rb +241 -0
- data/lib/parsanol/atoms/scope.rb +28 -0
- data/lib/parsanol/atoms/sequence.rb +157 -0
- data/lib/parsanol/atoms/str.rb +90 -0
- data/lib/parsanol/atoms/visitor.rb +91 -0
- data/lib/parsanol/atoms.rb +36 -0
- data/lib/parsanol/buffer.rb +130 -0
- data/lib/parsanol/builder_callbacks.rb +353 -0
- data/lib/parsanol/cause.rb +101 -0
- data/lib/parsanol/context.rb +23 -0
- data/lib/parsanol/convenience.rb +35 -0
- data/lib/parsanol/edit_tracker.rb +107 -0
- data/lib/parsanol/error_reporter/contextual.rb +122 -0
- data/lib/parsanol/error_reporter/deepest.rb +106 -0
- data/lib/parsanol/error_reporter/tree.rb +68 -0
- data/lib/parsanol/error_reporter.rb +98 -0
- data/lib/parsanol/export.rb +163 -0
- data/lib/parsanol/expression/treetop.rb +94 -0
- data/lib/parsanol/expression.rb +51 -0
- data/lib/parsanol/fast_mode.rb +145 -0
- data/lib/parsanol/first_set.rb +75 -0
- data/lib/parsanol/grammar_builder.rb +177 -0
- data/lib/parsanol/graphviz.rb +97 -0
- data/lib/parsanol/incremental_parser.rb +179 -0
- data/lib/parsanol/interval_tree.rb +215 -0
- data/lib/parsanol/lazy_result.rb +178 -0
- data/lib/parsanol/lexer.rb +146 -0
- data/lib/parsanol/native/parser.rb +630 -0
- data/lib/parsanol/native/serializer.rb +245 -0
- data/lib/parsanol/native/transformer.rb +438 -0
- data/lib/parsanol/native/types.rb +41 -0
- data/lib/parsanol/native.rb +217 -0
- data/lib/parsanol/optimizer.rb +86 -0
- data/lib/parsanol/optimizers/choice_optimizer.rb +78 -0
- data/lib/parsanol/optimizers/cut_inserter.rb +175 -0
- data/lib/parsanol/optimizers/lookahead_optimizer.rb +58 -0
- data/lib/parsanol/optimizers/quantifier_optimizer.rb +62 -0
- data/lib/parsanol/optimizers/sequence_optimizer.rb +97 -0
- data/lib/parsanol/options/ruby_transform.rb +109 -0
- data/lib/parsanol/options/serialized.rb +94 -0
- data/lib/parsanol/options/zero_copy.rb +130 -0
- data/lib/parsanol/options.rb +20 -0
- data/lib/parsanol/parallel.rb +133 -0
- data/lib/parsanol/parsanol_native.bundle +0 -0
- data/lib/parsanol/parser.rb +151 -0
- data/lib/parsanol/parslet.rb +148 -0
- data/lib/parsanol/parslet_native.bundle +0 -0
- data/lib/parsanol/pattern/binding.rb +49 -0
- data/lib/parsanol/pattern.rb +115 -0
- data/lib/parsanol/pool.rb +220 -0
- data/lib/parsanol/pools/array_pool.rb +75 -0
- data/lib/parsanol/pools/buffer_pool.rb +173 -0
- data/lib/parsanol/pools/position_pool.rb +92 -0
- data/lib/parsanol/pools/slice_pool.rb +64 -0
- data/lib/parsanol/position.rb +89 -0
- data/lib/parsanol/result.rb +44 -0
- data/lib/parsanol/result_builder.rb +208 -0
- data/lib/parsanol/result_stream.rb +262 -0
- data/lib/parsanol/rig/rspec.rb +52 -0
- data/lib/parsanol/rope.rb +78 -0
- data/lib/parsanol/scope.rb +42 -0
- data/lib/parsanol/slice.rb +172 -0
- data/lib/parsanol/source/line_cache.rb +99 -0
- data/lib/parsanol/source.rb +171 -0
- data/lib/parsanol/source_location.rb +164 -0
- data/lib/parsanol/streaming_parser.rb +124 -0
- data/lib/parsanol/string_view.rb +192 -0
- data/lib/parsanol/transform.rb +267 -0
- data/lib/parsanol/version.rb +5 -0
- data/lib/parsanol/wasm/README.md +80 -0
- data/lib/parsanol/wasm/package.json +51 -0
- data/lib/parsanol/wasm/parsanol.js +252 -0
- data/lib/parsanol/wasm/parslet.d.ts +129 -0
- data/lib/parsanol/wasm_parser.rb +239 -0
- data/lib/parsanol.rb +408 -0
- data/parsanol-ruby.gemspec +56 -0
- data/spec/acceptance/examples_spec.rb +96 -0
- data/spec/acceptance/infix_parser_spec.rb +145 -0
- data/spec/acceptance/mixing_parsers_spec.rb +74 -0
- data/spec/acceptance/regression_spec.rb +329 -0
- data/spec/acceptance/repetition_and_maybe_spec.rb +44 -0
- data/spec/acceptance/unconsumed_input_spec.rb +21 -0
- data/spec/benchmark/comparative/runner_spec.rb +105 -0
- data/spec/integration/array_pooling_spec.rb +193 -0
- data/spec/integration/buffer_allocation_spec.rb +324 -0
- data/spec/integration/position_pooling_spec.rb +184 -0
- data/spec/integration/result_builder_spec.rb +282 -0
- data/spec/integration/rope_stringview_integration_spec.rb +188 -0
- data/spec/integration/slice_pooling_spec.rb +63 -0
- data/spec/integration/string_view_integration_spec.rb +125 -0
- data/spec/lexer_spec.rb +231 -0
- data/spec/parsanol/atom_results_spec.rb +39 -0
- data/spec/parsanol/atoms/alternative_spec.rb +26 -0
- data/spec/parsanol/atoms/base_spec.rb +127 -0
- data/spec/parsanol/atoms/capture_spec.rb +21 -0
- data/spec/parsanol/atoms/combinations_spec.rb +5 -0
- data/spec/parsanol/atoms/custom_spec.rb +79 -0
- data/spec/parsanol/atoms/dsl_spec.rb +7 -0
- data/spec/parsanol/atoms/entity_spec.rb +77 -0
- data/spec/parsanol/atoms/ignored_spec.rb +15 -0
- data/spec/parsanol/atoms/infix_spec.rb +5 -0
- data/spec/parsanol/atoms/lookahead_spec.rb +22 -0
- data/spec/parsanol/atoms/named_spec.rb +4 -0
- data/spec/parsanol/atoms/re_spec.rb +14 -0
- data/spec/parsanol/atoms/repetition_spec.rb +24 -0
- data/spec/parsanol/atoms/scope_spec.rb +26 -0
- data/spec/parsanol/atoms/sequence_spec.rb +28 -0
- data/spec/parsanol/atoms/str_spec.rb +15 -0
- data/spec/parsanol/atoms/visitor_spec.rb +101 -0
- data/spec/parsanol/atoms_spec.rb +488 -0
- data/spec/parsanol/auto_optimize_spec.rb +334 -0
- data/spec/parsanol/buffer_spec.rb +219 -0
- data/spec/parsanol/builder_callbacks_spec.rb +377 -0
- data/spec/parsanol/choice_optimizer_spec.rb +231 -0
- data/spec/parsanol/convenience_spec.rb +54 -0
- data/spec/parsanol/cut_inserter_spec.rb +248 -0
- data/spec/parsanol/cut_spec.rb +66 -0
- data/spec/parsanol/edit_tracker_spec.rb +218 -0
- data/spec/parsanol/error_reporter/contextual_spec.rb +122 -0
- data/spec/parsanol/error_reporter/deepest_spec.rb +82 -0
- data/spec/parsanol/error_reporter/tree_spec.rb +7 -0
- data/spec/parsanol/export_spec.rb +67 -0
- data/spec/parsanol/expression/treetop_spec.rb +75 -0
- data/spec/parsanol/first_set_spec.rb +298 -0
- data/spec/parsanol/interval_tree_spec.rb +205 -0
- data/spec/parsanol/lazy_result_spec.rb +288 -0
- data/spec/parsanol/lookahead_optimizer_spec.rb +252 -0
- data/spec/parsanol/minilisp.citrus +29 -0
- data/spec/parsanol/minilisp.tt +29 -0
- data/spec/parsanol/optimizer_spec.rb +459 -0
- data/spec/parsanol/options/parslet_compat_spec.rb +166 -0
- data/spec/parsanol/options/ruby_transform_spec.rb +70 -0
- data/spec/parsanol/options/serialized_spec.rb +69 -0
- data/spec/parsanol/options/zero_copy_spec.rb +230 -0
- data/spec/parsanol/parser_spec.rb +36 -0
- data/spec/parsanol/parslet_spec.rb +38 -0
- data/spec/parsanol/pattern_spec.rb +272 -0
- data/spec/parsanol/pool_spec.rb +392 -0
- data/spec/parsanol/pools/array_pool_spec.rb +356 -0
- data/spec/parsanol/pools/buffer_pool_spec.rb +365 -0
- data/spec/parsanol/pools/position_pool_spec.rb +118 -0
- data/spec/parsanol/pools/slice_pool_spec.rb +262 -0
- data/spec/parsanol/position_spec.rb +14 -0
- data/spec/parsanol/result_builder_spec.rb +391 -0
- data/spec/parsanol/rig/rspec_spec.rb +54 -0
- data/spec/parsanol/rope_spec.rb +207 -0
- data/spec/parsanol/scope_spec.rb +45 -0
- data/spec/parsanol/slice_spec.rb +249 -0
- data/spec/parsanol/source/line_cache_spec.rb +74 -0
- data/spec/parsanol/source_spec.rb +207 -0
- data/spec/parsanol/string_view_spec.rb +345 -0
- data/spec/parsanol/transform/context_spec.rb +56 -0
- data/spec/parsanol/transform_spec.rb +183 -0
- data/spec/parsanol/tree_memoization_spec.rb +149 -0
- data/spec/parslet_compatibility/expressir_edge_cases_spec.rb +153 -0
- data/spec/parslet_compatibility/minimal_reproduction.rb +199 -0
- data/spec/parslet_compatibility_spec.rb +399 -0
- data/spec/parslet_imported/atom_spec.rb +93 -0
- data/spec/parslet_imported/combinator_spec.rb +161 -0
- data/spec/parslet_imported/spec_helper.rb +73 -0
- data/spec/performance/batch_parsing_benchmark.rb +129 -0
- data/spec/performance/complete_optimization_summary.rb +143 -0
- data/spec/performance/grammar_caching_analysis.rb +121 -0
- data/spec/performance/grammar_caching_benchmark.rb +80 -0
- data/spec/performance/native_benchmark_spec.rb +230 -0
- data/spec/performance/phase5_benchmark.rb +144 -0
- data/spec/performance/profiling_benchmark.rb +131 -0
- data/spec/performance/ruby_improvements_benchmark.rb +171 -0
- data/spec/performance_spec.rb +374 -0
- data/spec/spec_helper.rb +79 -0
- data/spec/support/opal.rb +8 -0
- data/spec/support/opal.rb.erb +14 -0
- metadata +485 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'parsanol/parslet'
|
|
3
|
+
|
|
4
|
+
# Integration tests for Phase 1.3: Array Buffer Pooling
|
|
5
|
+
# Verifies that ArrayPool is properly integrated into Context and used by atoms
|
|
6
|
+
describe "Array Pooling Integration" do
|
|
7
|
+
describe "Context integration" do
|
|
8
|
+
it "has an array_pool" do
|
|
9
|
+
context = Parsanol::Atoms::Context.new
|
|
10
|
+
expect(context.array_pool).to be_a(Parsanol::Pools::ArrayPool)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it "provides acquire_array helper" do
|
|
14
|
+
context = Parsanol::Atoms::Context.new
|
|
15
|
+
array = context.acquire_array
|
|
16
|
+
expect(array).to be_a(Array)
|
|
17
|
+
expect(array).to be_empty
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it "provides release_array helper" do
|
|
21
|
+
context = Parsanol::Atoms::Context.new
|
|
22
|
+
array = context.acquire_array
|
|
23
|
+
array << 1 << 2 << 3
|
|
24
|
+
result = context.release_array(array)
|
|
25
|
+
expect(result).to be true
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "clears arrays on release" do
|
|
29
|
+
context = Parsanol::Atoms::Context.new
|
|
30
|
+
array = context.acquire_array
|
|
31
|
+
array << 1 << 2 << 3
|
|
32
|
+
context.release_array(array)
|
|
33
|
+
|
|
34
|
+
# Next acquisition should get a cleared array
|
|
35
|
+
array2 = context.acquire_array
|
|
36
|
+
expect(array2).to be_empty
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
describe "Repetition parsing with array pooling" do
|
|
41
|
+
it "reuses arrays during simple repetition" do
|
|
42
|
+
parser = Class.new(Parsanol::Parser) do
|
|
43
|
+
root :items
|
|
44
|
+
rule(:items) { str('x').repeat(5) }
|
|
45
|
+
end.new
|
|
46
|
+
|
|
47
|
+
result = parser.parse("xxxxx")
|
|
48
|
+
expect(result.to_s).to eq("xxxxx")
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it "reuses arrays during repetition with min/max" do
|
|
52
|
+
parser = Class.new(Parsanol::Parser) do
|
|
53
|
+
root :items
|
|
54
|
+
rule(:items) { str('a').repeat(2, 4) }
|
|
55
|
+
end.new
|
|
56
|
+
|
|
57
|
+
result = parser.parse("aaa")
|
|
58
|
+
expect(result.to_s).to eq("aaa")
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it "handles nested repetitions with pooling" do
|
|
62
|
+
parser = Class.new(Parsanol::Parser) do
|
|
63
|
+
root :nested
|
|
64
|
+
rule(:nested) { (str('x') >> str('x')).repeat(3) }
|
|
65
|
+
end.new
|
|
66
|
+
|
|
67
|
+
result = parser.parse("xxxxxx")
|
|
68
|
+
expect(result.to_s).to eq("xxxxxx")
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
it "handles maybe (repeat(0,1)) with pooling" do
|
|
72
|
+
parser = Class.new(Parsanol::Parser) do
|
|
73
|
+
root :optional
|
|
74
|
+
rule(:optional) { str('a').maybe >> str('b') }
|
|
75
|
+
end.new
|
|
76
|
+
|
|
77
|
+
expect(parser.parse("b").to_s).to eq("b")
|
|
78
|
+
expect(parser.parse("ab").to_s).to eq("ab")
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
describe "Sequence parsing with array pooling" do
|
|
83
|
+
it "reuses arrays during simple sequence" do
|
|
84
|
+
parser = Class.new(Parsanol::Parser) do
|
|
85
|
+
root :sequence
|
|
86
|
+
rule(:sequence) { str('a') >> str('b') >> str('c') }
|
|
87
|
+
end.new
|
|
88
|
+
|
|
89
|
+
result = parser.parse("abc")
|
|
90
|
+
expect(result.to_s).to eq("abc")
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
it "reuses arrays during longer sequences" do
|
|
94
|
+
parser = Class.new(Parsanol::Parser) do
|
|
95
|
+
root :sequence
|
|
96
|
+
rule(:sequence) { str('a') >> str('b') >> str('c') >> str('d') >> str('e') }
|
|
97
|
+
end.new
|
|
98
|
+
|
|
99
|
+
result = parser.parse("abcde")
|
|
100
|
+
expect(result.to_s).to eq("abcde")
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
it "handles nested sequences with pooling" do
|
|
104
|
+
parser = Class.new(Parsanol::Parser) do
|
|
105
|
+
root :nested
|
|
106
|
+
rule(:nested) { (str('a') >> str('b')) >> (str('c') >> str('d')) }
|
|
107
|
+
end.new
|
|
108
|
+
|
|
109
|
+
result = parser.parse("abcd")
|
|
110
|
+
expect(result.to_s).to eq("abcd")
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
describe "Complex parsing with array pooling" do
|
|
115
|
+
it "handles mixed repetitions and sequences" do
|
|
116
|
+
parser = Class.new(Parsanol::Parser) do
|
|
117
|
+
root :mixed
|
|
118
|
+
rule(:mixed) { (str('x') >> str('y')).repeat(3) }
|
|
119
|
+
end.new
|
|
120
|
+
|
|
121
|
+
result = parser.parse("xyxyxy")
|
|
122
|
+
expect(result.to_s).to eq("xyxyxy")
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
it "handles repetition of sequences" do
|
|
126
|
+
parser = Class.new(Parsanol::Parser) do
|
|
127
|
+
root :items
|
|
128
|
+
rule(:items) { item.repeat(3) }
|
|
129
|
+
rule(:item) { str('a') >> str('b') >> space? }
|
|
130
|
+
rule(:space?) { str(' ').maybe }
|
|
131
|
+
end.new
|
|
132
|
+
|
|
133
|
+
result = parser.parse("ab ab ab")
|
|
134
|
+
expect(result.to_s).to eq("ab ab ab")
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
describe "Array pool statistics" do
|
|
139
|
+
it "shows pool reuse during parsing" do
|
|
140
|
+
# Create a context and verify pool behavior
|
|
141
|
+
context = Parsanol::Atoms::Context.new
|
|
142
|
+
pool = context.array_pool
|
|
143
|
+
|
|
144
|
+
# ArrayPool is preallocated, so we need to exhaust the pool first
|
|
145
|
+
# to see newly created arrays. For this test, just verify the pool exists
|
|
146
|
+
# and can acquire/release arrays correctly.
|
|
147
|
+
|
|
148
|
+
# Acquire an array
|
|
149
|
+
arr1 = context.acquire_array
|
|
150
|
+
expect(arr1).to be_a(Array)
|
|
151
|
+
expect(arr1).to be_empty
|
|
152
|
+
|
|
153
|
+
# Release it back
|
|
154
|
+
arr1 << 1 << 2 << 3
|
|
155
|
+
result = context.release_array(arr1)
|
|
156
|
+
expect(result).to be true
|
|
157
|
+
|
|
158
|
+
# Acquire again - should get a cleared array
|
|
159
|
+
arr2 = context.acquire_array
|
|
160
|
+
expect(arr2).to be_empty
|
|
161
|
+
|
|
162
|
+
# Verify pool statistics exist
|
|
163
|
+
stats = pool.statistics
|
|
164
|
+
expect(stats).to have_key(:created)
|
|
165
|
+
expect(stats).to have_key(:reused)
|
|
166
|
+
expect(stats).to have_key(:size)
|
|
167
|
+
expect(stats).to have_key(:utilization)
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
describe "Array structure preservation" do
|
|
172
|
+
it "maintains [:repetition, ...] structure" do
|
|
173
|
+
parser = Class.new(Parsanol::Parser) do
|
|
174
|
+
root :items
|
|
175
|
+
rule(:items) { str('x').repeat(3) }
|
|
176
|
+
end.new
|
|
177
|
+
|
|
178
|
+
result = parser.parse("xxx")
|
|
179
|
+
# Result should be a Parsanol::Slice or properly tagged array
|
|
180
|
+
expect(result.to_s).to eq("xxx")
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
it "maintains [:sequence, ...] structure" do
|
|
184
|
+
parser = Class.new(Parsanol::Parser) do
|
|
185
|
+
root :seq
|
|
186
|
+
rule(:seq) { str('a') >> str('b') }
|
|
187
|
+
end.new
|
|
188
|
+
|
|
189
|
+
result = parser.parse("ab")
|
|
190
|
+
expect(result.to_s).to eq("ab")
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'parsanol/parslet'
|
|
3
|
+
|
|
4
|
+
# Integration tests for Phase 2.1: Fixed-Size Buffer Pre-allocation
|
|
5
|
+
# Verifies that BufferPool is properly integrated into Context and accessible
|
|
6
|
+
describe "Buffer Allocation Integration" do
|
|
7
|
+
describe "Context integration" do
|
|
8
|
+
it "has a buffer_pool" do
|
|
9
|
+
context = Parsanol::Atoms::Context.new
|
|
10
|
+
expect(context.buffer_pool).to be_a(Parsanol::Pools::BufferPool)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it "provides acquire_buffer helper" do
|
|
14
|
+
context = Parsanol::Atoms::Context.new
|
|
15
|
+
buffer = context.acquire_buffer(size: 8)
|
|
16
|
+
|
|
17
|
+
expect(buffer).to be_a(Parsanol::Buffer)
|
|
18
|
+
expect(buffer.capacity).to be >= 8
|
|
19
|
+
expect(buffer).to be_empty
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it "provides release_buffer helper" do
|
|
23
|
+
context = Parsanol::Atoms::Context.new
|
|
24
|
+
buffer = context.acquire_buffer(size: 8)
|
|
25
|
+
buffer.push("a")
|
|
26
|
+
buffer.push("b")
|
|
27
|
+
|
|
28
|
+
result = context.release_buffer(buffer)
|
|
29
|
+
expect(result).to be true
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it "clears buffers on release" do
|
|
33
|
+
context = Parsanol::Atoms::Context.new
|
|
34
|
+
|
|
35
|
+
# Acquire and use buffer
|
|
36
|
+
buffer = context.acquire_buffer(size: 4)
|
|
37
|
+
buffer.push("a")
|
|
38
|
+
buffer.push("b")
|
|
39
|
+
buffer.push("c")
|
|
40
|
+
context.release_buffer(buffer)
|
|
41
|
+
|
|
42
|
+
# Next acquisition should get a cleared buffer
|
|
43
|
+
buffer2 = context.acquire_buffer(size: 4)
|
|
44
|
+
expect(buffer2).to be_empty
|
|
45
|
+
expect(buffer2.size).to eq(0)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it "reuses buffers across acquire/release cycles" do
|
|
49
|
+
context = Parsanol::Atoms::Context.new
|
|
50
|
+
|
|
51
|
+
# First cycle
|
|
52
|
+
buffer1 = context.acquire_buffer(size: 8)
|
|
53
|
+
buffer1_id = buffer1.object_id
|
|
54
|
+
context.release_buffer(buffer1)
|
|
55
|
+
|
|
56
|
+
# Second cycle - should reuse same buffer
|
|
57
|
+
buffer2 = context.acquire_buffer(size: 8)
|
|
58
|
+
expect(buffer2.object_id).to eq(buffer1_id)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
describe "Buffer size class selection" do
|
|
63
|
+
let(:context) { Parsanol::Atoms::Context.new }
|
|
64
|
+
|
|
65
|
+
it "selects appropriate size classes" do
|
|
66
|
+
test_cases = [
|
|
67
|
+
[2, 2],
|
|
68
|
+
[3, 4],
|
|
69
|
+
[5, 8],
|
|
70
|
+
[10, 16],
|
|
71
|
+
[20, 32],
|
|
72
|
+
[50, 64]
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
test_cases.each do |requested, expected|
|
|
76
|
+
buffer = context.acquire_buffer(size: requested)
|
|
77
|
+
expect(buffer.capacity).to eq(expected),
|
|
78
|
+
"size #{requested} should get capacity #{expected}, got #{buffer.capacity}"
|
|
79
|
+
context.release_buffer(buffer)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it "handles standard size classes" do
|
|
84
|
+
Parsanol::Pools::BufferPool::SIZE_CLASSES.each do |size|
|
|
85
|
+
buffer = context.acquire_buffer(size: size)
|
|
86
|
+
expect(buffer.capacity).to eq(size)
|
|
87
|
+
context.release_buffer(buffer)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
describe "Buffer pool statistics" do
|
|
93
|
+
it "tracks buffer creation and reuse" do
|
|
94
|
+
context = Parsanol::Atoms::Context.new
|
|
95
|
+
pool = context.buffer_pool
|
|
96
|
+
|
|
97
|
+
# First acquire - creates new buffer
|
|
98
|
+
buffer1 = context.acquire_buffer(size: 8)
|
|
99
|
+
context.release_buffer(buffer1)
|
|
100
|
+
|
|
101
|
+
# Second acquire - reuses buffer
|
|
102
|
+
buffer2 = context.acquire_buffer(size: 8)
|
|
103
|
+
context.release_buffer(buffer2)
|
|
104
|
+
|
|
105
|
+
# Check statistics
|
|
106
|
+
stats = pool.statistics
|
|
107
|
+
expect(stats).to be_a(Hash)
|
|
108
|
+
expect(stats[8]).to include(
|
|
109
|
+
:created,
|
|
110
|
+
:reused,
|
|
111
|
+
:released,
|
|
112
|
+
:utilization
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
expect(stats[8][:created]).to eq(1)
|
|
116
|
+
expect(stats[8][:reused]).to eq(1)
|
|
117
|
+
expect(stats[8][:utilization]).to be > 0
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
it "tracks statistics per size class" do
|
|
121
|
+
context = Parsanol::Atoms::Context.new
|
|
122
|
+
pool = context.buffer_pool
|
|
123
|
+
|
|
124
|
+
# Use different size classes
|
|
125
|
+
buffer2 = context.acquire_buffer(size: 2)
|
|
126
|
+
buffer4 = context.acquire_buffer(size: 4)
|
|
127
|
+
buffer8 = context.acquire_buffer(size: 8)
|
|
128
|
+
|
|
129
|
+
context.release_buffer(buffer2)
|
|
130
|
+
context.release_buffer(buffer4)
|
|
131
|
+
context.release_buffer(buffer8)
|
|
132
|
+
|
|
133
|
+
stats = pool.statistics
|
|
134
|
+
expect(stats[2][:created]).to eq(1)
|
|
135
|
+
expect(stats[4][:created]).to eq(1)
|
|
136
|
+
expect(stats[8][:created]).to eq(1)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
describe "Buffer lifecycle" do
|
|
141
|
+
it "maintains buffer capacity across reuse" do
|
|
142
|
+
context = Parsanol::Atoms::Context.new
|
|
143
|
+
|
|
144
|
+
# Create buffer with capacity 8
|
|
145
|
+
buffer = context.acquire_buffer(size: 8)
|
|
146
|
+
expect(buffer.capacity).to eq(8)
|
|
147
|
+
|
|
148
|
+
# Fill it
|
|
149
|
+
8.times { |i| buffer.push(i) }
|
|
150
|
+
expect(buffer.size).to eq(8)
|
|
151
|
+
|
|
152
|
+
# Release and reacquire
|
|
153
|
+
context.release_buffer(buffer)
|
|
154
|
+
buffer2 = context.acquire_buffer(size: 8)
|
|
155
|
+
|
|
156
|
+
# Capacity should be preserved, size reset
|
|
157
|
+
expect(buffer2.capacity).to eq(8)
|
|
158
|
+
expect(buffer2.size).to eq(0)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
it "handles buffer growth gracefully" do
|
|
162
|
+
context = Parsanol::Atoms::Context.new
|
|
163
|
+
|
|
164
|
+
# Acquire small buffer
|
|
165
|
+
buffer = context.acquire_buffer(size: 4)
|
|
166
|
+
expect(buffer.capacity).to eq(4)
|
|
167
|
+
|
|
168
|
+
# Grow beyond capacity
|
|
169
|
+
10.times { |i| buffer.push(i) }
|
|
170
|
+
expect(buffer.size).to eq(10)
|
|
171
|
+
expect(buffer.capacity).to be > 4
|
|
172
|
+
|
|
173
|
+
# Verify contents
|
|
174
|
+
expect(buffer.to_a).to eq((0..9).to_a)
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
describe "Multiple contexts" do
|
|
179
|
+
it "each context has independent buffer pool" do
|
|
180
|
+
context1 = Parsanol::Atoms::Context.new
|
|
181
|
+
context2 = Parsanol::Atoms::Context.new
|
|
182
|
+
|
|
183
|
+
pool1 = context1.buffer_pool
|
|
184
|
+
pool2 = context2.buffer_pool
|
|
185
|
+
|
|
186
|
+
expect(pool1.object_id).not_to eq(pool2.object_id)
|
|
187
|
+
|
|
188
|
+
# Activity in one doesn't affect the other
|
|
189
|
+
buffer1 = context1.acquire_buffer(size: 8)
|
|
190
|
+
context1.release_buffer(buffer1)
|
|
191
|
+
|
|
192
|
+
stats1 = pool1.statistics[8]
|
|
193
|
+
stats2 = pool2.statistics[8]
|
|
194
|
+
|
|
195
|
+
expect(stats1[:created]).to eq(1)
|
|
196
|
+
expect(stats2[:created]).to eq(0)
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
describe "Buffer operations" do
|
|
201
|
+
let(:context) { Parsanol::Atoms::Context.new }
|
|
202
|
+
|
|
203
|
+
it "supports push operations" do
|
|
204
|
+
buffer = context.acquire_buffer(size: 4)
|
|
205
|
+
|
|
206
|
+
buffer.push("a")
|
|
207
|
+
buffer.push("b")
|
|
208
|
+
|
|
209
|
+
expect(buffer.size).to eq(2)
|
|
210
|
+
expect(buffer.to_a).to eq(["a", "b"])
|
|
211
|
+
|
|
212
|
+
context.release_buffer(buffer)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
it "supports array conversion" do
|
|
216
|
+
buffer = context.acquire_buffer(size: 8)
|
|
217
|
+
|
|
218
|
+
elements = ["x", "y", "z"]
|
|
219
|
+
elements.each { |e| buffer.push(e) }
|
|
220
|
+
|
|
221
|
+
result = buffer.to_a
|
|
222
|
+
expect(result).to eq(elements)
|
|
223
|
+
expect(result).to be_a(Array)
|
|
224
|
+
|
|
225
|
+
context.release_buffer(buffer)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
it "supports indexing" do
|
|
229
|
+
buffer = context.acquire_buffer(size: 8)
|
|
230
|
+
|
|
231
|
+
buffer.push("a")
|
|
232
|
+
buffer.push("b")
|
|
233
|
+
buffer.push("c")
|
|
234
|
+
|
|
235
|
+
expect(buffer[0]).to eq("a")
|
|
236
|
+
expect(buffer[1]).to eq("b")
|
|
237
|
+
expect(buffer[2]).to eq("c")
|
|
238
|
+
|
|
239
|
+
buffer[1] = "x"
|
|
240
|
+
expect(buffer[1]).to eq("x")
|
|
241
|
+
|
|
242
|
+
context.release_buffer(buffer)
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
it "supports empty? check" do
|
|
246
|
+
buffer = context.acquire_buffer(size: 4)
|
|
247
|
+
|
|
248
|
+
expect(buffer.empty?).to be true
|
|
249
|
+
|
|
250
|
+
buffer.push("a")
|
|
251
|
+
expect(buffer.empty?).to be false
|
|
252
|
+
|
|
253
|
+
buffer.clear!
|
|
254
|
+
expect(buffer.empty?).to be true
|
|
255
|
+
|
|
256
|
+
context.release_buffer(buffer)
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
describe "Pool capacity management" do
|
|
261
|
+
it "handles pool overflow gracefully" do
|
|
262
|
+
context = Parsanol::Atoms::Context.new
|
|
263
|
+
pool = context.buffer_pool
|
|
264
|
+
|
|
265
|
+
# Note: BufferPool has pool_size of 100 by default
|
|
266
|
+
# We'll just verify the pool can handle multiple buffer cycles
|
|
267
|
+
|
|
268
|
+
10.times do
|
|
269
|
+
buffer = context.acquire_buffer(size: 8)
|
|
270
|
+
buffer.push("data")
|
|
271
|
+
context.release_buffer(buffer)
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
stats = pool.statistics[8]
|
|
275
|
+
expect(stats[:released]).to be >= 10
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
describe "Performance characteristics" do
|
|
280
|
+
it "buffer reuse reduces allocations" do
|
|
281
|
+
context = Parsanol::Atoms::Context.new
|
|
282
|
+
pool = context.buffer_pool
|
|
283
|
+
|
|
284
|
+
# Clear statistics
|
|
285
|
+
pool.clear!
|
|
286
|
+
|
|
287
|
+
# Perform multiple acquire/release cycles
|
|
288
|
+
cycles = 20
|
|
289
|
+
cycles.times do
|
|
290
|
+
buffer = context.acquire_buffer(size: 8)
|
|
291
|
+
buffer.push("x")
|
|
292
|
+
context.release_buffer(buffer)
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
stats = pool.statistics[8]
|
|
296
|
+
|
|
297
|
+
# Should have high reuse rate
|
|
298
|
+
# First acquire creates, rest reuse
|
|
299
|
+
expect(stats[:created]).to be <= 2
|
|
300
|
+
expect(stats[:reused]).to be >= (cycles - 2)
|
|
301
|
+
expect(stats[:utilization]).to be > 80.0
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
describe "Context pool coexistence" do
|
|
306
|
+
it "buffer_pool works alongside array_pool" do
|
|
307
|
+
context = Parsanol::Atoms::Context.new
|
|
308
|
+
|
|
309
|
+
# Both pools should exist
|
|
310
|
+
expect(context.array_pool).to be_a(Parsanol::Pools::ArrayPool)
|
|
311
|
+
expect(context.buffer_pool).to be_a(Parsanol::Pools::BufferPool)
|
|
312
|
+
|
|
313
|
+
# Both should be functional
|
|
314
|
+
array = context.acquire_array
|
|
315
|
+
buffer = context.acquire_buffer(size: 4)
|
|
316
|
+
|
|
317
|
+
expect(array).to be_a(Array)
|
|
318
|
+
expect(buffer).to be_a(Parsanol::Buffer)
|
|
319
|
+
|
|
320
|
+
context.release_array(array)
|
|
321
|
+
context.release_buffer(buffer)
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
end
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require 'parsanol/parslet'
|
|
5
|
+
|
|
6
|
+
describe "Position Pooling Integration" do
|
|
7
|
+
describe "Source integration" do
|
|
8
|
+
let(:source) { Parsanol::Source.new("hello\nworld\n") }
|
|
9
|
+
|
|
10
|
+
it "has a position_pool" do
|
|
11
|
+
expect(source.position_pool).to be_a(Parsanol::Pools::PositionPool)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it "creates pooled positions" do
|
|
15
|
+
pos1 = source.position(0)
|
|
16
|
+
expect(pos1).to be_a(Parsanol::Position)
|
|
17
|
+
expect(pos1.bytepos).to eq(0)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it "position method works without arguments" do
|
|
21
|
+
source.bytepos = 5
|
|
22
|
+
pos = source.position
|
|
23
|
+
expect(pos.bytepos).to eq(5)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it "positions can be reused from pool" do
|
|
27
|
+
pos1 = source.position(0)
|
|
28
|
+
id1 = pos1.object_id
|
|
29
|
+
|
|
30
|
+
# Release back to pool (simulated by creating another position)
|
|
31
|
+
source.position_pool.release(pos1)
|
|
32
|
+
|
|
33
|
+
# Get another position - should reuse the same object
|
|
34
|
+
pos2 = source.position(10)
|
|
35
|
+
expect(pos2.object_id).to eq(id1)
|
|
36
|
+
expect(pos2.bytepos).to eq(10)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
describe "Error reporting with pooled positions" do
|
|
41
|
+
def catch_failed_parse
|
|
42
|
+
yield
|
|
43
|
+
nil
|
|
44
|
+
rescue Parsanol::ParseFailed => e
|
|
45
|
+
e
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it "generates error messages correctly" do
|
|
49
|
+
parser = Class.new(Parsanol::Parser) do
|
|
50
|
+
root :num
|
|
51
|
+
rule(:num) { match('[0-9]').repeat(1) }
|
|
52
|
+
end.new
|
|
53
|
+
|
|
54
|
+
error = catch_failed_parse { parser.parse("abc") }
|
|
55
|
+
expect(error).to be_a(Parsanol::ParseFailed)
|
|
56
|
+
expect(error.message).to include("line 1")
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it "handles multi-line input with correct line numbers" do
|
|
60
|
+
parser = Class.new(Parsanol::Parser) do
|
|
61
|
+
root :lines
|
|
62
|
+
rule(:lines) { line.repeat }
|
|
63
|
+
rule(:line) { match('[0-9]').repeat(1) >> str("\n") }
|
|
64
|
+
end.new
|
|
65
|
+
|
|
66
|
+
error = catch_failed_parse { parser.parse("123\n456\nabc\n") }
|
|
67
|
+
expect(error).to be_a(Parsanol::ParseFailed)
|
|
68
|
+
expect(error.message).to include("line 3")
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
describe "Pool statistics and reuse" do
|
|
73
|
+
it "shows position reuse in pool statistics" do
|
|
74
|
+
source = Parsanol::Source.new("test input for pooling")
|
|
75
|
+
pool = source.position_pool
|
|
76
|
+
|
|
77
|
+
# Create multiple positions
|
|
78
|
+
pos1 = source.position(0)
|
|
79
|
+
pos2 = source.position(5)
|
|
80
|
+
pos3 = source.position(10)
|
|
81
|
+
|
|
82
|
+
# Check that positions were created
|
|
83
|
+
stats = pool.statistics
|
|
84
|
+
expect(stats[:created]).to be >= 3
|
|
85
|
+
|
|
86
|
+
# Release and reuse
|
|
87
|
+
pool.release(pos1)
|
|
88
|
+
pool.release(pos2)
|
|
89
|
+
pool.release(pos3)
|
|
90
|
+
|
|
91
|
+
pos4 = source.position(15)
|
|
92
|
+
pos5 = source.position(20)
|
|
93
|
+
|
|
94
|
+
# Should show reuse
|
|
95
|
+
stats = pool.statistics
|
|
96
|
+
expect(stats[:reused]).to be >= 2
|
|
97
|
+
expect(stats[:utilization]).to be > 0
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
it "pool handles many position creations efficiently" do
|
|
101
|
+
source = Parsanol::Source.new("a" * 1000)
|
|
102
|
+
pool = source.position_pool
|
|
103
|
+
|
|
104
|
+
# Create many positions
|
|
105
|
+
100.times do |i|
|
|
106
|
+
pos = source.position(i)
|
|
107
|
+
pool.release(pos) if i % 2 == 0 # Release half of them
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
stats = pool.statistics
|
|
111
|
+
# Verify pool is being used
|
|
112
|
+
expect(stats[:created]).to be > 0
|
|
113
|
+
expect(stats[:released]).to be > 0
|
|
114
|
+
|
|
115
|
+
# Check that utilization is reasonable
|
|
116
|
+
# We release 50 and create 50 more in the next phase
|
|
117
|
+
expect(stats[:utilization]).to be >= 0
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
describe "Position object correctness" do
|
|
122
|
+
it "positions maintain correct byte and character positions" do
|
|
123
|
+
# Test with ASCII
|
|
124
|
+
source = Parsanol::Source.new("hello world")
|
|
125
|
+
pos = source.position(6)
|
|
126
|
+
|
|
127
|
+
expect(pos.bytepos).to eq(6)
|
|
128
|
+
expect(pos.charpos).to eq(6) # ASCII: byte == char
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
it "positions work with UTF-8 strings" do
|
|
132
|
+
# Test with UTF-8
|
|
133
|
+
source = Parsanol::Source.new("café")
|
|
134
|
+
pos = source.position(4) # After 'caf'
|
|
135
|
+
|
|
136
|
+
expect(pos.bytepos).to eq(4)
|
|
137
|
+
# charpos calculation may differ based on encoding
|
|
138
|
+
expect(pos.charpos).to be_a(Integer)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
it "positions track source string correctly" do
|
|
142
|
+
input = "test string"
|
|
143
|
+
source = Parsanol::Source.new(input)
|
|
144
|
+
pos = source.position(5)
|
|
145
|
+
|
|
146
|
+
# Position should reference the original source string
|
|
147
|
+
expect(pos.string).to eq(input)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
describe "Integration with existing parser" do
|
|
152
|
+
class SimpleParser < Parsanol::Parser
|
|
153
|
+
root :document
|
|
154
|
+
rule(:document) { word.repeat.as(:words) }
|
|
155
|
+
rule(:word) { match('[a-z]').repeat(1).as(:word) >> space.maybe }
|
|
156
|
+
rule(:space) { match('\s').repeat(1) }
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
it "parser works correctly with position pooling" do
|
|
160
|
+
parser = SimpleParser.new
|
|
161
|
+
result = parser.parse("hello world")
|
|
162
|
+
|
|
163
|
+
expect(result).to eq({
|
|
164
|
+
words: [
|
|
165
|
+
{ word: "hello" },
|
|
166
|
+
{ word: "world" }
|
|
167
|
+
]
|
|
168
|
+
})
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
it "parser generates errors with position information" do
|
|
172
|
+
parser = SimpleParser.new
|
|
173
|
+
|
|
174
|
+
error = begin
|
|
175
|
+
parser.parse("hello 123")
|
|
176
|
+
rescue Parsanol::ParseFailed => e
|
|
177
|
+
e
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
expect(error).to be_a(Parsanol::ParseFailed)
|
|
181
|
+
expect(error.message).to match(/line \d+ char \d+/)
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|