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,248 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Parsanol::Optimizers::CutInserter do
|
|
4
|
+
include Parsanol
|
|
5
|
+
|
|
6
|
+
let(:inserter) { Parsanol::Optimizers::CutInserter.new }
|
|
7
|
+
|
|
8
|
+
describe "basic functionality" do
|
|
9
|
+
it "returns parslet unchanged if not an alternative" do
|
|
10
|
+
parslet = str('hello')
|
|
11
|
+
result = inserter.optimize(parslet)
|
|
12
|
+
expect(result.to_s).to include('hello')
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it "returns sequence unchanged if no alternatives inside" do
|
|
16
|
+
parslet = str('a') >> str('b')
|
|
17
|
+
result = inserter.optimize(parslet)
|
|
18
|
+
# Note: Phase 24 may optimize this to str('ab')
|
|
19
|
+
expect(result).to be_a(Parsanol::Atoms::Base)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
describe "disjoint alternatives" do
|
|
24
|
+
it "inserts cuts for simple disjoint alternatives" do
|
|
25
|
+
parslet = str('if') | str('while') | str('print')
|
|
26
|
+
result = inserter.optimize(parslet)
|
|
27
|
+
|
|
28
|
+
# Should insert cuts in each alternative
|
|
29
|
+
expect(result).to be_a(Parsanol::Atoms::Alternative)
|
|
30
|
+
result.alternatives.each do |alt|
|
|
31
|
+
# Each alternative should be wrapped with cut
|
|
32
|
+
expect(alt).to be_a(Parsanol::Atoms::Cut)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it "inserts cuts for two disjoint alternatives" do
|
|
37
|
+
parslet = str('yes') | str('no')
|
|
38
|
+
result = inserter.optimize(parslet)
|
|
39
|
+
|
|
40
|
+
expect(result).to be_a(Parsanol::Atoms::Alternative)
|
|
41
|
+
expect(result.alternatives.size).to eq(2)
|
|
42
|
+
result.alternatives.each do |alt|
|
|
43
|
+
expect(alt).to be_a(Parsanol::Atoms::Cut)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it "inserts cuts after deterministic prefix in sequences" do
|
|
48
|
+
# Note: Phase 24 may concatenate adjacent strings
|
|
49
|
+
# So str('if') >> str(' ') >> str('x') may become str('if x')
|
|
50
|
+
parslet = (str('if') >> str(' ') >> str('x')) |
|
|
51
|
+
(str('while') >> str(' ') >> str('y'))
|
|
52
|
+
result = inserter.optimize(parslet)
|
|
53
|
+
|
|
54
|
+
expect(result).to be_a(Parsanol::Atoms::Alternative)
|
|
55
|
+
# Each alternative should be wrapped with cut
|
|
56
|
+
# (May be Cut wrapping whole thing if Phase 24 concatenated strings)
|
|
57
|
+
result.alternatives.each do |alt|
|
|
58
|
+
expect(alt).to be_a(Parsanol::Atoms::Cut)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
describe "overlapping alternatives" do
|
|
64
|
+
it "does not insert cuts when FIRST sets overlap" do
|
|
65
|
+
# Same atom in both alternatives - not disjoint
|
|
66
|
+
atom = str('same')
|
|
67
|
+
parslet = atom | atom
|
|
68
|
+
result = inserter.optimize(parslet)
|
|
69
|
+
|
|
70
|
+
# Should not insert cuts
|
|
71
|
+
expect(result).to be_a(Parsanol::Atoms::Alternative)
|
|
72
|
+
result.alternatives.each do |alt|
|
|
73
|
+
expect(alt).not_to be_a(Parsanol::Atoms::Cut)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
describe "EPSILON handling" do
|
|
79
|
+
it "does not cut after parslets with EPSILON in FIRST set" do
|
|
80
|
+
# str('a').maybe has EPSILON in FIRST set
|
|
81
|
+
parslet = (str('a').maybe >> str('b')) |
|
|
82
|
+
(str('c') >> str('d'))
|
|
83
|
+
result = inserter.optimize(parslet)
|
|
84
|
+
|
|
85
|
+
# First alternative has EPSILON, so shouldn't have cut at start
|
|
86
|
+
expect(result).to be_a(Parsanol::Atoms::Alternative)
|
|
87
|
+
first_alt = result.alternatives[0]
|
|
88
|
+
# The sequence may be optimized but should still have structure
|
|
89
|
+
# Second alternative should be wrapped with cut since 'cd' is deterministic
|
|
90
|
+
second_alt = result.alternatives[1]
|
|
91
|
+
expect(second_alt).to be_a(Parsanol::Atoms::Cut)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
it "cuts after non-EPSILON prefix even if later elements have EPSILON" do
|
|
95
|
+
# str('if') doesn't have EPSILON, safe to cut after it
|
|
96
|
+
parslet = (str('if') >> str('x').maybe >> str('y')) |
|
|
97
|
+
(str('while') >> str('z'))
|
|
98
|
+
result = inserter.optimize(parslet)
|
|
99
|
+
|
|
100
|
+
expect(result).to be_a(Parsanol::Atoms::Alternative)
|
|
101
|
+
# First alternative should have cut after 'if'
|
|
102
|
+
first_alt = result.alternatives[0]
|
|
103
|
+
expect(first_alt).to be_a(Parsanol::Atoms::Sequence)
|
|
104
|
+
# First element should be a cut wrapping 'if'
|
|
105
|
+
expect(first_alt.parslets.first).to be_a(Parsanol::Atoms::Cut)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
describe "nested alternatives" do
|
|
110
|
+
it "recursively optimizes nested alternatives" do
|
|
111
|
+
# Outer alternative: 'a' | (inner alternative)
|
|
112
|
+
# Inner alternative: 'b' | 'c'
|
|
113
|
+
inner = str('b') | str('c')
|
|
114
|
+
outer = str('a') | inner
|
|
115
|
+
|
|
116
|
+
result = inserter.optimize(outer)
|
|
117
|
+
|
|
118
|
+
expect(result).to be_a(Parsanol::Atoms::Alternative)
|
|
119
|
+
# Outer should have 2 alternatives
|
|
120
|
+
expect(result.alternatives.size).to eq(2)
|
|
121
|
+
|
|
122
|
+
# First should be wrapped with cut
|
|
123
|
+
expect(result.alternatives[0]).to be_a(Parsanol::Atoms::Cut)
|
|
124
|
+
|
|
125
|
+
# Second alternative should be the optimized inner alternative
|
|
126
|
+
# It will be a Cut-wrapped Alternative
|
|
127
|
+
second = result.alternatives[1]
|
|
128
|
+
# The inner alternative was optimized and wrapped
|
|
129
|
+
# Structure: outer sees flattened alternatives OR nested structure
|
|
130
|
+
# Just verify both alternatives have cuts applied somewhere
|
|
131
|
+
expect(result.to_s).to include('↑')
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
describe "repetitions" do
|
|
136
|
+
it "recursively optimizes alternatives inside repetitions" do
|
|
137
|
+
parslet = (str('a') | str('b')).repeat(1, 3)
|
|
138
|
+
result = inserter.optimize(parslet)
|
|
139
|
+
|
|
140
|
+
expect(result).to be_a(Parsanol::Atoms::Repetition)
|
|
141
|
+
# The inner alternative should be optimized
|
|
142
|
+
inner = result.parslet
|
|
143
|
+
expect(inner).to be_a(Parsanol::Atoms::Alternative)
|
|
144
|
+
inner.alternatives.each do |alt|
|
|
145
|
+
expect(alt).to be_a(Parsanol::Atoms::Cut)
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
describe "named atoms" do
|
|
151
|
+
it "recursively optimizes named alternatives" do
|
|
152
|
+
parslet = (str('x') | str('y')).as(:choice)
|
|
153
|
+
result = inserter.optimize(parslet)
|
|
154
|
+
|
|
155
|
+
expect(result).to be_a(Parsanol::Atoms::Named)
|
|
156
|
+
expect(result.name).to eq(:choice)
|
|
157
|
+
|
|
158
|
+
# The wrapped alternative should be optimized
|
|
159
|
+
inner = result.parslet
|
|
160
|
+
expect(inner).to be_a(Parsanol::Atoms::Alternative)
|
|
161
|
+
inner.alternatives.each do |alt|
|
|
162
|
+
expect(alt).to be_a(Parsanol::Atoms::Cut)
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
describe "complex grammars" do
|
|
168
|
+
it "optimizes statement-like grammar" do
|
|
169
|
+
# Simulates: if_stmt | while_stmt | print_stmt
|
|
170
|
+
# Each statement has keyword followed by other stuff
|
|
171
|
+
# Note: Phase 24 may concatenate these into single strings
|
|
172
|
+
if_stmt = str('if') >> str(' ') >> str('condition')
|
|
173
|
+
while_stmt = str('while') >> str(' ') >> str('condition')
|
|
174
|
+
print_stmt = str('print') >> str(' ') >> str('expr')
|
|
175
|
+
|
|
176
|
+
parslet = if_stmt | while_stmt | print_stmt
|
|
177
|
+
result = inserter.optimize(parslet)
|
|
178
|
+
|
|
179
|
+
expect(result).to be_a(Parsanol::Atoms::Alternative)
|
|
180
|
+
expect(result.alternatives.size).to eq(3)
|
|
181
|
+
|
|
182
|
+
# Each alternative should have a cut somewhere
|
|
183
|
+
# The exact structure depends on Phase 24 optimization
|
|
184
|
+
result.alternatives.each do |alt|
|
|
185
|
+
# Should be either Cut or Sequence with Cut
|
|
186
|
+
expect([Parsanol::Atoms::Cut, Parsanol::Atoms::Sequence]).to include(alt.class)
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
it "handles mixed safe and unsafe alternatives correctly" do
|
|
191
|
+
# If FIRST sets aren't all disjoint, no cuts
|
|
192
|
+
parslet = str('a') >> str('b') |
|
|
193
|
+
str('a') >> str('c') # Both start with 'a' - not disjoint!
|
|
194
|
+
|
|
195
|
+
result = inserter.optimize(parslet)
|
|
196
|
+
|
|
197
|
+
# Should not insert cuts since FIRST sets overlap
|
|
198
|
+
# Note: str('a') >> str('b') might be optimized to str('ab') by Phase 24
|
|
199
|
+
expect(result).to be_a(Parsanol::Atoms::Alternative)
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
describe "edge cases" do
|
|
204
|
+
it "handles single alternative (no optimization needed)" do
|
|
205
|
+
# Alternative with one option - trivial case
|
|
206
|
+
parslet = Parsanol::Atoms::Alternative.new(str('only'))
|
|
207
|
+
result = inserter.optimize(parslet)
|
|
208
|
+
|
|
209
|
+
# Should still work, just return optimized single alternative
|
|
210
|
+
expect(result).to be_a(Parsanol::Atoms::Alternative)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
it "handles empty sequence prefix (no cut insertion)" do
|
|
214
|
+
# If prefix is empty, shouldn't insert cut
|
|
215
|
+
parslet = (str('a').maybe >> str('b')) | str('c')
|
|
216
|
+
result = inserter.optimize(parslet)
|
|
217
|
+
|
|
218
|
+
# Alternatives are disjoint but first has EPSILON prefix
|
|
219
|
+
expect(result).to be_a(Parsanol::Atoms::Alternative)
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
describe "preservation of semantics" do
|
|
224
|
+
it "produces parslet that parses the same input" do
|
|
225
|
+
parslet = str('if') | str('while') | str('for')
|
|
226
|
+
optimized = inserter.optimize(parslet)
|
|
227
|
+
|
|
228
|
+
# Should parse same inputs
|
|
229
|
+
expect(parslet.parse('if')).to eq('if')
|
|
230
|
+
expect(optimized.parse('if')).to eq('if')
|
|
231
|
+
|
|
232
|
+
expect(parslet.parse('while')).to eq('while')
|
|
233
|
+
expect(optimized.parse('while')).to eq('while')
|
|
234
|
+
|
|
235
|
+
expect(parslet.parse('for')).to eq('for')
|
|
236
|
+
expect(optimized.parse('for')).to eq('for')
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
it "produces parslet that fails on same invalid input" do
|
|
240
|
+
parslet = str('yes') | str('no')
|
|
241
|
+
optimized = inserter.optimize(parslet)
|
|
242
|
+
|
|
243
|
+
# Both should fail on invalid input
|
|
244
|
+
expect { parslet.parse('maybe') }.to raise_error(Parsanol::ParseFailed)
|
|
245
|
+
expect { optimized.parse('maybe') }.to raise_error(Parsanol::ParseFailed)
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe "Cut operator" do
|
|
4
|
+
include Parsanol
|
|
5
|
+
|
|
6
|
+
describe "basic functionality" do
|
|
7
|
+
it "allows successful parse when cut succeeds" do
|
|
8
|
+
parser = str('if').cut >> str(' ') >> str('x')
|
|
9
|
+
result = parser.parse('if x')
|
|
10
|
+
expect(result).to eq('if x')
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it "provides cache eviction on successful cut" do
|
|
14
|
+
# Cut operator primarily provides cache eviction for memory optimization
|
|
15
|
+
# It clears cache before the cut position
|
|
16
|
+
parser = str('if').cut >> str('x')
|
|
17
|
+
result = parser.parse('ifx')
|
|
18
|
+
expect(result).to eq('ifx')
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
describe "with alternatives" do
|
|
23
|
+
it "works with cuts in each alternative branch" do
|
|
24
|
+
parser =
|
|
25
|
+
(str('if').cut >> str(' then')) |
|
|
26
|
+
(str('while').cut >> str(' do')) |
|
|
27
|
+
str('print')
|
|
28
|
+
|
|
29
|
+
expect(parser.parse('if then')).to eq('if then')
|
|
30
|
+
expect(parser.parse('while do')).to eq('while do')
|
|
31
|
+
expect(parser.parse('print')).to eq('print')
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
describe "cut position" do
|
|
36
|
+
it "cuts at the correct position" do
|
|
37
|
+
parser = str('a').cut >> str('b') >> str('c')
|
|
38
|
+
result = parser.parse('abc')
|
|
39
|
+
expect(result).to eq('abc')
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
describe "FIRST set delegation" do
|
|
44
|
+
it "delegates first_set to wrapped parslet" do
|
|
45
|
+
cut_atom = str('test').cut
|
|
46
|
+
expect(cut_atom.first_set.size).to eq(1)
|
|
47
|
+
expect(cut_atom.first_set.first).to be_a(Parsanol::Atoms::Str)
|
|
48
|
+
expect(cut_atom.first_set.first.str).to eq('test')
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
describe "caching behavior" do
|
|
53
|
+
it "is not cached itself (thin wrapper)" do
|
|
54
|
+
cut_atom = str('test').cut
|
|
55
|
+
expect(cut_atom.cached?).to be false
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
describe "string representation" do
|
|
60
|
+
it "shows cut operator in to_s" do
|
|
61
|
+
cut_atom = str('foo').cut
|
|
62
|
+
expect(cut_atom.to_s).to include('foo')
|
|
63
|
+
expect(cut_atom.to_s).to include('↑')
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'parsanol/edit_tracker'
|
|
3
|
+
|
|
4
|
+
describe Parsanol::EditTracker do
|
|
5
|
+
let(:tracker) { Parsanol::EditTracker.new }
|
|
6
|
+
|
|
7
|
+
describe '#initialize' do
|
|
8
|
+
it 'creates an empty tracker' do
|
|
9
|
+
expect(tracker.empty?).to be true
|
|
10
|
+
expect(tracker.size).to eq 0
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
describe '#insert and #delete' do
|
|
15
|
+
it 'records insertions' do
|
|
16
|
+
tracker.insert(10, 5)
|
|
17
|
+
expect(tracker.size).to eq 1
|
|
18
|
+
expect(tracker.edits.first.position).to eq 10
|
|
19
|
+
expect(tracker.edits.first.delta).to eq 5
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it 'records deletions' do
|
|
23
|
+
tracker.delete(20, 3)
|
|
24
|
+
expect(tracker.size).to eq 1
|
|
25
|
+
expect(tracker.edits.first.position).to eq 20
|
|
26
|
+
expect(tracker.edits.first.delta).to eq(-3)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it 'records multiple edits in order' do
|
|
30
|
+
tracker.insert(10, 5)
|
|
31
|
+
tracker.delete(30, 2)
|
|
32
|
+
tracker.insert(50, 10)
|
|
33
|
+
|
|
34
|
+
expect(tracker.size).to eq 3
|
|
35
|
+
expect(tracker.edits[0].delta).to eq 5
|
|
36
|
+
expect(tracker.edits[1].delta).to eq(-2)
|
|
37
|
+
expect(tracker.edits[2].delta).to eq 10
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
describe '#shift_interval' do
|
|
42
|
+
context 'with no edits' do
|
|
43
|
+
it 'returns the original interval' do
|
|
44
|
+
result = tracker.shift_interval(10, 20)
|
|
45
|
+
expect(result).to eq [10, 20]
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
context 'with edit before interval' do
|
|
50
|
+
it 'shifts both boundaries forward for insertion' do
|
|
51
|
+
tracker.insert(5, 10) # Insert 10 chars at position 5
|
|
52
|
+
result = tracker.shift_interval(10, 20)
|
|
53
|
+
expect(result).to eq [20, 30] # Both shifted by +10
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it 'shifts both boundaries backward for deletion' do
|
|
57
|
+
tracker.delete(5, 3) # Delete 3 chars at position 5
|
|
58
|
+
result = tracker.shift_interval(10, 20)
|
|
59
|
+
expect(result).to eq [7, 17] # Both shifted by -3
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
context 'with edit after interval' do
|
|
64
|
+
it 'does not shift interval for insertion' do
|
|
65
|
+
tracker.insert(50, 10)
|
|
66
|
+
result = tracker.shift_interval(10, 20)
|
|
67
|
+
expect(result).to eq [10, 20] # No change
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it 'does not shift interval for deletion' do
|
|
71
|
+
tracker.delete(50, 5)
|
|
72
|
+
result = tracker.shift_interval(10, 20)
|
|
73
|
+
expect(result).to eq [10, 20] # No change
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
context 'with edit inside interval' do
|
|
78
|
+
it 'invalidates interval for insertion' do
|
|
79
|
+
tracker.insert(15, 5) # Insert inside [10, 20)
|
|
80
|
+
result = tracker.shift_interval(10, 20)
|
|
81
|
+
expect(result).to be_nil # Invalidated
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it 'invalidates interval for deletion' do
|
|
85
|
+
tracker.delete(15, 3) # Delete inside [10, 20)
|
|
86
|
+
result = tracker.shift_interval(10, 20)
|
|
87
|
+
expect(result).to be_nil # Invalidated
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it 'invalidates interval for edit at start boundary' do
|
|
91
|
+
tracker.insert(10, 5) # Edit at start of [10, 20)
|
|
92
|
+
result = tracker.shift_interval(10, 20)
|
|
93
|
+
expect(result).to be_nil # Invalidated
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
context 'with edit at end boundary' do
|
|
98
|
+
it 'does not invalidate interval' do
|
|
99
|
+
tracker.insert(20, 5) # Edit at end of [10, 20) - not inside
|
|
100
|
+
result = tracker.shift_interval(10, 20)
|
|
101
|
+
expect(result).to eq [10, 20] # Not invalidated
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
context 'with multiple edits' do
|
|
106
|
+
it 'applies all edits in order' do
|
|
107
|
+
tracker.insert(5, 10) # Shift [10,20) to [20,30)
|
|
108
|
+
tracker.insert(3, 5) # Shift [20,30) to [25,35)
|
|
109
|
+
tracker.delete(2, 1) # Shift [25,35) to [24,34)
|
|
110
|
+
|
|
111
|
+
result = tracker.shift_interval(10, 20)
|
|
112
|
+
expect(result).to eq [24, 34]
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
it 'invalidates if any edit is inside interval' do
|
|
116
|
+
tracker.insert(5, 10) # Shift [10,20) to [20,30)
|
|
117
|
+
tracker.insert(25, 5) # Inside shifted interval [20,30)
|
|
118
|
+
|
|
119
|
+
result = tracker.shift_interval(10, 20)
|
|
120
|
+
expect(result).to be_nil # Invalidated by second edit
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
it 'handles complex sequence of edits' do
|
|
124
|
+
# Start with interval [100, 200)
|
|
125
|
+
tracker.insert(50, 20) # Shift to [120, 220)
|
|
126
|
+
tracker.delete(80, 10) # Shift to [110, 210)
|
|
127
|
+
tracker.insert(250, 30) # After interval, no shift
|
|
128
|
+
tracker.insert(90, 5) # Shift to [115, 215)
|
|
129
|
+
|
|
130
|
+
result = tracker.shift_interval(100, 200)
|
|
131
|
+
expect(result).to eq [115, 215]
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
context 'with invalidation conditions' do
|
|
136
|
+
it 'invalidates if shifted interval becomes negative' do
|
|
137
|
+
tracker.delete(5, 20) # Large deletion before interval
|
|
138
|
+
result = tracker.shift_interval(10, 20)
|
|
139
|
+
expect(result).to be_nil # Would become [<0, <0)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
it 'invalidates if high becomes less than low' do
|
|
143
|
+
tracker.delete(5, 100) # Very large deletion
|
|
144
|
+
result = tracker.shift_interval(10, 20)
|
|
145
|
+
expect(result).to be_nil # Would become invalid
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
describe '#invalidates?' do
|
|
151
|
+
it 'returns false when interval is valid after edits' do
|
|
152
|
+
tracker.insert(5, 10)
|
|
153
|
+
expect(tracker.invalidates?(20, 30)).to be false
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
it 'returns true when interval is invalidated' do
|
|
157
|
+
tracker.insert(15, 5)
|
|
158
|
+
expect(tracker.invalidates?(10, 20)).to be true
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
describe '#clear' do
|
|
163
|
+
it 'removes all edits' do
|
|
164
|
+
tracker.insert(10, 5)
|
|
165
|
+
tracker.delete(20, 3)
|
|
166
|
+
tracker.clear
|
|
167
|
+
|
|
168
|
+
expect(tracker.empty?).to be true
|
|
169
|
+
expect(tracker.size).to eq 0
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
it 'resets interval shifting' do
|
|
173
|
+
tracker.insert(5, 10)
|
|
174
|
+
tracker.clear
|
|
175
|
+
|
|
176
|
+
result = tracker.shift_interval(10, 20)
|
|
177
|
+
expect(result).to eq [10, 20] # No shift after clear
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
describe 'edge cases' do
|
|
182
|
+
it 'handles zero-length intervals' do
|
|
183
|
+
tracker.insert(10, 5)
|
|
184
|
+
result = tracker.shift_interval(15, 15)
|
|
185
|
+
expect(result).to eq [20, 20] # Shifted but still zero-length
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
it 'handles zero-length insertions' do
|
|
189
|
+
tracker.insert(10, 0)
|
|
190
|
+
result = tracker.shift_interval(5, 15)
|
|
191
|
+
expect(result).to eq [5, 15] # No change
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
it 'handles zero-length deletions' do
|
|
195
|
+
tracker.delete(10, 0)
|
|
196
|
+
result = tracker.shift_interval(5, 15)
|
|
197
|
+
expect(result).to eq [5, 15] # No change
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
it 'handles negative positions in edits' do
|
|
201
|
+
tracker.insert(-5, 10)
|
|
202
|
+
result = tracker.shift_interval(10, 20)
|
|
203
|
+
expect(result).to eq [20, 30] # Shifted
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
describe 'Edit#to_s' do
|
|
208
|
+
it 'describes insertions' do
|
|
209
|
+
edit = Parsanol::EditTracker::Edit.new(10, 5)
|
|
210
|
+
expect(edit.to_s).to eq "Insert(5 chars at 10)"
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
it 'describes deletions' do
|
|
214
|
+
edit = Parsanol::EditTracker::Edit.new(20, -3)
|
|
215
|
+
expect(edit.to_s).to eq "Delete(3 chars at 20)"
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
end
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Parsanol::ErrorReporter::Contextual do
|
|
4
|
+
let(:reporter) { described_class.new }
|
|
5
|
+
let(:fake_source) { double('source') }
|
|
6
|
+
let(:fake_atom) { double('atom') }
|
|
7
|
+
let(:fake_cause) { double('cause') }
|
|
8
|
+
|
|
9
|
+
describe '#err' do
|
|
10
|
+
before do
|
|
11
|
+
allow(fake_source).to receive(:pos).and_return(13)
|
|
12
|
+
allow(fake_source).to receive(:line_and_column).and_return([1, 1])
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it 'returns the deepest cause' do
|
|
16
|
+
expect(reporter).to receive(:deepest).and_return(:deepest)
|
|
17
|
+
expect(reporter.err('parslet', fake_source, 'message')).to eq(:deepest)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
describe '#err_at' do
|
|
22
|
+
before do
|
|
23
|
+
allow(fake_source).to receive(:pos).and_return(13)
|
|
24
|
+
allow(fake_source).to receive(:line_and_column).and_return([1, 1])
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it 'returns the deepest cause' do
|
|
28
|
+
expect(reporter).to receive(:deepest).and_return(:deepest)
|
|
29
|
+
expect(reporter.err('parslet', fake_source, 'message', 13)).to eq(:deepest)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
describe '#deepest(cause)' do
|
|
34
|
+
def fake_cause(pos = 13, children = nil)
|
|
35
|
+
double('cause' + pos.to_s, pos: pos, children: children)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
context 'when there is no deepest cause yet' do
|
|
39
|
+
let(:cause) { fake_cause }
|
|
40
|
+
|
|
41
|
+
it 'returns the given cause' do
|
|
42
|
+
reporter.deepest(cause).should == cause
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
context 'when the previous cause is deeper (no relationship)' do
|
|
47
|
+
let(:previous) { fake_cause }
|
|
48
|
+
|
|
49
|
+
before do
|
|
50
|
+
reporter.deepest(previous)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it 'returns the previous cause' do
|
|
54
|
+
reporter.deepest(fake_cause(12))
|
|
55
|
+
.should == previous
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
context 'when the previous cause is deeper (child)' do
|
|
60
|
+
let(:previous) { fake_cause }
|
|
61
|
+
|
|
62
|
+
before do
|
|
63
|
+
reporter.deepest(previous)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it 'returns the given cause' do
|
|
67
|
+
given = fake_cause(12, [previous])
|
|
68
|
+
reporter.deepest(given).should == given
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
context 'when the previous cause is shallower' do
|
|
73
|
+
before do
|
|
74
|
+
reporter.deepest(fake_cause)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it 'stores the cause as deepest' do
|
|
78
|
+
deeper = fake_cause(14)
|
|
79
|
+
reporter.deepest(deeper)
|
|
80
|
+
reporter.deepest_cause.should == deeper
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
describe '#reset' do
|
|
86
|
+
before do
|
|
87
|
+
allow(fake_source).to receive(:pos).and_return(Parsanol::Position.new(
|
|
88
|
+
"source", 13, 13
|
|
89
|
+
))
|
|
90
|
+
allow(fake_source).to receive(:line_and_column).and_return([1, 1])
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
it 'resets deepest cause on success of sibling expression' do
|
|
94
|
+
expect(reporter).to receive(:deepest).and_return(:deepest)
|
|
95
|
+
expect(reporter.err('parslet', fake_source, 'message')).to eq(:deepest)
|
|
96
|
+
expect(reporter).to receive(:reset).once
|
|
97
|
+
reporter.succ(fake_source)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
describe 'label' do
|
|
102
|
+
before do
|
|
103
|
+
allow(fake_source).to receive(:pos).and_return(Parsanol::Position.new(
|
|
104
|
+
"source", 13, 13
|
|
105
|
+
))
|
|
106
|
+
allow(fake_source).to receive(:line_and_column).and_return([1, 1])
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
it 'sets label if atom has one' do
|
|
110
|
+
expect(fake_atom).to receive(:label).once.and_return('label')
|
|
111
|
+
expect(fake_cause).to receive(:set_label).once
|
|
112
|
+
expect(reporter).to receive(:deepest).and_return(fake_cause)
|
|
113
|
+
expect(reporter.err(fake_atom, fake_source, 'message')).to eq(fake_cause)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
it 'does not set label if atom does not have one' do
|
|
117
|
+
expect(reporter).to receive(:deepest).and_return(:deepest)
|
|
118
|
+
expect(fake_atom).not_to receive(:update_label)
|
|
119
|
+
expect(reporter.err(fake_atom, fake_source, 'message')).to eq(:deepest)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|