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,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
|