Commits

Liam Staskawicz committed 5ddec10

pattern: add set matching

Comments (0)

Files changed (2)

 			return false
 
 		case '[':
-			return false
+			var result bool
+			if result, pattern = matchSet(pattern, rune(test[0])); !result {
+				return false
+			}
+			test = test[1:]
 
 		case '{':
 			return false
 
 	panic("unreachable - failure in osc.PatternMatch()")
 }
+
+// spec: "a string of characters in square brackets (e.g., "[string]")
+// in the OSC Address Pattern matches any character in the string."
+func matchSet(pattern string, test rune) (result bool, patternout string) {
+
+	// "An exclamation point at the beginning of a bracketed string
+	// negates the sense of the list, meaning that the list matches
+	// any character not in the list. (An exclamation point anywhere
+	// besides the first character after the open bracket has no special meaning.)"
+	negated := false
+	if pattern[1] == '!' {
+		negated = true
+		pattern = pattern[1:]
+	}
+
+	for i, c := range pattern {
+		// if we got to the closing bracket without matching,
+		// 'negated' specifies whether that's what was actually asked for
+		if c == ']' {
+			return negated, ""
+		}
+
+		// "two characters separated by a minus sign indicate the
+		// range of characters between the given two in ASCII collating sequence."
+		// ie, check for 'a-z' pattern and skip anything else up to the closing ]
+		if pattern[i+1] == '-' && i+3 < len(pattern) {
+			if test >= c && test <= rune(pattern[i+2]) {
+				if negated {
+					return false, ""
+				}
+				return incrementUntil(pattern[i+2:], ']')
+			}
+		}
+
+		// otherwise check for normal inclusion in the set
+		if c == test {
+			if negated {
+				return false, ""
+			}
+			return incrementUntil(pattern[i:], ']')
+		}
+	}
+
+	// we didn't find the closing bracket
+	return false, ""
+}
+
+func incrementUntil(s string, b rune) (bool, string) {
+
+	for i, c := range s {
+		if c == b {
+			return true, s[i+1:]
+		}
+	}
+
+	return false, ""
+}
 		t.Error()
 	}
 }
+
+func TestSet(t *testing.T) {
+
+	// set range is inclusive
+	// make sure we catch either extreme and a couple instances in between
+	if !PatternMatch("/[a-z]", "/a") {
+		t.Error()
+	}
+
+	if !PatternMatch("/[a-z]", "/z") {
+		t.Error()
+	}
+
+	if !PatternMatch("/[a-z]", "/m") {
+		t.Error()
+	}
+
+	// non-range mode
+	if !PatternMatch("/[abc]", "/b") {
+		t.Error()
+	}
+
+	if !PatternMatch("/[abc]", "/c") {
+		t.Error()
+	}
+
+	if PatternMatch("/[abc]", "/d") {
+		t.Error()
+	}
+
+	if PatternMatch("/[abc]", "/d") {
+		t.Error()
+	}
+
+	// at an offset into the pattern
+	if !PatternMatch("/a[xyz]c", "/ayc") {
+		t.Error()
+	}
+
+	// negated
+	if !PatternMatch("/[!abc]", "/d") {
+		t.Error()
+	}
+}