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,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require 'json'
|
|
5
|
+
|
|
6
|
+
describe Parsanol::Serialized do
|
|
7
|
+
let(:parser_class) do
|
|
8
|
+
Class.new(Parsanol::Parser) do
|
|
9
|
+
include Parsanol::Serialized
|
|
10
|
+
|
|
11
|
+
rule(:number) { match('[0-9]').repeat(1).as(:int) }
|
|
12
|
+
rule(:space) { match('\s').repeat }
|
|
13
|
+
rule(:add_op) { match('[+-]').as(:op) >> space }
|
|
14
|
+
rule(:expression) { (number.as(:left) >> add_op.as(:op) >> expression.as(:right)).as(:binop) | number }
|
|
15
|
+
root(:expression)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
let(:parser) { parser_class.new }
|
|
20
|
+
|
|
21
|
+
describe '#parse_to_json' do
|
|
22
|
+
context 'when native extension is not available' do
|
|
23
|
+
before do
|
|
24
|
+
allow(Parsanol::Native).to receive(:available?).and_return(false)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it 'raises LoadError' do
|
|
28
|
+
expect { parser.parse_to_json('42') }.to raise_error(LoadError, /Serialized mode requires native extension/)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
describe '#parse_to_struct' do
|
|
34
|
+
context 'when native extension is not available' do
|
|
35
|
+
before do
|
|
36
|
+
allow(Parsanol::Native).to receive(:available?).and_return(false)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it 'raises LoadError' do
|
|
40
|
+
deserializer = Class.new do
|
|
41
|
+
def self.from_json(json); JSON.parse(json); end
|
|
42
|
+
end
|
|
43
|
+
expect { parser.parse_to_struct('42', deserializer) }.to raise_error(LoadError)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
describe '#parse' do
|
|
49
|
+
context 'when native extension is not available' do
|
|
50
|
+
before do
|
|
51
|
+
allow(Parsanol::Native).to receive(:available?).and_return(false)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it 'raises LoadError' do
|
|
55
|
+
expect { parser.parse('42') }.to raise_error(LoadError, /Serialized mode requires native extension/)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
describe '.output_schema' do
|
|
61
|
+
it 'allows defining output schema' do
|
|
62
|
+
parser_class.output_schema(
|
|
63
|
+
number: { type: :integer },
|
|
64
|
+
binop: { type: :object, properties: [:left, :op, :right] }
|
|
65
|
+
)
|
|
66
|
+
expect(parser_class.output_schema).to be_a(Hash)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
describe Parsanol::ZeroCopy do
|
|
6
|
+
# Define test AST classes
|
|
7
|
+
before(:all) do
|
|
8
|
+
module TestCalculator
|
|
9
|
+
class Expr
|
|
10
|
+
def eval
|
|
11
|
+
raise NotImplementedError
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class Number < Expr
|
|
16
|
+
attr_reader :value
|
|
17
|
+
|
|
18
|
+
def initialize(value)
|
|
19
|
+
@value = value
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def eval = @value
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
class BinOp < Expr
|
|
26
|
+
attr_reader :left, :op, :right
|
|
27
|
+
|
|
28
|
+
def initialize(left:, op:, right:)
|
|
29
|
+
@left = left
|
|
30
|
+
@op = op
|
|
31
|
+
@right = right
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def eval
|
|
35
|
+
case @op
|
|
36
|
+
when '+' then @left.eval + @right.eval
|
|
37
|
+
when '-' then @left.eval - @right.eval
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
let(:parser_class) do
|
|
45
|
+
Class.new(Parsanol::Parser) do
|
|
46
|
+
include Parsanol::ZeroCopy
|
|
47
|
+
|
|
48
|
+
rule(:number) { match('[0-9]').repeat(1).as(:int) }
|
|
49
|
+
rule(:space) { match('\s').repeat }
|
|
50
|
+
rule(:add_op) { match('[+-]').as(:op) >> space }
|
|
51
|
+
rule(:expression) { (number.as(:left) >> add_op.as(:op) >> expression.as(:right)).as(:binop) | number }
|
|
52
|
+
root(:expression)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
let(:parser) { parser_class.new }
|
|
57
|
+
|
|
58
|
+
describe '.output_types' do
|
|
59
|
+
it 'allows defining output types' do
|
|
60
|
+
parser_class.output_types(
|
|
61
|
+
number: TestCalculator::Number,
|
|
62
|
+
binop: TestCalculator::BinOp
|
|
63
|
+
)
|
|
64
|
+
expect(parser_class.output_types[:number]).to eq(TestCalculator::Number)
|
|
65
|
+
expect(parser_class.output_types[:binop]).to eq(TestCalculator::BinOp)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it 'returns empty hash by default' do
|
|
69
|
+
fresh_parser_class = Class.new(Parsanol::Parser) do
|
|
70
|
+
include Parsanol::ZeroCopy
|
|
71
|
+
end
|
|
72
|
+
expect(fresh_parser_class.output_types).to eq({})
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
describe '.output_type' do
|
|
77
|
+
it 'allows defining a single output type' do
|
|
78
|
+
parser_class.output_type(:number, TestCalculator::Number)
|
|
79
|
+
expect(parser_class.output_types[:number]).to eq(TestCalculator::Number)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
describe '.output_types_for_ffi' do
|
|
84
|
+
it 'converts types to FFI-compatible format' do
|
|
85
|
+
parser_class.output_types(
|
|
86
|
+
number: TestCalculator::Number,
|
|
87
|
+
binop: TestCalculator::BinOp
|
|
88
|
+
)
|
|
89
|
+
ffi_types = parser_class.output_types_for_ffi
|
|
90
|
+
expect(ffi_types['number']).to eq('TestCalculator::Number')
|
|
91
|
+
expect(ffi_types['binop']).to eq('TestCalculator::BinOp')
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
describe '#parse' do
|
|
96
|
+
context 'when native extension is not available' do
|
|
97
|
+
before do
|
|
98
|
+
allow(Parsanol::Native).to receive(:available?).and_return(false)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
it 'raises LoadError' do
|
|
102
|
+
expect { parser.parse('42') }.to raise_error(LoadError, /ZeroCopy mode requires native extension/)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
context 'when output_types is not defined' do
|
|
107
|
+
before do
|
|
108
|
+
allow(Parsanol::Native).to receive(:available?).and_return(true)
|
|
109
|
+
allow(Parsanol::Native).to receive(:serialize_grammar).and_return('{}')
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
it 'raises ArgumentError' do
|
|
113
|
+
fresh_parser_class = Class.new(Parsanol::Parser) do
|
|
114
|
+
include Parsanol::ZeroCopy
|
|
115
|
+
rule(:test) { str('a') }
|
|
116
|
+
root(:test)
|
|
117
|
+
end
|
|
118
|
+
fresh_parser = fresh_parser_class.new
|
|
119
|
+
expect { fresh_parser.parse('a') }.to raise_error(ArgumentError, /ZeroCopy mode requires output_types/)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
describe '#parse_with_types' do
|
|
125
|
+
context 'when native extension is not available' do
|
|
126
|
+
before do
|
|
127
|
+
allow(Parsanol::Native).to receive(:available?).and_return(false)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
it 'raises LoadError' do
|
|
131
|
+
expect { parser.parse_with_types('42', {}) }.to raise_error(LoadError, /ZeroCopy mode requires native extension/)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Tests for parse_to_ruby_objects FFI function
|
|
137
|
+
# These tests verify that the new FFI function directly constructs
|
|
138
|
+
# Parsanol::Slice objects without intermediate Hash markers.
|
|
139
|
+
describe 'parse_to_ruby_objects FFI' do
|
|
140
|
+
# Simple string match grammar
|
|
141
|
+
let(:string_grammar) do
|
|
142
|
+
{
|
|
143
|
+
atoms: [{ Str: { pattern: 'hello' } }],
|
|
144
|
+
root: 0
|
|
145
|
+
}.to_json
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Named capture grammar
|
|
149
|
+
let(:named_grammar) do
|
|
150
|
+
{
|
|
151
|
+
atoms: [
|
|
152
|
+
{ Str: { pattern: 'hello' } },
|
|
153
|
+
{ Named: { name: 'greeting', atom: 0 } }
|
|
154
|
+
],
|
|
155
|
+
root: 1
|
|
156
|
+
}.to_json
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Sequence grammar
|
|
160
|
+
let(:sequence_grammar) do
|
|
161
|
+
{
|
|
162
|
+
atoms: [
|
|
163
|
+
{ Str: { pattern: 'hello' } },
|
|
164
|
+
{ Str: { pattern: ' ' } },
|
|
165
|
+
{ Str: { pattern: 'world' } },
|
|
166
|
+
{ Sequence: { atoms: [0, 1, 2] } }
|
|
167
|
+
],
|
|
168
|
+
root: 3
|
|
169
|
+
}.to_json
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
context 'with native extension available' do
|
|
173
|
+
before do
|
|
174
|
+
skip 'Native extension not available' unless Parsanol::Native.available?
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
it 'returns Slice object for simple string match' do
|
|
178
|
+
result = Parsanol::Native.parse_to_ruby_objects(string_grammar, 'hello')
|
|
179
|
+
expect(result).to be_a(Parsanol::Slice)
|
|
180
|
+
expect(result.to_s).to eq('hello')
|
|
181
|
+
expect(result.offset).to eq(0)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
it 'returns Hash with Slice values for named captures' do
|
|
185
|
+
result = Parsanol::Native.parse_to_ruby_objects(named_grammar, 'hello')
|
|
186
|
+
expect(result).to be_a(Hash)
|
|
187
|
+
expect(result.keys).to eq(['greeting'])
|
|
188
|
+
|
|
189
|
+
greeting = result['greeting']
|
|
190
|
+
expect(greeting).to be_a(Parsanol::Slice)
|
|
191
|
+
expect(greeting.to_s).to eq('hello')
|
|
192
|
+
expect(greeting.offset).to eq(0)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
it 'returns Array of Slice objects for sequences' do
|
|
196
|
+
result = Parsanol::Native.parse_to_ruby_objects(sequence_grammar, 'hello world')
|
|
197
|
+
expect(result).to be_a(Array)
|
|
198
|
+
expect(result.length).to eq(3)
|
|
199
|
+
|
|
200
|
+
# First slice: "hello"
|
|
201
|
+
expect(result[0]).to be_a(Parsanol::Slice)
|
|
202
|
+
expect(result[0].to_s).to eq('hello')
|
|
203
|
+
expect(result[0].offset).to eq(0)
|
|
204
|
+
|
|
205
|
+
# Second slice: " "
|
|
206
|
+
expect(result[1]).to be_a(Parsanol::Slice)
|
|
207
|
+
expect(result[1].to_s).to eq(' ')
|
|
208
|
+
expect(result[1].offset).to eq(5)
|
|
209
|
+
|
|
210
|
+
# Third slice: "world"
|
|
211
|
+
expect(result[2]).to be_a(Parsanol::Slice)
|
|
212
|
+
expect(result[2].to_s).to eq('world')
|
|
213
|
+
expect(result[2].offset).to eq(6)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
it 'preserves correct byte offsets for multi-byte characters' do
|
|
217
|
+
# Test with input containing UTF-8 characters
|
|
218
|
+
utf8_grammar = {
|
|
219
|
+
atoms: [{ Str: { pattern: '日本語' } }],
|
|
220
|
+
root: 0
|
|
221
|
+
}.to_json
|
|
222
|
+
|
|
223
|
+
result = Parsanol::Native.parse_to_ruby_objects(utf8_grammar, '日本語')
|
|
224
|
+
expect(result).to be_a(Parsanol::Slice)
|
|
225
|
+
expect(result.to_s).to eq('日本語')
|
|
226
|
+
expect(result.offset).to eq(0)
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Parsanol::Parser do
|
|
4
|
+
include Parsanol
|
|
5
|
+
class FooParser < Parsanol::Parser
|
|
6
|
+
rule(:foo) { str('foo') }
|
|
7
|
+
root(:foo)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
describe '<- .root' do
|
|
11
|
+
parser = Class.new(Parsanol::Parser) do
|
|
12
|
+
def root_parslet
|
|
13
|
+
:answer
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
parser.root :root_parslet
|
|
17
|
+
|
|
18
|
+
it "has defined a 'root' method, returning the root" do
|
|
19
|
+
parser_instance = parser.new
|
|
20
|
+
expect(parser_instance.root).to eq(:answer)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "parses 'foo'" do
|
|
25
|
+
FooParser.new.parse('foo').should == 'foo'
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
context 'composition' do
|
|
29
|
+
let(:parser) { FooParser.new }
|
|
30
|
+
|
|
31
|
+
it 'allows concatenation' do
|
|
32
|
+
composite = parser >> str('bar')
|
|
33
|
+
composite.should parse('foobar')
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Parsanol do
|
|
4
|
+
include Parsanol
|
|
5
|
+
|
|
6
|
+
describe Parsanol::ParseFailed do
|
|
7
|
+
it "should be caught by an empty rescue" do
|
|
8
|
+
begin
|
|
9
|
+
raise Parsanol::ParseFailed
|
|
10
|
+
rescue
|
|
11
|
+
# Success! Ignore this.
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
describe "<- .rule" do
|
|
16
|
+
# Rules define methods. This can be easily tested by defining them right
|
|
17
|
+
# here.
|
|
18
|
+
context "empty rule" do
|
|
19
|
+
rule(:empty) { }
|
|
20
|
+
|
|
21
|
+
it "should raise a NotImplementedError" do
|
|
22
|
+
lambda {
|
|
23
|
+
empty.parslet
|
|
24
|
+
}.should raise_error(NotImplementedError)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
context "containing 'any'" do
|
|
29
|
+
rule(:any_rule) { any }
|
|
30
|
+
subject { any_rule }
|
|
31
|
+
|
|
32
|
+
it { should be_a Parsanol::Atoms::Entity }
|
|
33
|
+
it "should memoize the returned instance" do
|
|
34
|
+
any_rule.object_id.should == any_rule.object_id
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
require 'parsanol/parslet'
|
|
4
|
+
|
|
5
|
+
describe Parsanol::Pattern do
|
|
6
|
+
include Parsanol
|
|
7
|
+
|
|
8
|
+
# These two factory methods help make the specs more robust to interface
|
|
9
|
+
# changes. They also help to label trees (t) and patterns (p).
|
|
10
|
+
def p(pattern)
|
|
11
|
+
Parsanol::Pattern.new(pattern)
|
|
12
|
+
end
|
|
13
|
+
def t(obj)
|
|
14
|
+
obj
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Tries to match pattern to the tree, and verifies the bindings hash. Don't
|
|
18
|
+
# use this for new examples.
|
|
19
|
+
#
|
|
20
|
+
RSpec::Matchers.define :match_with_bind do |pattern, exp_bindings|
|
|
21
|
+
unless respond_to?(:failure_message)
|
|
22
|
+
alias_method :failure_message_for_should, :failure_message
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
failure_message do |tree|
|
|
26
|
+
"expected #{pattern.inspect} to match #{tree.inspect}, but didn't. (block wasn't called or not correctly)"
|
|
27
|
+
end
|
|
28
|
+
match do |tree|
|
|
29
|
+
bindings = Parsanol::Pattern.new(pattern).match(tree)
|
|
30
|
+
bindings && bindings == exp_bindings
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# This is the more modern version of verifying a match: (uses 'exp'
|
|
35
|
+
# implicitly). Checks for a match of pattern in +exp+ and yields the
|
|
36
|
+
# matched variables.
|
|
37
|
+
#
|
|
38
|
+
def with_match_locals(pattern, &block)
|
|
39
|
+
bindings = p(pattern).match(exp)
|
|
40
|
+
bindings.should_not be_nil
|
|
41
|
+
|
|
42
|
+
block.call(bindings) if block
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Can't use #match here, so I went to the Thesaurus.
|
|
46
|
+
#
|
|
47
|
+
RSpec::Matchers.define :detect do |pattern|
|
|
48
|
+
match do |tree|
|
|
49
|
+
bindings = Parsanol::Pattern.new(pattern).match(tree)
|
|
50
|
+
|
|
51
|
+
bindings ? true : false
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
describe "<- #match" do
|
|
56
|
+
context "injecting bindings" do
|
|
57
|
+
let(:pattern) { p(simple(:x)) }
|
|
58
|
+
|
|
59
|
+
it "should not modify the original bindings hash" do
|
|
60
|
+
h = {}
|
|
61
|
+
b=pattern.match('a', h)
|
|
62
|
+
h.size.should == 0
|
|
63
|
+
b.size.should == 1
|
|
64
|
+
end
|
|
65
|
+
it "should return nil when no match succeeds" do
|
|
66
|
+
pattern.match([], :foo => :bar).should be_nil
|
|
67
|
+
end
|
|
68
|
+
context "when matching simple(:x) against 'a'" do
|
|
69
|
+
let(:bindings) { pattern.match(t('a'), :foo => :bar) }
|
|
70
|
+
|
|
71
|
+
before(:each) { bindings.should_not be_nil }
|
|
72
|
+
it "should return the injected bindings" do
|
|
73
|
+
bindings[:foo].should == :bar
|
|
74
|
+
end
|
|
75
|
+
it "should return the new bindings" do
|
|
76
|
+
bindings[:x].should == 'a'
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
context "simple strings" do
|
|
81
|
+
let(:exp) { 'aaaa' }
|
|
82
|
+
|
|
83
|
+
it "should match simple strings" do
|
|
84
|
+
exp.should match_with_bind(simple(:x), :x => 'aaaa')
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
context "simple hash {:a => 'b'}" do
|
|
88
|
+
attr_reader :exp
|
|
89
|
+
before(:each) do
|
|
90
|
+
@exp = t(:a => 'b')
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
it "should not match {:a => simple(:x), :b => simple(:y)}" do
|
|
94
|
+
exp.should_not detect(:a => simple(:x), :b => simple(:y))
|
|
95
|
+
end
|
|
96
|
+
it "should match {:a => simple(:x)}, binding 'x' to the first argument" do
|
|
97
|
+
exp.should match_with_bind({:a => simple(:x)}, :x => 'b')
|
|
98
|
+
end
|
|
99
|
+
it "should match {:a => 'b'} with no binds" do
|
|
100
|
+
exp.should match_with_bind({:a => 'b'}, {})
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
context "a more complex hash {:a => {:b => 'c'}}" do
|
|
104
|
+
attr_reader :exp
|
|
105
|
+
before(:each) do
|
|
106
|
+
@exp = t(:a => {:b => 'c'})
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
it "should match wholly with {:a => {:b => simple(:x)}}" do
|
|
110
|
+
exp.should match_with_bind({:a => {:b => simple(:x)}}, :x => 'c')
|
|
111
|
+
end
|
|
112
|
+
it "should match wholly with {:a => subtree(:t)}" do
|
|
113
|
+
with_match_locals(:a => subtree(:t)) do |dict|
|
|
114
|
+
dict[:t].should == {:b => 'c'}
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
it "should not bind subtrees to variables in {:a => simple(:x)}" do
|
|
118
|
+
p(:a => simple(:x)).should_not detect(exp)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
context "a more complex hash {:a => 'a', :b => 'b'}" do
|
|
122
|
+
attr_reader :exp
|
|
123
|
+
before(:each) do
|
|
124
|
+
@exp = t({:a => 'a', :b => 'b'})
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
it "should not match partially" do
|
|
128
|
+
Parsanol::Pattern.new(:a => simple(:x)).match(exp).should be_nil
|
|
129
|
+
end
|
|
130
|
+
it "should match completely" do
|
|
131
|
+
exp.should match_with_bind({:a => simple(:x), :b => simple(:y)},
|
|
132
|
+
:x => 'a',
|
|
133
|
+
:y => 'b')
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
context "an array of 'a', 'b', 'c'" do
|
|
137
|
+
let(:exp) { ['a', 'b', 'c'] }
|
|
138
|
+
|
|
139
|
+
it "should match all elements at once" do
|
|
140
|
+
exp.should match_with_bind(
|
|
141
|
+
[simple(:x), simple(:y), simple(:z)],
|
|
142
|
+
:x => 'a', :y => 'b', :z => 'c')
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
context "{:a => 'a', :b => 'b'}" do
|
|
146
|
+
attr_reader :exp
|
|
147
|
+
before(:each) do
|
|
148
|
+
@exp = t(:a => 'a', :b => 'b')
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
it "should match both elements simple(:x), simple(:y)" do
|
|
152
|
+
exp.should match_with_bind(
|
|
153
|
+
{:a => simple(:x), :b => simple(:y)},
|
|
154
|
+
:x => 'a', :y => 'b')
|
|
155
|
+
end
|
|
156
|
+
it "should not match a constrained match (simple(:x) != simple(:y))" do
|
|
157
|
+
exp.should_not detect({:a => simple(:x), :b => simple(:x)})
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
context "{:a => 'a', :b => 'a'}" do
|
|
161
|
+
attr_reader :exp
|
|
162
|
+
before(:each) do
|
|
163
|
+
@exp = t(:a => 'a', :b => 'a')
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
it "should match constrained pattern" do
|
|
167
|
+
exp.should match_with_bind(
|
|
168
|
+
{:a => simple(:x), :b => simple(:x)},
|
|
169
|
+
:x => 'a')
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
context "{:sub1 => {:a => 'a'}, :sub2 => {:a => 'a'}}" do
|
|
173
|
+
attr_reader :exp
|
|
174
|
+
before(:each) do
|
|
175
|
+
@exp = t({
|
|
176
|
+
:sub1 => {:a => 'a'},
|
|
177
|
+
:sub2 => {:a => 'a'}
|
|
178
|
+
})
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
it "should verify constraints over several subtrees" do
|
|
182
|
+
exp.should match_with_bind({
|
|
183
|
+
:sub1 => {:a => simple(:x)},
|
|
184
|
+
:sub2 => {:a => simple(:x)}
|
|
185
|
+
}, :x => 'a')
|
|
186
|
+
end
|
|
187
|
+
it "should return both bind variables simple(:x), simple(:y)" do
|
|
188
|
+
exp.should match_with_bind({
|
|
189
|
+
:sub1 => {:a => simple(:x)},
|
|
190
|
+
:sub2 => {:a => simple(:y)}
|
|
191
|
+
}, :x => 'a', :y => 'a')
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
context "{:sub1 => {:a => 'a'}, :sub2 => {:a => 'b'}}" do
|
|
195
|
+
attr_reader :exp
|
|
196
|
+
before(:each) do
|
|
197
|
+
@exp = t({
|
|
198
|
+
:sub1 => {:a => 'a'},
|
|
199
|
+
:sub2 => {:a => 'b'}
|
|
200
|
+
})
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
it "should verify constraints over several subtrees" do
|
|
204
|
+
exp.should_not match_with_bind({
|
|
205
|
+
:sub1 => {:a => simple(:x)},
|
|
206
|
+
:sub2 => {:a => simple(:x)}
|
|
207
|
+
}, :x => 'a')
|
|
208
|
+
end
|
|
209
|
+
it "should return both bind variables simple(:x), simple(:y)" do
|
|
210
|
+
exp.should match_with_bind({
|
|
211
|
+
:sub1 => {:a => simple(:x)},
|
|
212
|
+
:sub2 => {:a => simple(:y)}
|
|
213
|
+
}, :x => 'a', :y => 'b')
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
context "[{:a => 'x'}, {:a => 'y'}]" do
|
|
217
|
+
attr_reader :exp
|
|
218
|
+
before(:each) do
|
|
219
|
+
@exp = t([{:a => 'x'}, {:a => 'y'}])
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
it "should not match sequence(:x) (as a whole)" do
|
|
223
|
+
exp.should_not detect(sequence(:x))
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
context "['x', 'y', 'z']" do
|
|
227
|
+
attr_reader :exp
|
|
228
|
+
before(:each) do
|
|
229
|
+
@exp = t(['x', 'y', 'z'])
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
it "should match [simple(:x), simple(:y), simple(:z)]" do
|
|
233
|
+
with_match_locals([simple(:x), simple(:y), simple(:z)]) do |dict|
|
|
234
|
+
dict[:x].should == 'x'
|
|
235
|
+
dict[:y].should == 'y'
|
|
236
|
+
dict[:z].should == 'z'
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
it "should match %w(x y z)" do
|
|
240
|
+
exp.should match_with_bind(%w(x y z), { })
|
|
241
|
+
end
|
|
242
|
+
it "should not match [simple(:x), simple(:y), simple(:x)]" do
|
|
243
|
+
exp.should_not detect([simple(:x), simple(:y), simple(:x)])
|
|
244
|
+
end
|
|
245
|
+
it "should not match [simple(:x), simple(:y)]" do
|
|
246
|
+
exp.should_not detect([simple(:x), simple(:y), simple(:x)])
|
|
247
|
+
end
|
|
248
|
+
it "should match sequence(:x) (as array)" do
|
|
249
|
+
exp.should match_with_bind(sequence(:x), :x => ['x', 'y', 'z'])
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
context "{:a => [1,2,3]}" do
|
|
253
|
+
attr_reader :exp
|
|
254
|
+
before(:each) do
|
|
255
|
+
@exp = t(:a => [1,2,3])
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
it "should match :a => sequence(:x) (binding x to the whole array)" do
|
|
259
|
+
exp.should match_with_bind({:a => sequence(:x)}, {:x => [1,2,3]})
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
context "with differently ordered hashes" do
|
|
263
|
+
it "should still match" do
|
|
264
|
+
t(:a => 'a', :b => 'b').should detect(:a => 'a', :b => 'b')
|
|
265
|
+
t(:a => 'a', :b => 'b').should detect(:b => 'b', :a => 'a')
|
|
266
|
+
|
|
267
|
+
t(:b => 'b', :a => 'a').should detect(:b => 'b', :a => 'a')
|
|
268
|
+
t(:b => 'b', :a => 'a').should detect(:a => 'a', :b => 'b')
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
end
|