From 7fc2a22d685fc07fc24ab99c36b11b4b8f0d7f27 Mon Sep 17 00:00:00 2001 From: Victor Porof Date: Thu, 23 Feb 2017 09:11:34 +0100 Subject: [PATCH] Implement a basic edn matcher, r=ncalexan (#271) (#338) Signed-off-by: Victor Porof --- edn/src/lib.rs | 1 + edn/src/matcher.rs | 581 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 582 insertions(+) create mode 100644 edn/src/matcher.rs diff --git a/edn/src/lib.rs b/edn/src/lib.rs index 64e81758..04d02b5c 100644 --- a/edn/src/lib.rs +++ b/edn/src/lib.rs @@ -17,6 +17,7 @@ pub mod symbols; pub mod types; pub mod pretty_print; pub mod utils; +pub mod matcher; pub mod parse { include!(concat!(env!("OUT_DIR"), "/edn.rs")); diff --git a/edn/src/matcher.rs b/edn/src/matcher.rs new file mode 100644 index 00000000..22748bb3 --- /dev/null +++ b/edn/src/matcher.rs @@ -0,0 +1,581 @@ +// Copyright 2016 Mozilla +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use +// this file except in compliance with the License. You may obtain a copy of the +// License at http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +use std::collections::HashMap; +use std::cell::RefCell; +use itertools::diff_with; + +use symbols; +use types::Value; + +/// A trait defining pattern matching rules for any given pattern of type `T`. +trait PatternMatchingRules<'a, T> { + /// Return true if the given pattern matches an arbitrary value. + fn matches_any(pattern: &T) -> bool; + + /// Return the placeholder name if the given pattern matches a placeholder. + fn matches_placeholder(pattern: &'a T) -> Option<(&'a String)>; +} + +/// A default type implementing `PatternMatchingRules` specialized on +/// EDN values using plain symbols as patterns. These patterns are: +/// * `_` matches arbitrary sub-EDN; +/// * `?name` matches sub-EDN, which must be identical each place `?name` appears; +struct DefaultPatternMatchingRules; + +impl<'a> PatternMatchingRules<'a, Value> for DefaultPatternMatchingRules { + fn matches_any(pattern: &Value) -> bool { + match *pattern { + Value::PlainSymbol(symbols::PlainSymbol(ref s)) => s.starts_with('_'), + _ => false + } + } + + fn matches_placeholder(pattern: &'a Value) -> Option<(&'a String)> { + match *pattern { + Value::PlainSymbol(symbols::PlainSymbol(ref s)) => if s.starts_with('?') { Some(s) } else { None }, + _ => None + } + } +} + +/// Pattern matcher for EDN values utilizing specified pattern matching rules. +/// For example, using this with `DefaultPatternMatchingRules`: +/// * `[_]` matches an arbitrary one-element vector; +/// * `[_ _]` matches an arbitrary two-element vector; +/// * `[?x ?x]` matches `[1 1]` and `[#{} #{}]` but not `[1 2]` or `[[] #{}]`; +struct Matcher<'a> { + placeholders: RefCell> +} + +impl<'a> Matcher<'a> { + /// Creates a Matcher instance. + fn new() -> Matcher<'a> { + Matcher { + placeholders: RefCell::default() + } + } + + /// Performs pattern matching between two EDN `Value` instances (`value` + /// and `pattern`) utilizing a specified pattern matching ruleset `T`. + /// Returns true if matching succeeds. + pub fn match_with_rules(value: &'a Value, pattern: &'a Value) -> bool + where T: PatternMatchingRules<'a, Value> { + let matcher = Matcher::new(); + matcher.match_internal::(value, pattern) + } + + /// Recursively traverses two EDN `Value` instances (`value` and `pattern`) + /// performing pattern matching. Note that the internal `placeholders` cache + /// might not be empty on invocation. + fn match_internal(&self, value: &'a Value, pattern: &'a Value) -> bool + where T: PatternMatchingRules<'a, Value> { + use Value::*; + + if T::matches_any(pattern) { + true + } else if let Some(symbol) = T::matches_placeholder(pattern) { + let mut placeholders = self.placeholders.borrow_mut(); + value == *placeholders.entry(symbol).or_insert(value) + } else { + match (value, pattern) { + (&Vector(ref v), &Vector(ref p)) => + diff_with(v, p, |a, b| self.match_internal::(a, b)).is_none(), + (&List(ref v), &List(ref p)) => + diff_with(v, p, |a, b| self.match_internal::(a, b)).is_none(), + (&Set(ref v), &Set(ref p)) => + v.len() == p.len() && + v.iter().all(|a| p.iter().any(|b| self.match_internal::(a, b))) && + p.iter().all(|b| v.iter().any(|a| self.match_internal::(a, b))), + (&Map(ref v), &Map(ref p)) => + v.len() == p.len() && + v.iter().all(|a| p.iter().any(|b| self.match_internal::(a.0, b.0) && self.match_internal::(a.1, b.1))) && + p.iter().all(|b| v.iter().any(|a| self.match_internal::(a.0, b.0) && self.match_internal::(a.1, b.1))), + _ => value == pattern + } + } + } +} + +impl Value { + /// Performs default pattern matching between this value and some `pattern`. + /// Returns true if matching succeeds. + pub fn matches(&self, pattern: &Value) -> bool { + Matcher::match_with_rules::(self, pattern) + } +} + +#[cfg(test)] +mod test { + use parse; + + macro_rules! assert_match { + ( $pattern:tt, $value:tt, $expected:expr ) => { + let pattern = parse::value($pattern).unwrap().without_spans(); + let value = parse::value($value).unwrap().without_spans(); + assert_eq!(value.matches(&pattern), $expected); + }; + ( $pattern:tt =~ $value:tt ) => { + assert_match!($pattern, $value, true); + }; + ( $pattern:tt !~ $value:tt ) => { + assert_match!($pattern, $value, false); + } + } + + #[test] + fn test_match_primitives() { + assert_match!("nil" =~ "nil"); + assert_match!("true" =~ "true"); + assert_match!("true" !~ "false"); + assert_match!("1" =~ "1"); + assert_match!("1" !~ "2"); + assert_match!("1N" =~ "1N"); + assert_match!("1N" !~ "2N"); + assert_match!("1.0" =~ "1.0"); + assert_match!("1.0" !~ "2.0"); + assert_match!("\"a\"" =~ "\"a\""); + assert_match!("\"a\"" !~ "\"b\""); + assert_match!("foo" =~ "foo"); + assert_match!("foo" !~ "bar"); + assert_match!("foo/bar" !~ "foo"); + } + + #[test] + fn test_match_collections_sorted() { + assert_match!("[nil, true, 1, \"foo\", bar, :baz]" =~ "[nil, true, 1, \"foo\", bar, :baz]"); + assert_match!("(nil, true, 1, \"foo\", bar, :baz)" =~ "(nil, true, 1, \"foo\", bar, :baz)"); + assert_match!("#{nil, true, 1, \"foo\", bar, :baz}" =~ "#{nil, true, 1, \"foo\", bar, :baz}"); + assert_match!("{nil true, 1 \"foo\", bar :baz}" =~ "{nil true, 1 \"foo\", bar :baz}"); + } + + #[test] + fn test_match_collections_unsorted() { + assert_match!("[nil, true, 1, \"foo\", bar, :baz]" !~ "[1, \"foo\", nil, true, bar, :baz]"); + assert_match!("(nil, true, 1, \"foo\", bar, :baz)" !~ "(1, \"foo\", nil, true, bar, :baz)"); + assert_match!("#{nil, true, 1, \"foo\", bar, :baz}" =~ "#{1, \"foo\", nil, true, bar, :baz}"); + assert_match!("{nil true, 1 \"foo\", bar :baz}" =~ "{1 \"foo\", nil true, bar :baz}"); + } + + #[test] + fn test_match_maps_switched_key_values() { + assert_match!("{1 2, 3 4}" =~ "{1 2, 3 4}"); + assert_match!("{2 1, 3 4}" !~ "{1 2, 3 4}"); + assert_match!("{2 1, 4 3}" !~ "{1 2, 3 4}"); + assert_match!("{1 2, 4 3}" !~ "{1 2, 3 4}"); + } + + #[test] + fn test_match_maps_ordered_collection_keys_and_values() { + assert_match!("{[1, 2] (3, 4)}" =~ "{[1, 2] (3, 4)}"); + assert_match!("{[2, 1] (3, 4)}" !~ "{[1, 2] (3, 4)}"); + assert_match!("{[2, 1] (4, 3)}" !~ "{[1, 2] (3, 4)}"); + assert_match!("{[1, 2] (4, 3)}" !~ "{[1, 2] (3, 4)}"); + + assert_match!("{(3, 4) [1, 2]}" !~ "{[1, 2] (3, 4)}"); + assert_match!("{(3, 4) [2, 1]}" !~ "{[1, 2] (3, 4)}"); + assert_match!("{(4, 3) [2, 1]}" !~ "{[1, 2] (3, 4)}"); + assert_match!("{(4, 3) [1, 2]}" !~ "{[1, 2] (3, 4)}"); + } + + #[test] + fn test_match_maps_unordered_collection_keys_and_values() { + assert_match!("{#{1, 2} #{3, 4}}" =~ "{#{1, 2} #{3, 4}}"); + assert_match!("{#{2, 1} #{3, 4}}" =~ "{#{1, 2} #{3, 4}}"); + assert_match!("{#{2, 1} #{4, 3}}" =~ "{#{1, 2} #{3, 4}}"); + assert_match!("{#{1, 2} #{4, 3}}" =~ "{#{1, 2} #{3, 4}}"); + + assert_match!("{#{3, 4} #{1, 2}}" !~ "{#{1, 2} #{3, 4}}"); + assert_match!("{#{3, 4} #{2, 1}}" !~ "{#{1, 2} #{3, 4}}"); + assert_match!("{#{4, 3} #{2, 1}}" !~ "{#{1, 2} #{3, 4}}"); + assert_match!("{#{4, 3} #{1, 2}}" !~ "{#{1, 2} #{3, 4}}"); + } + + #[test] + fn test_match_any_simple() { + assert_match!("_" =~ "nil"); + assert_match!("_" =~ "true"); + assert_match!("_" =~ "1"); + assert_match!("_" =~ "1N"); + assert_match!("_" =~ "1.0"); + assert_match!("_" =~ "\"a\""); + assert_match!("_" =~ "_"); + assert_match!("_" =~ "symbol"); + assert_match!("_" =~ "ns/symbol"); + assert_match!("_" =~ ":keyword"); + assert_match!("_" =~ ":ns/keyword"); + assert_match!("_" =~ "[nil, true, 1, \"foo\", bar, :baz]"); + assert_match!("_" =~ "(nil, true, 1, \"foo\", bar, :baz)"); + assert_match!("_" =~ "#{nil, true, 1, \"foo\", bar, :baz}"); + assert_match!("_" =~ "{nil true, 1 \"foo\", bar :baz}"); + } + + #[test] + fn test_match_any_in_same_collection_type_simple() { + assert_match!("[_]" =~ "[1]"); + assert_match!("(_)" =~ "(2)"); + assert_match!("#{_}" =~ "#{3}"); + assert_match!("{_ _}" =~ "{4 5}"); + } + + #[test] + fn test_match_any_in_different_collection_type_simple() { + assert_match!("[_]" !~ "(1)"); + assert_match!("(_)" !~ "#{2}"); + assert_match!("#{_}" !~ "[3]"); + assert_match!("{_ _}" !~ "[4 5]"); + assert_match!("{_ _}" !~ "(6 7)"); + assert_match!("{_ _}" !~ "#{8 9}"); + } + + #[test] + fn test_match_any_in_vector_with_multiple_values() { + assert_match!("[_ 2]" =~ "[1 2]"); + assert_match!("[1 _]" =~ "[1 2]"); + assert_match!("[1 _ 3 4]" =~ "[1 2 3 4]"); + assert_match!("[1 [2 [3 _]] 5 [_ 7]]" =~ "[1 [2 [3 4]] 5 [6 7]]"); + + assert_match!("[_]" =~ "[[foo bar]]"); + assert_match!("[_]" =~ "[(foo bar)]"); + assert_match!("[_]" =~ "[#{foo bar}]"); + assert_match!("[_]" =~ "[{foo bar}]"); + + assert_match!("[_ 2]" !~ "[2 1]"); + assert_match!("[1 _]" !~ "[2 1]"); + assert_match!("[1 _ 3]" !~ "[2 1 3]"); + + assert_match!("[_ 2]" !~ "[3 4]"); + assert_match!("[1 _]" !~ "[3 4]"); + } + + #[test] + fn test_match_multiple_any_in_vector_with_multiple_values() { + assert_match!("[1 _ _]" =~ "[1 2 3]"); + assert_match!("[2 _ _]" !~ "[1 2 3]"); + assert_match!("[3 _ _]" !~ "[1 2 3]"); + assert_match!("[_ 1 _]" !~ "[1 2 3]"); + assert_match!("[_ 2 _]" =~ "[1 2 3]"); + assert_match!("[_ 3 _]" !~ "[1 2 3]"); + assert_match!("[_ _ 1]" !~ "[1 2 3]"); + assert_match!("[_ _ 2]" !~ "[1 2 3]"); + assert_match!("[_ _ 3]" =~ "[1 2 3]"); + + assert_match!("[1 _ _]" !~ "[2 1 3]"); + assert_match!("[2 _ _]" =~ "[2 1 3]"); + assert_match!("[3 _ _]" !~ "[2 1 3]"); + assert_match!("[_ 1 _]" =~ "[2 1 3]"); + assert_match!("[_ 2 _]" !~ "[2 1 3]"); + assert_match!("[_ 3 _]" !~ "[2 1 3]"); + assert_match!("[_ _ 1]" !~ "[2 1 3]"); + assert_match!("[_ _ 2]" !~ "[2 1 3]"); + assert_match!("[_ _ 3]" =~ "[2 1 3]"); + } + + #[test] + fn test_match_any_in_list_with_multiple_values() { + assert_match!("(_ 2)" =~ "(1 2)"); + assert_match!("(1 _)" =~ "(1 2)"); + assert_match!("(1 _ 3 4)" =~ "(1 2 3 4)"); + assert_match!("(1 (2 (3 _)) 5 (_ 7))" =~ "(1 (2 (3 4)) 5 (6 7))"); + + assert_match!("(_)" =~ "([foo bar])"); + assert_match!("(_)" =~ "((foo bar))"); + assert_match!("(_)" =~ "(#{foo bar})"); + assert_match!("(_)" =~ "({foo bar})"); + + assert_match!("(_ 2)" !~ "(2 1)"); + assert_match!("(1 _)" !~ "(2 1)"); + assert_match!("(1 _ 3)" !~ "(2 1 3)"); + + assert_match!("(_ 2)" !~ "(3 4)"); + assert_match!("(1 _)" !~ "(3 4)"); + } + + #[test] + fn test_match_multiple_any_in_list_with_multiple_values() { + assert_match!("(1 _ _)" =~ "(1 2 3)"); + assert_match!("(2 _ _)" !~ "(1 2 3)"); + assert_match!("(3 _ _)" !~ "(1 2 3)"); + assert_match!("(_ 1 _)" !~ "(1 2 3)"); + assert_match!("(_ 2 _)" =~ "(1 2 3)"); + assert_match!("(_ 3 _)" !~ "(1 2 3)"); + assert_match!("(_ _ 1)" !~ "(1 2 3)"); + assert_match!("(_ _ 2)" !~ "(1 2 3)"); + assert_match!("(_ _ 3)" =~ "(1 2 3)"); + + assert_match!("(1 _ _)" !~ "(2 1 3)"); + assert_match!("(2 _ _)" =~ "(2 1 3)"); + assert_match!("(3 _ _)" !~ "(2 1 3)"); + assert_match!("(_ 1 _)" =~ "(2 1 3)"); + assert_match!("(_ 2 _)" !~ "(2 1 3)"); + assert_match!("(_ 3 _)" !~ "(2 1 3)"); + assert_match!("(_ _ 1)" !~ "(2 1 3)"); + assert_match!("(_ _ 2)" !~ "(2 1 3)"); + assert_match!("(_ _ 3)" =~ "(2 1 3)"); + } + + #[test] + fn test_match_any_in_set_with_multiple_values() { + assert_match!("#{_ 2}" =~ "#{1 2}"); + assert_match!("#{1 _}" =~ "#{1 2}"); + assert_match!("#{1 _ 3 4}" =~ "#{1 2 3 4}"); + assert_match!("#{1 #{2 #{3 _}} 5 #{_ 7}}" =~ "#{1 #{2 #{3 4}} 5 #{6 7}}"); + + assert_match!("#{_}" =~ "#{[foo bar]}"); + assert_match!("#{_}" =~ "#{(foo bar)}"); + assert_match!("#{_}" =~ "#{#{foo bar}}"); + assert_match!("#{_}" =~ "#{{foo bar}}"); + + assert_match!("#{_ 2}" =~ "#{2 1}"); + assert_match!("#{1 _}" =~ "#{2 1}"); + assert_match!("#{1 _ 3}" =~ "#{2 1 3}"); + + assert_match!("#{_ 2}" !~ "#{3 4}"); + assert_match!("#{1 _}" !~ "#{3 4}"); + } + + #[test] + fn test_match_multiple_any_in_set_with_multiple_values() { + // These are false because _ is a symbol and sets guarantee + // uniqueness of children. So pattern matching will fail because + // the pattern is a set of length 2, while the matched edn is a set + // of length 3. If _ were unique, all of these assertions would + // be true. Need to better handle pattern rules. + + assert_match!("#{1 _ _}" !~ "#{1 2 3}"); + assert_match!("#{2 _ _}" !~ "#{1 2 3}"); + assert_match!("#{3 _ _}" !~ "#{1 2 3}"); + assert_match!("#{_ 1 _}" !~ "#{1 2 3}"); + assert_match!("#{_ 2 _}" !~ "#{1 2 3}"); + assert_match!("#{_ 3 _}" !~ "#{1 2 3}"); + assert_match!("#{_ _ 1}" !~ "#{1 2 3}"); + assert_match!("#{_ _ 2}" !~ "#{1 2 3}"); + assert_match!("#{_ _ 3}" !~ "#{1 2 3}"); + + assert_match!("#{1 _ _}" !~ "#{2 1 3}"); + assert_match!("#{2 _ _}" !~ "#{2 1 3}"); + assert_match!("#{3 _ _}" !~ "#{2 1 3}"); + assert_match!("#{_ 1 _}" !~ "#{2 1 3}"); + assert_match!("#{_ 2 _}" !~ "#{2 1 3}"); + assert_match!("#{_ 3 _}" !~ "#{2 1 3}"); + assert_match!("#{_ _ 1}" !~ "#{2 1 3}"); + assert_match!("#{_ _ 2}" !~ "#{2 1 3}"); + assert_match!("#{_ _ 3}" !~ "#{2 1 3}"); + } + + #[test] + fn test_match_any_in_map_with_multiple_values() { + assert_match!("{_ 2}" =~ "{1 2}"); + assert_match!("{1 _}" =~ "{1 2}"); + assert_match!("{1 _, 3 4}" =~ "{1 2, 3 4}"); + assert_match!("{1 {2 {3 _}}, 5 {_ 7}}" =~ "{1 {2 {3 4}}, 5 {6 7}}"); + + assert_match!("{_ _}" =~ "{[foo bar] [baz boz]}"); + assert_match!("{_ _}" =~ "{(foo bar) (baz boz)}"); + assert_match!("{_ _}" =~ "{#{foo bar} #{baz boz}}"); + assert_match!("{_ _}" =~ "{{foo bar} {baz boz}}"); + + assert_match!("{_ 2, 3 4}" =~ "{3 4, 1 2}"); + assert_match!("{1 _, 3 4}" =~ "{3 4, 1 2}"); + assert_match!("{_ _, 3 4}" =~ "{3 4, 1 2}"); + assert_match!("{1 2, _ 4}" =~ "{3 4, 1 2}"); + assert_match!("{1 2, 3 _}" =~ "{3 4, 1 2}"); + assert_match!("{1 2, _ _}" =~ "{3 4, 1 2}"); + assert_match!("{1 2, _ 4, 5 6}" =~ "{3 4, 1 2, 5 6}"); + assert_match!("{1 2, 3 _, 5 6}" =~ "{3 4, 1 2, 5 6}"); + assert_match!("{1 2, _ _, 5 6}" =~ "{3 4, 1 2, 5 6}"); + + assert_match!("{_ 2}" !~ "{3 4}"); + assert_match!("{1 _}" !~ "{3 4}"); + } + + #[test] + fn test_match_multiple_any_in_map_with_multiple_values() { + // These are false because _ is a symbol and maps guarantee + // uniqueness of keys. So pattern matching will fail because + // the pattern is a map of length 2, while the matched edn is a map + // of length 3. If _ were unique, all of these assertions would + // be true. Need to better handle pattern rules. + + assert_match!("{1 2, _ 4, _ 6}" !~ "{1 2, 3 4, 5 6}"); + assert_match!("{3 4, _ 6, _ 2}" !~ "{1 2, 3 4, 5 6}"); + assert_match!("{5 6, _ 2, _ 4}" !~ "{1 2, 3 4, 5 6}"); + + assert_match!("{1 2, _ _, _ _}" !~ "{1 2, 3 4, 5 6}"); + assert_match!("{3 4, _ _, _ _}" !~ "{1 2, 3 4, 5 6}"); + assert_match!("{5 6, _ _, _ _}" !~ "{1 2, 3 4, 5 6}"); + assert_match!("{_ _, 1 2, _ _}" !~ "{1 2, 3 4, 5 6}"); + assert_match!("{_ _, 3 4, _ _}" !~ "{1 2, 3 4, 5 6}"); + assert_match!("{_ _, 5 6, _ _}" !~ "{1 2, 3 4, 5 6}"); + assert_match!("{_ _, _ _, 1 2}" !~ "{1 2, 3 4, 5 6}"); + assert_match!("{_ _, _ _, 3 4}" !~ "{1 2, 3 4, 5 6}"); + assert_match!("{_ _, _ _, 5 6}" !~ "{1 2, 3 4, 5 6}"); + + assert_match!("{1 2, _ _, _ _}" !~ "{3 4, 1 2, 5 6}"); + assert_match!("{3 4, _ _, _ _}" !~ "{3 4, 1 2, 5 6}"); + assert_match!("{5 6, _ _, _ _}" !~ "{3 4, 1 2, 5 6}"); + assert_match!("{_ _, 1 2, _ _}" !~ "{3 4, 1 2, 5 6}"); + assert_match!("{_ _, 3 4, _ _}" !~ "{3 4, 1 2, 5 6}"); + assert_match!("{_ _, 5 6, _ _}" !~ "{3 4, 1 2, 5 6}"); + assert_match!("{_ _, _ _, 1 2}" !~ "{3 4, 1 2, 5 6}"); + assert_match!("{_ _, _ _, 3 4}" !~ "{3 4, 1 2, 5 6}"); + assert_match!("{_ _, _ _, 5 6}" !~ "{3 4, 1 2, 5 6}"); + } + + #[test] + fn test_match_placeholder_simple() { + assert_match!("?x" =~ "nil"); + assert_match!("?x" =~ "true"); + assert_match!("?x" =~ "1"); + assert_match!("?x" =~ "1N"); + assert_match!("?x" =~ "1.0"); + assert_match!("?x" =~ "\"a\""); + assert_match!("?x" =~ "_"); + assert_match!("?x" =~ "symbol"); + assert_match!("?x" =~ "ns/symbol"); + assert_match!("?x" =~ ":keyword"); + assert_match!("?x" =~ ":ns/keyword"); + assert_match!("?x" =~ "[nil, true, 1, \"foo\", bar, :baz]"); + assert_match!("?x" =~ "(nil, true, 1, \"foo\", bar, :baz)"); + assert_match!("?x" =~ "#{nil, true, 1, \"foo\", bar, :baz}"); + assert_match!("?x" =~ "{nil true, 1 \"foo\", bar :baz}"); + } + + #[test] + fn test_match_placeholder_in_same_collection_type_simple() { + assert_match!("[?x]" =~ "[1]"); + assert_match!("(?x)" =~ "(2)"); + assert_match!("#{?x}" =~ "#{3}"); + assert_match!("{?x ?x}" =~ "{4 4}"); + assert_match!("{?x ?x}" !~ "{4 5}"); + assert_match!("{?x ?y}" =~ "{4 4}"); + assert_match!("{?x ?y}" =~ "{4 5}"); + } + + #[test] + fn test_match_placeholder_in_different_collection_type_simple() { + assert_match!("[?x]" !~ "(1)"); + assert_match!("(?x)" !~ "#{2}"); + assert_match!("#{?x}" !~ "[3]"); + assert_match!("{?x ?x}" !~ "[4 5]"); + assert_match!("{?x ?x}" !~ "(6 7)"); + assert_match!("{?x ?x}" !~ "#{8 9}"); + } + + #[test] + fn test_match_placeholder_in_vector_with_multiple_values() { + assert_match!("[?x ?y]" =~ "[1 2]"); + assert_match!("[?x ?y]" =~ "[1 1]"); + assert_match!("[?x ?x]" !~ "[1 2]"); + assert_match!("[?x ?x]" =~ "[1 1]"); + + assert_match!("[1 ?x 3 ?y]" =~ "[1 2 3 4]"); + assert_match!("[1 ?x 3 ?y]" =~ "[1 2 3 2]"); + assert_match!("[1 ?x 3 ?x]" !~ "[1 2 3 4]"); + assert_match!("[1 ?x 3 ?x]" =~ "[1 2 3 2]"); + + assert_match!("[1 [2 [3 ?x]] 5 [?y 7]]" =~ "[1 [2 [3 4]] 5 [6 7]]"); + assert_match!("[1 [2 [3 ?x]] 5 [?y 7]]" =~ "[1 [2 [3 4]] 5 [4 7]]"); + assert_match!("[1 [2 [3 ?x]] 5 [?x 7]]" !~ "[1 [2 [3 4]] 5 [6 7]]"); + assert_match!("[1 [2 [3 ?x]] 5 [?y 7]]" =~ "[1 [2 [3 4]] 5 [4 7]]"); + + assert_match!("[?x ?y ?x ?y]" =~ "[1 2 1 2]"); + assert_match!("[?x ?y ?x ?y]" !~ "[1 2 2 1]"); + + assert_match!("[[?x ?y] [?x ?y]]" =~ "[[1 2] [1 2]]"); + assert_match!("[[?x ?y] [?x ?y]]" !~ "[[1 2] [2 1]]"); + } + + #[test] + fn test_match_placeholder_in_list_with_multiple_values() { + assert_match!("(?x ?y)" =~ "(1 2)"); + assert_match!("(?x ?y)" =~ "(1 1)"); + assert_match!("(?x ?x)" !~ "(1 2)"); + assert_match!("(?x ?x)" =~ "(1 1)"); + + assert_match!("(1 ?x 3 ?y)" =~ "(1 2 3 4)"); + assert_match!("(1 ?x 3 ?y)" =~ "(1 2 3 2)"); + assert_match!("(1 ?x 3 ?x)" !~ "(1 2 3 4)"); + assert_match!("(1 ?x 3 ?x)" =~ "(1 2 3 2)"); + + assert_match!("(1 (2 (3 ?x)) 5 (?y 7))" =~ "(1 (2 (3 4)) 5 (6 7))"); + assert_match!("(1 (2 (3 ?x)) 5 (?y 7))" =~ "(1 (2 (3 4)) 5 (4 7))"); + assert_match!("(1 (2 (3 ?x)) 5 (?x 7))" !~ "(1 (2 (3 4)) 5 (6 7))"); + assert_match!("(1 (2 (3 ?x)) 5 (?y 7))" =~ "(1 (2 (3 4)) 5 (4 7))"); + + assert_match!("(?x ?y ?x ?y)" =~ "(1 2 1 2)"); + assert_match!("(?x ?y ?x ?y)" !~ "(1 2 2 1)"); + + assert_match!("((?x ?y) (?x ?y))" =~ "((1 2) (1 2))"); + assert_match!("((?x ?y) (?x ?y))" !~ "((1 2) (2 1))"); + } + + #[test] + fn test_match_placeholder_in_set_with_multiple_values() { + assert_match!("#{?x ?y}" =~ "#{1 2}"); + assert_match!("#{?x ?y}" !~ "#{1 1}"); + assert_match!("#{?x ?x}" !~ "#{1 2}"); + assert_match!("#{?x ?x}" =~ "#{1 1}"); + + assert_match!("#{1 ?x 3 ?y}" =~ "#{1 2 3 4}"); + assert_match!("#{1 ?x 3 ?y}" !~ "#{1 2 3 2}"); + assert_match!("#{1 ?x 3 ?x}" !~ "#{1 2 3 4}"); + assert_match!("#{1 ?x 3 ?x}" =~ "#{1 2 3 2}"); + + assert_match!("#{1 #{2 #{3 ?x}} 5 #{?y 7}}" =~ "#{1 #{2 #{3 4}} 5 #{6 7}}"); + assert_match!("#{1 #{2 #{3 ?x}} 5 #{?y 7}}" =~ "#{1 #{2 #{3 4}} 5 #{4 7}}"); + assert_match!("#{1 #{2 #{3 ?x}} 5 #{?x 7}}" !~ "#{1 #{2 #{3 4}} 5 #{6 7}}"); + assert_match!("#{1 #{2 #{3 ?x}} 5 #{?y 7}}" =~ "#{1 #{2 #{3 4}} 5 #{4 7}}"); + + assert_match!("#{?x ?y ?x ?y}" =~ "#{1 2 1 2}"); + assert_match!("#{?x ?y ?x ?y}" =~ "#{1 2 2 1}"); + + assert_match!("#{#{?x ?y} #{?x ?y}}" =~ "#{#{1 2} #{1 2}}"); + assert_match!("#{#{?x ?y} #{?x ?y}}" =~ "#{#{1 2} #{2 1}}"); + } + + #[test] + fn test_match_placeholder_in_map_with_multiple_values() { + assert_match!("{?x ?y}" =~ "{1 2}"); + assert_match!("{?x ?y}" =~ "{1 1}"); + assert_match!("{?x ?x}" !~ "{1 2}"); + assert_match!("{?x ?x}" =~ "{1 1}"); + + assert_match!("{1 ?x, 3 ?y}" =~ "{1 2, 3 4}"); + assert_match!("{1 ?x, 3 ?y}" =~ "{1 2, 3 2}"); + assert_match!("{1 ?x, 3 ?x}" !~ "{1 2, 3 4}"); + assert_match!("{1 ?x, 3 ?x}" =~ "{1 2, 3 2}"); + + assert_match!("{1 {2 {3 ?x}}, 5 {?y 7}}" =~ "{1 {2 {3 4}}, 5 {6 7}}"); + assert_match!("{1 {2 {3 ?x}}, 5 {?y 7}}" =~ "{1 {2 {3 4}}, 5 {4 7}}"); + assert_match!("{1 {2 {3 ?x}}, 5 {?x 7}}" !~ "{1 {2 {3 4}}, 5 {6 7}}"); + assert_match!("{1 {2 {3 ?x}}, 5 {?y 7}}" =~ "{1 {2 {3 4}}, 5 {4 7}}"); + + assert_match!("{?x ?y, ?x ?y}" =~ "{1 2, 1 2}"); + assert_match!("{?x ?y, ?x ?y}" !~ "{1 2, 2 1}"); + + assert_match!("{{?x ?y}, {?x ?y}}" =~ "{{1 2}, {1 2}}"); + assert_match!("{{?x ?y}, {?x ?y}}" !~ "{{1 2}, {2 1}}"); + } + + #[test] + fn test_match_placeholder_in_different_value_types() { + assert_match!("{1 {2 [3 ?x]}, 5 (?y 7)}" =~ "{1 {2 [3 4]}, 5 (6 7)}"); + assert_match!("{1 {2 [3 ?x]}, 5 (?y 7)}" =~ "{1 {2 [3 4]}, 5 (4 7)}"); + assert_match!("{1 {2 [3 ?x]}, 5 (?x 7)}" !~ "{1 {2 [3 4]}, 5 (6 7)}"); + assert_match!("{1 {2 [3 ?x]}, 5 (?y 7)}" =~ "{1 {2 [3 4]}, 5 (4 7)}"); + + assert_match!("{?x {?x [?x ?x]}, ?x (?x ?x)}" !~ "{1 {2 [3 4]}, 5 (6 7)}"); + assert_match!("{?x {?x [?x ?x]}, ?x (?x ?x)}" =~ "{1 {1 [1 1]}, 1 (1 1)}"); + + assert_match!("[#{?x ?y} ?x]" =~ "[#{1 2} 1]"); + assert_match!("[#{?x ?y} ?y]" =~ "[#{1 2} 2]"); + } +} \ No newline at end of file