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,345 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
describe Parsanol::StringView do
|
|
6
|
+
let(:input) { "Hello World" }
|
|
7
|
+
|
|
8
|
+
describe '#initialize' do
|
|
9
|
+
it 'creates view with offset and length' do
|
|
10
|
+
view = described_class.new(input, offset: 6, length: 5)
|
|
11
|
+
expect(view.offset).to eq(6)
|
|
12
|
+
expect(view.length).to eq(5)
|
|
13
|
+
expect(view.string).to eq(input)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it 'defaults to full string when no offset given' do
|
|
17
|
+
view = described_class.new(input)
|
|
18
|
+
expect(view.offset).to eq(0)
|
|
19
|
+
expect(view.length).to eq(input.bytesize)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it 'calculates length from offset when not provided' do
|
|
23
|
+
view = described_class.new(input, offset: 6)
|
|
24
|
+
expect(view.length).to eq(5) # "Hello World" - 6 = 5
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it 'accepts explicit length' do
|
|
28
|
+
view = described_class.new(input, offset: 0, length: 5)
|
|
29
|
+
expect(view.length).to eq(5)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it 'does not materialize string on initialization' do
|
|
33
|
+
view = described_class.new(input, offset: 0, length: 5)
|
|
34
|
+
expect(view.inspect).not_to include('cached')
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
describe '#to_s' do
|
|
39
|
+
it 'materializes substring' do
|
|
40
|
+
view = described_class.new(input, offset: 0, length: 5)
|
|
41
|
+
expect(view.to_s).to eq("Hello")
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it 'materializes full string' do
|
|
45
|
+
view = described_class.new(input)
|
|
46
|
+
expect(view.to_s).to eq("Hello World")
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it 'materializes substring from middle' do
|
|
50
|
+
view = described_class.new(input, offset: 6, length: 5)
|
|
51
|
+
expect(view.to_s).to eq("World")
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it 'caches materialized string' do
|
|
55
|
+
view = described_class.new(input, offset: 6, length: 5)
|
|
56
|
+
str1 = view.to_s
|
|
57
|
+
str2 = view.to_s
|
|
58
|
+
expect(str1.object_id).to eq(str2.object_id)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it 'handles empty view' do
|
|
62
|
+
view = described_class.new(input, offset: 0, length: 0)
|
|
63
|
+
expect(view.to_s).to eq("")
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it 'handles single character' do
|
|
67
|
+
view = described_class.new(input, offset: 0, length: 1)
|
|
68
|
+
expect(view.to_s).to eq("H")
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
describe '#[]' do
|
|
73
|
+
it 'accesses character without materialization' do
|
|
74
|
+
view = described_class.new(input, offset: 6, length: 5)
|
|
75
|
+
expect(view[0]).to eq("W")
|
|
76
|
+
expect(view[4]).to eq("d")
|
|
77
|
+
expect(view.inspect).not_to include('cached')
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
it 'returns first character' do
|
|
81
|
+
view = described_class.new(input, offset: 0, length: 5)
|
|
82
|
+
expect(view[0]).to eq("H")
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it 'returns last character' do
|
|
86
|
+
view = described_class.new(input, offset: 0, length: 5)
|
|
87
|
+
expect(view[4]).to eq("o")
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it 'returns nil for out of bounds positive index' do
|
|
91
|
+
view = described_class.new(input, offset: 0, length: 5)
|
|
92
|
+
expect(view[10]).to be_nil
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it 'returns nil for negative index' do
|
|
96
|
+
view = described_class.new(input, offset: 0, length: 5)
|
|
97
|
+
expect(view[-1]).to be_nil
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
it 'returns nil for index at length boundary' do
|
|
101
|
+
view = described_class.new(input, offset: 0, length: 5)
|
|
102
|
+
expect(view[5]).to be_nil
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
it 'works with offset view' do
|
|
106
|
+
view = described_class.new(input, offset: 6, length: 5)
|
|
107
|
+
expect(view[0]).to eq("W")
|
|
108
|
+
expect(view[1]).to eq("o")
|
|
109
|
+
expect(view[2]).to eq("r")
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
describe '#slice' do
|
|
114
|
+
it 'creates substring view without copying' do
|
|
115
|
+
view = described_class.new(input, offset: 0, length: 11)
|
|
116
|
+
sub = view.slice(6, 5)
|
|
117
|
+
expect(sub.to_s).to eq("World")
|
|
118
|
+
expect(sub.offset).to eq(6)
|
|
119
|
+
expect(sub.length).to eq(5)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
it 'shares same string reference' do
|
|
123
|
+
view = described_class.new(input, offset: 0, length: 11)
|
|
124
|
+
sub = view.slice(6, 5)
|
|
125
|
+
expect(sub.string.object_id).to eq(view.string.object_id)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
it 'creates view from middle of view' do
|
|
129
|
+
view = described_class.new(input, offset: 6, length: 5)
|
|
130
|
+
sub = view.slice(1, 3)
|
|
131
|
+
expect(sub.to_s).to eq("orl")
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
it 'handles zero-length slice' do
|
|
135
|
+
view = described_class.new(input, offset: 0, length: 5)
|
|
136
|
+
sub = view.slice(0, 0)
|
|
137
|
+
expect(sub.to_s).to eq("")
|
|
138
|
+
expect(sub.empty?).to be true
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
it 'handles negative length' do
|
|
142
|
+
view = described_class.new(input, offset: 0, length: 5)
|
|
143
|
+
sub = view.slice(0, -1)
|
|
144
|
+
expect(sub.to_s).to eq("")
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
it 'clamps to valid range when slicing beyond end' do
|
|
148
|
+
view = described_class.new(input, offset: 0, length: 5)
|
|
149
|
+
sub = view.slice(3, 10)
|
|
150
|
+
expect(sub.to_s).to eq("lo")
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
it 'returns empty view when start is beyond length' do
|
|
154
|
+
view = described_class.new(input, offset: 0, length: 5)
|
|
155
|
+
sub = view.slice(10, 5)
|
|
156
|
+
expect(sub.to_s).to eq("")
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
describe '#bytesize, #size, #length' do
|
|
161
|
+
it 'returns length in bytes' do
|
|
162
|
+
view = described_class.new(input, offset: 0, length: 5)
|
|
163
|
+
expect(view.bytesize).to eq(5)
|
|
164
|
+
expect(view.size).to eq(5)
|
|
165
|
+
expect(view.length).to eq(5)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
it 'returns zero for empty view' do
|
|
169
|
+
view = described_class.new(input, offset: 0, length: 0)
|
|
170
|
+
expect(view.bytesize).to eq(0)
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
describe '#empty?' do
|
|
175
|
+
it 'returns true for zero-length view' do
|
|
176
|
+
view = described_class.new(input, offset: 0, length: 0)
|
|
177
|
+
expect(view.empty?).to be true
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
it 'returns false for non-empty view' do
|
|
181
|
+
view = described_class.new(input, offset: 0, length: 5)
|
|
182
|
+
expect(view.empty?).to be false
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
describe '#==' do
|
|
187
|
+
it 'compares with String by materializing' do
|
|
188
|
+
view = described_class.new(input, offset: 0, length: 5)
|
|
189
|
+
expect(view == "Hello").to be true
|
|
190
|
+
expect(view == "World").to be false
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
it 'compares two StringViews without materializing' do
|
|
194
|
+
view1 = described_class.new(input, offset: 0, length: 5)
|
|
195
|
+
view2 = described_class.new(input, offset: 0, length: 5)
|
|
196
|
+
expect(view1 == view2).to be true
|
|
197
|
+
expect(view1.inspect).not_to include('cached')
|
|
198
|
+
expect(view2.inspect).not_to include('cached')
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
it 'distinguishes different ranges on same string' do
|
|
202
|
+
view1 = described_class.new(input, offset: 0, length: 5)
|
|
203
|
+
view2 = described_class.new(input, offset: 6, length: 5)
|
|
204
|
+
expect(view1 == view2).to be false
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
it 'distinguishes different strings' do
|
|
208
|
+
other_input = "Hello World".dup # Ensure different object
|
|
209
|
+
view1 = described_class.new(input, offset: 0, length: 5)
|
|
210
|
+
view2 = described_class.new(other_input, offset: 0, length: 5)
|
|
211
|
+
# Verify they are actually different objects
|
|
212
|
+
expect(input.object_id).not_to eq(other_input.object_id)
|
|
213
|
+
# StringViews should not be equal (different string objects)
|
|
214
|
+
expect(view1 == view2).to be false
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
it 'eql? works same as ==' do
|
|
218
|
+
view = described_class.new(input, offset: 0, length: 5)
|
|
219
|
+
expect(view.eql?("Hello")).to be true
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
describe '#hash' do
|
|
224
|
+
it 'returns hash code for hashing' do
|
|
225
|
+
view = described_class.new(input, offset: 0, length: 5)
|
|
226
|
+
expect(view.hash).to be_a(Integer)
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
it 'same views have same hash' do
|
|
230
|
+
view1 = described_class.new(input, offset: 0, length: 5)
|
|
231
|
+
view2 = described_class.new(input, offset: 0, length: 5)
|
|
232
|
+
expect(view1.hash).to eq(view2.hash)
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
it 'different views have different hashes' do
|
|
236
|
+
view1 = described_class.new(input, offset: 0, length: 5)
|
|
237
|
+
view2 = described_class.new(input, offset: 6, length: 5)
|
|
238
|
+
expect(view1.hash).not_to eq(view2.hash)
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
it 'can be used in Hash' do
|
|
242
|
+
view = described_class.new(input, offset: 0, length: 5)
|
|
243
|
+
hash = { view => "value" }
|
|
244
|
+
expect(hash[view]).to eq("value")
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
describe '#inspect' do
|
|
249
|
+
it 'shows offset and length' do
|
|
250
|
+
view = described_class.new(input, offset: 6, length: 5)
|
|
251
|
+
result = view.inspect
|
|
252
|
+
expect(result).to include('StringView')
|
|
253
|
+
expect(result).to include('@offset=6')
|
|
254
|
+
expect(result).to include('@length=5')
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
it 'shows cached status when materialized' do
|
|
258
|
+
view = described_class.new(input, offset: 0, length: 5)
|
|
259
|
+
view.to_s
|
|
260
|
+
result = view.inspect
|
|
261
|
+
expect(result).to include('cached="Hello"')
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
it 'does not show cached when not materialized' do
|
|
265
|
+
view = described_class.new(input, offset: 0, length: 5)
|
|
266
|
+
result = view.inspect
|
|
267
|
+
expect(result).not_to include('cached')
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
describe '#reset!' do
|
|
272
|
+
it 'resets view with new values' do
|
|
273
|
+
view = described_class.new(input, offset: 0, length: 5)
|
|
274
|
+
view.to_s # Materialize
|
|
275
|
+
|
|
276
|
+
new_input = "Goodbye"
|
|
277
|
+
view.reset!(new_input, 0, 4)
|
|
278
|
+
|
|
279
|
+
expect(view.string).to eq(new_input)
|
|
280
|
+
expect(view.offset).to eq(0)
|
|
281
|
+
expect(view.length).to eq(4)
|
|
282
|
+
expect(view.to_s).to eq("Good")
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
it 'clears cached materialization' do
|
|
286
|
+
view = described_class.new(input, offset: 0, length: 5)
|
|
287
|
+
view.to_s # Materialize
|
|
288
|
+
expect(view.inspect).to include('cached')
|
|
289
|
+
|
|
290
|
+
view.reset!(input, 6, 5)
|
|
291
|
+
expect(view.inspect).not_to include('cached')
|
|
292
|
+
expect(view.to_s).to eq("World")
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
it 'returns self for chaining' do
|
|
296
|
+
view = described_class.new(input, offset: 0, length: 5)
|
|
297
|
+
result = view.reset!(input, 6, 5)
|
|
298
|
+
expect(result).to be(view)
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
describe 'UTF-8 support' do
|
|
303
|
+
let(:utf8_input) { "Hello 世界" }
|
|
304
|
+
|
|
305
|
+
it 'handles UTF-8 strings' do
|
|
306
|
+
view = described_class.new(utf8_input, offset: 0, length: utf8_input.bytesize)
|
|
307
|
+
expect(view.to_s).to eq(utf8_input)
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
it 'uses byte offsets not character offsets' do
|
|
311
|
+
# "Hello " is 6 bytes, "世" is 3 bytes, "界" is 3 bytes
|
|
312
|
+
view = described_class.new(utf8_input, offset: 6, length: 6)
|
|
313
|
+
expect(view.to_s).to eq("世界")
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
describe 'zero-copy performance characteristics' do
|
|
318
|
+
it 'does not create strings during slicing operations' do
|
|
319
|
+
view = described_class.new(input, offset: 0, length: 11)
|
|
320
|
+
|
|
321
|
+
# Chain multiple slice operations
|
|
322
|
+
sub1 = view.slice(0, 5)
|
|
323
|
+
sub2 = sub1.slice(0, 3)
|
|
324
|
+
sub3 = sub2.slice(1, 1)
|
|
325
|
+
|
|
326
|
+
# No strings created yet
|
|
327
|
+
expect(view.inspect).not_to include('cached')
|
|
328
|
+
expect(sub1.inspect).not_to include('cached')
|
|
329
|
+
expect(sub2.inspect).not_to include('cached')
|
|
330
|
+
|
|
331
|
+
# Only when we call to_s
|
|
332
|
+
expect(sub3.to_s).to eq("e")
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
it 'shares string reference across all views' do
|
|
336
|
+
view = described_class.new(input, offset: 0, length: 11)
|
|
337
|
+
sub1 = view.slice(0, 5)
|
|
338
|
+
sub2 = sub1.slice(0, 3)
|
|
339
|
+
|
|
340
|
+
expect(view.string.object_id).to eq(input.object_id)
|
|
341
|
+
expect(sub1.string.object_id).to eq(input.object_id)
|
|
342
|
+
expect(sub2.string.object_id).to eq(input.object_id)
|
|
343
|
+
end
|
|
344
|
+
end
|
|
345
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Parsanol::Context do
|
|
4
|
+
def context(*args)
|
|
5
|
+
described_class.new(*args)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
it "binds hash keys as variable like things" do
|
|
9
|
+
context(:a => 'value').instance_eval { a }.
|
|
10
|
+
should == 'value'
|
|
11
|
+
end
|
|
12
|
+
it "one contexts variables aren't the next ones" do
|
|
13
|
+
ca = context(:a => 'b')
|
|
14
|
+
cb = context(:b => 'c')
|
|
15
|
+
|
|
16
|
+
ca.methods.should_not include(:b)
|
|
17
|
+
cb.methods.should_not include(:a)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
describe 'works as a Ruby object should' do
|
|
21
|
+
let(:obj) { context(a: 1) }
|
|
22
|
+
|
|
23
|
+
it 'responds_to? :a' do
|
|
24
|
+
expect(obj.respond_to?(:a)).to be_truthy
|
|
25
|
+
end
|
|
26
|
+
it 'includes :a in #methods' do
|
|
27
|
+
expect(obj.methods).to include(:a)
|
|
28
|
+
end
|
|
29
|
+
it 'allows inspection' do
|
|
30
|
+
expect(obj.inspect).to match(/@a=1/)
|
|
31
|
+
end
|
|
32
|
+
it 'allows conversion to string' do
|
|
33
|
+
expect(obj.to_s).to match(/Parsanol::Context:0x/)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
context 'when the context is enhanced' do
|
|
37
|
+
before(:each) do
|
|
38
|
+
class << obj
|
|
39
|
+
def foo
|
|
40
|
+
'foo'
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it 'responds_to correctly' do
|
|
46
|
+
expect(obj.respond_to?(:foo)).to be_truthy
|
|
47
|
+
end
|
|
48
|
+
it 'includes :foo also in methods' do
|
|
49
|
+
expect(obj.methods).to include(:foo)
|
|
50
|
+
end
|
|
51
|
+
it 'allows calling #foo' do
|
|
52
|
+
expect(obj.foo).to eq('foo')
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
require 'parsanol/parslet'
|
|
4
|
+
|
|
5
|
+
describe Parsanol::Transform do
|
|
6
|
+
include Parsanol
|
|
7
|
+
|
|
8
|
+
let(:transform) { Parsanol::Transform.new }
|
|
9
|
+
|
|
10
|
+
class A < Struct.new(:elt); end
|
|
11
|
+
class B < Struct.new(:elt); end
|
|
12
|
+
class C < Struct.new(:elt); end
|
|
13
|
+
class Bi < Struct.new(:a, :b); end
|
|
14
|
+
|
|
15
|
+
describe "delayed construction" do
|
|
16
|
+
context "given simple(:x) => A.new(x)" do
|
|
17
|
+
before(:each) do
|
|
18
|
+
transform.rule(simple(:x)) { |d| A.new(d[:x]) }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it "should transform 'a' into A.new('a')" do
|
|
22
|
+
transform.apply('a').should == A.new('a')
|
|
23
|
+
end
|
|
24
|
+
it "should transform ['a', 'b'] into [A.new('a'), A.new('b')]" do
|
|
25
|
+
transform.apply(['a', 'b']).should ==
|
|
26
|
+
[A.new('a'), A.new('b')]
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
context "given rules on {:a => simple(:x)} and {:b => :_x}" do
|
|
30
|
+
before(:each) do
|
|
31
|
+
transform.rule(:a => simple(:x)) { |d| A.new(d[:x]) }
|
|
32
|
+
transform.rule(:b => simple(:x)) { |d| B.new(d[:x]) }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it "should transform {:d=>{:b=>'c'}} into d => B('c')" do
|
|
36
|
+
transform.apply({:d=>{:b=>'c'}}).should == {:d => B.new('c')}
|
|
37
|
+
end
|
|
38
|
+
it "should transform {:a=>{:b=>'c'}} into A(B('c'))" do
|
|
39
|
+
transform.apply({:a=>{:b=>'c'}}).should == A.new(B.new('c'))
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
describe "pulling out subbranches" do
|
|
43
|
+
before(:each) do
|
|
44
|
+
transform.rule(:a => {:b => simple(:x)}, :d => {:e => simple(:y)}) { |d|
|
|
45
|
+
Bi.new(*d.values_at(:x, :y))
|
|
46
|
+
}
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it "should yield Bi.new('c', 'f')" do
|
|
50
|
+
transform.apply(:a => {:b => 'c'}, :d => {:e => 'f'}).should ==
|
|
51
|
+
Bi.new('c', 'f')
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
describe "dsl construction" do
|
|
56
|
+
let(:transform) { Parsanol::Transform.new do
|
|
57
|
+
rule(simple(:x)) { A.new(x) }
|
|
58
|
+
end
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
it "should still evaluate rules correctly" do
|
|
62
|
+
transform.apply('a').should == A.new('a')
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
describe "class construction" do
|
|
66
|
+
class OptimusPrime < Parsanol::Transform
|
|
67
|
+
rule(:a => simple(:x)) { A.new(x) }
|
|
68
|
+
rule(:b => simple(:x)) { B.new(x) }
|
|
69
|
+
end
|
|
70
|
+
let(:transform) { OptimusPrime.new }
|
|
71
|
+
|
|
72
|
+
it "should evaluate rules" do
|
|
73
|
+
transform.apply(:a => 'a').should == A.new('a')
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
context "optionally raise when no match found" do
|
|
77
|
+
class BumbleBee < Parsanol::Transform
|
|
78
|
+
def initialize(&block)
|
|
79
|
+
super(raise_on_unmatch: true, &block)
|
|
80
|
+
end
|
|
81
|
+
rule(:a => simple(:x)) { A.new(x) }
|
|
82
|
+
end
|
|
83
|
+
let(:transform) { BumbleBee.new }
|
|
84
|
+
|
|
85
|
+
it "should evaluate rules" do
|
|
86
|
+
transform.apply(:a => 'a').should == A.new('a')
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
it "should raise when no rules are matched" do
|
|
90
|
+
lambda {
|
|
91
|
+
transform.apply(:z => 'z')
|
|
92
|
+
}.should raise_error(NotImplementedError, /Failed to match/)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
context "with inheritance" do
|
|
97
|
+
class OptimusPrimeJunior < OptimusPrime
|
|
98
|
+
rule(:b => simple(:x)) { B.new(x.upcase) }
|
|
99
|
+
rule(:c => simple(:x)) { C.new(x) }
|
|
100
|
+
end
|
|
101
|
+
let(:transform) { OptimusPrimeJunior.new }
|
|
102
|
+
|
|
103
|
+
it "should inherit rules from its parent" do
|
|
104
|
+
transform.apply(:a => 'a').should == A.new('a')
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
it "should be able to override rules from its parent" do
|
|
108
|
+
transform.apply(:b => 'b').should == B.new('B')
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
it "should be able to define new rules" do
|
|
112
|
+
transform.apply(:c => 'c').should == C.new('c')
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
describe "<- #call_on_match" do
|
|
117
|
+
let(:bindings) { { :foo => 'test' } }
|
|
118
|
+
context "when given a block of arity 1" do
|
|
119
|
+
it "should call the block" do
|
|
120
|
+
called = false
|
|
121
|
+
transform.call_on_match(bindings, lambda do |dict|
|
|
122
|
+
called = true
|
|
123
|
+
end)
|
|
124
|
+
|
|
125
|
+
called.should == true
|
|
126
|
+
end
|
|
127
|
+
it "should yield the bindings" do
|
|
128
|
+
transform.call_on_match(bindings, lambda do |dict|
|
|
129
|
+
dict.should == bindings
|
|
130
|
+
end)
|
|
131
|
+
end
|
|
132
|
+
it "should execute in the current context" do
|
|
133
|
+
foo = 'test'
|
|
134
|
+
transform.call_on_match(bindings, lambda do |dict|
|
|
135
|
+
foo.should == 'test'
|
|
136
|
+
end)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
context "when given a block of arity 0" do
|
|
140
|
+
it "should call the block" do
|
|
141
|
+
called = false
|
|
142
|
+
transform.call_on_match(bindings, proc do
|
|
143
|
+
called = true
|
|
144
|
+
end)
|
|
145
|
+
|
|
146
|
+
called.should == true
|
|
147
|
+
end
|
|
148
|
+
it "should have bindings as local variables" do
|
|
149
|
+
transform.call_on_match(bindings, proc do
|
|
150
|
+
foo.should == 'test'
|
|
151
|
+
end)
|
|
152
|
+
end
|
|
153
|
+
it "should execute in its own context" do
|
|
154
|
+
@bar = 'test'
|
|
155
|
+
transform.call_on_match(bindings, proc do
|
|
156
|
+
if instance_variable_defined?("@bar")
|
|
157
|
+
instance_variable_get("@bar").should_not == 'test'
|
|
158
|
+
end
|
|
159
|
+
end)
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
context "various transformations (regression)" do
|
|
165
|
+
context "hashes" do
|
|
166
|
+
it "are matched completely" do
|
|
167
|
+
transform.rule(:a => simple(:x)) { fail }
|
|
168
|
+
transform.apply(:a => 'a', :b => 'b')
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
context "when not using the bindings as hash, but as local variables" do
|
|
174
|
+
it "should access the variables" do
|
|
175
|
+
transform.rule(simple(:x)) { A.new(x) }
|
|
176
|
+
transform.apply('a').should == A.new('a')
|
|
177
|
+
end
|
|
178
|
+
it "should allow context as local variable" do
|
|
179
|
+
transform.rule(simple(:x)) { foo }
|
|
180
|
+
transform.apply('a', :foo => 'bar').should == 'bar'
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|