793 lines
34 KiB
C++
793 lines
34 KiB
C++
|
/*
|
||
|
==============================================================================
|
||
|
|
||
|
This file is part of the JUCE library.
|
||
|
Copyright (c) 2020 - Raw Material Software Limited
|
||
|
|
||
|
JUCE is an open source library subject to commercial or open-source
|
||
|
licensing.
|
||
|
|
||
|
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||
|
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||
|
|
||
|
End User License Agreement: www.juce.com/juce-6-licence
|
||
|
Privacy Policy: www.juce.com/juce-privacy-policy
|
||
|
|
||
|
Or: You may also use this code under the terms of the GPL v3 (see
|
||
|
www.gnu.org/licenses).
|
||
|
|
||
|
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||
|
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||
|
DISCLAIMED.
|
||
|
|
||
|
==============================================================================
|
||
|
*/
|
||
|
|
||
|
namespace juce
|
||
|
{
|
||
|
|
||
|
namespace
|
||
|
{
|
||
|
//==============================================================================
|
||
|
template <typename CharPointerType>
|
||
|
class OSCPatternMatcherImpl
|
||
|
{
|
||
|
using CharPtr = CharPointerType;
|
||
|
|
||
|
public:
|
||
|
//==============================================================================
|
||
|
static bool match (CharPtr pattern, CharPtr patternEnd, CharPtr target, CharPtr targetEnd)
|
||
|
{
|
||
|
if (pattern == patternEnd)
|
||
|
return matchTerminator (target, targetEnd);
|
||
|
|
||
|
auto c = pattern.getAndAdvance();
|
||
|
|
||
|
switch (c)
|
||
|
{
|
||
|
case '?': return matchAnyChar (pattern, patternEnd, target, targetEnd);
|
||
|
case '*': return matchAnyOrNoChars (pattern, patternEnd, target, targetEnd);
|
||
|
case '{': return matchInsideStringSet (pattern, patternEnd, target, targetEnd);
|
||
|
case '[': return matchInsideCharSet (pattern, patternEnd, target, targetEnd);
|
||
|
default: return matchChar (c, pattern, patternEnd, target, targetEnd);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
//==============================================================================
|
||
|
static bool matchTerminator (CharPtr target, CharPtr targetEnd)
|
||
|
{
|
||
|
return target == targetEnd;
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
static bool matchChar (juce_wchar c, CharPtr pattern, CharPtr patternEnd, CharPtr target, CharPtr targetEnd)
|
||
|
{
|
||
|
if (target == targetEnd || c != target.getAndAdvance())
|
||
|
return false;
|
||
|
|
||
|
return match (pattern, patternEnd, target, targetEnd);
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
static bool matchAnyChar (CharPtr pattern, CharPtr patternEnd, CharPtr target, CharPtr targetEnd)
|
||
|
{
|
||
|
if (target == targetEnd)
|
||
|
return false;
|
||
|
|
||
|
return match (pattern, patternEnd, ++target, targetEnd);
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
static bool matchAnyOrNoChars (CharPtr pattern, CharPtr patternEnd, CharPtr target, CharPtr targetEnd)
|
||
|
{
|
||
|
if (target == targetEnd)
|
||
|
return pattern == patternEnd;
|
||
|
|
||
|
if (match (pattern, patternEnd, target, targetEnd))
|
||
|
return true;
|
||
|
|
||
|
return matchAnyOrNoChars (pattern, patternEnd, ++target, targetEnd);
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
static bool matchInsideStringSet (CharPtr pattern, CharPtr patternEnd, CharPtr target, CharPtr targetEnd)
|
||
|
{
|
||
|
if (pattern == patternEnd)
|
||
|
return false;
|
||
|
|
||
|
// Note: In case this code is ever moved into the more generic CharPointerFunctions,
|
||
|
// the next two lines probably will not compile as soon as this class is used with a
|
||
|
// Char template type parameter that is not the same type as String::Char.
|
||
|
StringArray set;
|
||
|
String currentElement;
|
||
|
|
||
|
while (pattern != patternEnd)
|
||
|
{
|
||
|
auto c = pattern.getAndAdvance();
|
||
|
|
||
|
switch (c)
|
||
|
{
|
||
|
case '}':
|
||
|
set.add (currentElement);
|
||
|
currentElement.clear();
|
||
|
return matchStringSet (set, pattern, patternEnd, target, targetEnd);
|
||
|
|
||
|
case ',':
|
||
|
set.add (currentElement);
|
||
|
currentElement.clear();
|
||
|
continue;
|
||
|
|
||
|
default:
|
||
|
currentElement += c;
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
static bool matchStringSet (const StringArray& set, CharPtr pattern,
|
||
|
CharPtr patternEnd, CharPtr target, CharPtr targetEnd)
|
||
|
{
|
||
|
if (set.size() == 0)
|
||
|
return match (pattern, patternEnd, target, targetEnd);
|
||
|
|
||
|
for (auto& str : set)
|
||
|
if (str.getCharPointer().compareUpTo (target, str.length()) == 0)
|
||
|
if (match (pattern, patternEnd, target + str.length(), targetEnd))
|
||
|
return true;
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
static bool matchInsideCharSet (CharPtr pattern, CharPtr patternEnd,
|
||
|
CharPtr target, CharPtr targetEnd)
|
||
|
{
|
||
|
if (pattern == patternEnd)
|
||
|
return false;
|
||
|
|
||
|
Array<juce_wchar> set;
|
||
|
bool setIsNegated = false;
|
||
|
|
||
|
while (pattern != patternEnd)
|
||
|
{
|
||
|
auto c = pattern.getAndAdvance();
|
||
|
|
||
|
switch (c)
|
||
|
{
|
||
|
case ']':
|
||
|
return matchCharSet (set, setIsNegated, pattern, patternEnd, target, targetEnd);
|
||
|
|
||
|
case '-':
|
||
|
if (! addCharRangeToSet (set, pattern, patternEnd, target, targetEnd))
|
||
|
return false;
|
||
|
|
||
|
break;
|
||
|
|
||
|
case '!':
|
||
|
if (set.size() == 0 && setIsNegated == false)
|
||
|
{
|
||
|
setIsNegated = true;
|
||
|
break;
|
||
|
}
|
||
|
// else = special case: fall through to default and treat '!' as a non-special character.
|
||
|
JUCE_FALLTHROUGH
|
||
|
|
||
|
default:
|
||
|
set.add (c);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
static bool matchCharSet (const Array<juce_wchar>& set, bool setIsNegated,
|
||
|
CharPtr pattern, CharPtr patternEnd, CharPtr target, CharPtr targetEnd)
|
||
|
{
|
||
|
if (set.size() == 0)
|
||
|
return match (pattern, patternEnd, target, targetEnd);
|
||
|
|
||
|
if (target == targetEnd)
|
||
|
return false;
|
||
|
|
||
|
return setIsNegated ? matchCharSetNegated (set, pattern, patternEnd, target, targetEnd)
|
||
|
: matchCharSetNotNegated (set, pattern, patternEnd, target, targetEnd);
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
static bool matchCharSetNegated (const Array<juce_wchar>& set, CharPtr pattern,
|
||
|
CharPtr patternEnd, CharPtr target, CharPtr targetEnd)
|
||
|
{
|
||
|
for (auto c : set)
|
||
|
if (*target == c)
|
||
|
return false;
|
||
|
|
||
|
return match (pattern, patternEnd, target + 1, targetEnd);
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
static bool matchCharSetNotNegated (const Array<juce_wchar>& set, CharPtr pattern,
|
||
|
CharPtr patternEnd, CharPtr target, CharPtr targetEnd)
|
||
|
{
|
||
|
for (auto c : set)
|
||
|
if (*target == c)
|
||
|
if (match (pattern, patternEnd, target + 1, targetEnd))
|
||
|
return true;
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
static bool addCharRangeToSet (Array<juce_wchar>& set, CharPtr pattern,
|
||
|
CharPtr /*patternEnd*/, CharPtr target, CharPtr targetEnd)
|
||
|
{
|
||
|
if (target == targetEnd)
|
||
|
return false;
|
||
|
|
||
|
auto rangeStart = set.getLast();
|
||
|
auto rangeEnd = pattern.getAndAdvance();
|
||
|
|
||
|
if (rangeEnd == ']')
|
||
|
{
|
||
|
set.add ('-'); // special case: '-' has no special meaning at the end.
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if (rangeEnd == ',' || rangeEnd == '{' || rangeEnd == '}' || set.size() == 0)
|
||
|
return false;
|
||
|
|
||
|
while (rangeEnd > rangeStart)
|
||
|
set.add (++rangeStart);
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
//==============================================================================
|
||
|
static bool matchOscPattern (const String& pattern, const String& target)
|
||
|
{
|
||
|
return OSCPatternMatcherImpl<String::CharPointerType>::match (pattern.getCharPointer(),
|
||
|
pattern.getCharPointer().findTerminatingNull(),
|
||
|
target.getCharPointer(),
|
||
|
target.getCharPointer().findTerminatingNull());
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
template <typename OSCAddressType> struct OSCAddressTokeniserTraits;
|
||
|
template <> struct OSCAddressTokeniserTraits<OSCAddress> { static const char* getDisallowedChars() { return " #*,?/[]{}"; } };
|
||
|
template <> struct OSCAddressTokeniserTraits<OSCAddressPattern> { static const char* getDisallowedChars() { return " #/"; } };
|
||
|
|
||
|
//==============================================================================
|
||
|
template <typename OSCAddressType>
|
||
|
struct OSCAddressTokeniser
|
||
|
{
|
||
|
using Traits = OSCAddressTokeniserTraits<OSCAddressType>;
|
||
|
|
||
|
//==============================================================================
|
||
|
static bool isPrintableASCIIChar (juce_wchar c) noexcept
|
||
|
{
|
||
|
return c >= ' ' && c <= '~';
|
||
|
}
|
||
|
|
||
|
static bool isDisallowedChar (juce_wchar c) noexcept
|
||
|
{
|
||
|
return CharPointer_ASCII (Traits::getDisallowedChars()).indexOf (c, false) >= 0;
|
||
|
}
|
||
|
|
||
|
static bool containsOnlyAllowedPrintableASCIIChars (const String& string) noexcept
|
||
|
{
|
||
|
for (auto charPtr = string.getCharPointer(); ! charPtr.isEmpty();)
|
||
|
{
|
||
|
auto c = charPtr.getAndAdvance();
|
||
|
|
||
|
if (! isPrintableASCIIChar (c) || isDisallowedChar (c))
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
static StringArray tokenise (const String& address)
|
||
|
{
|
||
|
if (address.isEmpty())
|
||
|
throw OSCFormatError ("OSC format error: address string cannot be empty.");
|
||
|
|
||
|
if (! address.startsWithChar ('/'))
|
||
|
throw OSCFormatError ("OSC format error: address string must start with a forward slash.");
|
||
|
|
||
|
StringArray oscSymbols;
|
||
|
oscSymbols.addTokens (address, "/", StringRef());
|
||
|
oscSymbols.removeEmptyStrings (false);
|
||
|
|
||
|
for (auto& token : oscSymbols)
|
||
|
if (! containsOnlyAllowedPrintableASCIIChars (token))
|
||
|
throw OSCFormatError ("OSC format error: encountered characters not allowed in address string.");
|
||
|
|
||
|
return oscSymbols;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
} // namespace
|
||
|
|
||
|
//==============================================================================
|
||
|
OSCAddress::OSCAddress (const String& address)
|
||
|
: oscSymbols (OSCAddressTokeniser<OSCAddress>::tokenise (address)),
|
||
|
asString (address.trimCharactersAtEnd ("/"))
|
||
|
{
|
||
|
}
|
||
|
|
||
|
OSCAddress::OSCAddress (const char* address)
|
||
|
: oscSymbols (OSCAddressTokeniser<OSCAddress>::tokenise (String (address))),
|
||
|
asString (String (address).trimCharactersAtEnd ("/"))
|
||
|
{
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
bool OSCAddress::operator== (const OSCAddress& other) const noexcept
|
||
|
{
|
||
|
return asString == other.asString;
|
||
|
}
|
||
|
|
||
|
bool OSCAddress::operator!= (const OSCAddress& other) const noexcept
|
||
|
{
|
||
|
return ! operator== (other);
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
String OSCAddress::toString() const noexcept
|
||
|
{
|
||
|
return asString;
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
OSCAddressPattern::OSCAddressPattern (const String& address)
|
||
|
: oscSymbols (OSCAddressTokeniser<OSCAddressPattern>::tokenise (address)),
|
||
|
asString (address.trimCharactersAtEnd ("/")),
|
||
|
wasInitialisedWithWildcards (asString.containsAnyOf ("*?{}[]"))
|
||
|
|
||
|
{
|
||
|
}
|
||
|
|
||
|
OSCAddressPattern::OSCAddressPattern (const char* address)
|
||
|
: oscSymbols (OSCAddressTokeniser<OSCAddressPattern>::tokenise (String (address))),
|
||
|
asString (String (address).trimCharactersAtEnd ("/")),
|
||
|
wasInitialisedWithWildcards (asString.containsAnyOf ("*?{}[]"))
|
||
|
{
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
bool OSCAddressPattern::operator== (const OSCAddressPattern& other) const noexcept
|
||
|
{
|
||
|
return asString == other.asString;
|
||
|
}
|
||
|
|
||
|
bool OSCAddressPattern::operator!= (const OSCAddressPattern& other) const noexcept
|
||
|
{
|
||
|
return ! operator== (other);
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
bool OSCAddressPattern::matches (const OSCAddress& address) const noexcept
|
||
|
{
|
||
|
if (! containsWildcards())
|
||
|
return asString == address.asString;
|
||
|
|
||
|
if (oscSymbols.size() != address.oscSymbols.size())
|
||
|
return false;
|
||
|
|
||
|
for (int i = 0; i < oscSymbols.size(); ++i)
|
||
|
if (! matchOscPattern (oscSymbols[i], address.oscSymbols[i]))
|
||
|
return false;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
String OSCAddressPattern::toString() const noexcept
|
||
|
{
|
||
|
return asString;
|
||
|
}
|
||
|
|
||
|
|
||
|
//==============================================================================
|
||
|
//==============================================================================
|
||
|
#if JUCE_UNIT_TESTS
|
||
|
|
||
|
class OSCAddressTests : public UnitTest
|
||
|
{
|
||
|
public:
|
||
|
OSCAddressTests()
|
||
|
: UnitTest ("OSCAddress class", UnitTestCategories::osc)
|
||
|
{}
|
||
|
|
||
|
void runTest()
|
||
|
{
|
||
|
beginTest ("construction and parsing");
|
||
|
{
|
||
|
expectThrowsType (OSCAddress (""), OSCFormatError);
|
||
|
expectThrowsType (OSCAddress ("noleadingslash"), OSCFormatError);
|
||
|
expectThrowsType (OSCAddress ("/notallowedchar "), OSCFormatError);
|
||
|
expectThrowsType (OSCAddress ("/notallowedchar#"), OSCFormatError);
|
||
|
expectThrowsType (OSCAddress ("/notallowedchar*"), OSCFormatError);
|
||
|
expectThrowsType (OSCAddress ("/notallowedchar,"), OSCFormatError);
|
||
|
expectThrowsType (OSCAddress ("/notallowedchar?"), OSCFormatError);
|
||
|
expectThrowsType (OSCAddress ("/notallowedchar["), OSCFormatError);
|
||
|
expectThrowsType (OSCAddress ("/notallowedchar]"), OSCFormatError);
|
||
|
expectThrowsType (OSCAddress ("/notallowedchar{"), OSCFormatError);
|
||
|
expectThrowsType (OSCAddress ("/notallowedchar}andsomemorechars"), OSCFormatError);
|
||
|
expectThrowsType (OSCAddress (String::fromUTF8 ("/nonasciicharacter\xc3\xbc""blabla")), OSCFormatError);
|
||
|
expectThrowsType (OSCAddress ("/nonprintableasciicharacter\t"), OSCFormatError);
|
||
|
|
||
|
expectDoesNotThrow (OSCAddress ("/"));
|
||
|
expectDoesNotThrow (OSCAddress ("/a"));
|
||
|
expectDoesNotThrow (OSCAddress ("/a/"));
|
||
|
expectDoesNotThrow (OSCAddress ("/a/bcd/"));
|
||
|
expectDoesNotThrow (OSCAddress ("/abcd/efgh/ijkLMNOPq/666r/s"));
|
||
|
expectDoesNotThrow (OSCAddress ("/allowedprintablecharacters!$%&()+-.^_`|~"));
|
||
|
expectDoesNotThrow (OSCAddress ("/additonalslashes//will///be////ignored"));
|
||
|
}
|
||
|
|
||
|
beginTest ("conversion to/from String");
|
||
|
{
|
||
|
OSCAddress address ("/this/is/a/very/long/address/");
|
||
|
expectEquals (address.toString(), String ("/this/is/a/very/long/address"));
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
static OSCAddressTests OSCAddressUnitTests;
|
||
|
|
||
|
//==============================================================================
|
||
|
|
||
|
class OSCAddressPatternTests : public UnitTest
|
||
|
{
|
||
|
public:
|
||
|
OSCAddressPatternTests()
|
||
|
: UnitTest ("OSCAddressPattern class", UnitTestCategories::osc)
|
||
|
{}
|
||
|
|
||
|
void runTest()
|
||
|
{
|
||
|
beginTest ("construction and parsing");
|
||
|
{
|
||
|
expectThrowsType (OSCAddressPattern (""), OSCFormatError);
|
||
|
expectThrowsType (OSCAddressPattern ("noleadingslash"), OSCFormatError);
|
||
|
expectThrowsType (OSCAddressPattern ("/notallowedchar "), OSCFormatError);
|
||
|
expectThrowsType (OSCAddressPattern ("/notallowedchar#andsomemorechars"), OSCFormatError);
|
||
|
expectThrowsType (OSCAddressPattern (String::fromUTF8 ("/nonasciicharacter\xc3\xbc""blabla")), OSCFormatError);
|
||
|
expectThrowsType (OSCAddressPattern ("/nonprintableasciicharacter\t"), OSCFormatError);
|
||
|
|
||
|
expectDoesNotThrow (OSCAddressPattern ("/"));
|
||
|
expectDoesNotThrow (OSCAddressPattern ("/a"));
|
||
|
expectDoesNotThrow (OSCAddressPattern ("/a/"));
|
||
|
expectDoesNotThrow (OSCAddressPattern ("/a/bcd/"));
|
||
|
expectDoesNotThrow (OSCAddressPattern ("/abcd/efgh/ijkLMNOPq/666r/s"));
|
||
|
expectDoesNotThrow (OSCAddressPattern ("/allowedprintablecharacters!$%&()+-.:;<=>@^_`|~"));
|
||
|
expectDoesNotThrow (OSCAddressPattern ("/additonalslashes//will///be////ignored"));
|
||
|
}
|
||
|
|
||
|
beginTest ("construction and parsing - with wildcards");
|
||
|
{
|
||
|
expectDoesNotThrow (OSCAddressPattern ("/foo/b?r/"));
|
||
|
expectDoesNotThrow (OSCAddressPattern ("/?????"));
|
||
|
expectDoesNotThrow (OSCAddressPattern ("/foo/b*r"));
|
||
|
expectDoesNotThrow (OSCAddressPattern ("/**"));
|
||
|
expectDoesNotThrow (OSCAddressPattern ("/?/b/*c"));
|
||
|
}
|
||
|
|
||
|
beginTest ("construction and parsing - with match expressions");
|
||
|
{
|
||
|
expectDoesNotThrow (OSCAddressPattern ("/{}"));
|
||
|
expectDoesNotThrow (OSCAddressPattern ("/{foo}"));
|
||
|
expectDoesNotThrow (OSCAddressPattern ("/{foo,bar,baz}"));
|
||
|
expectDoesNotThrow (OSCAddressPattern ("/[]"));
|
||
|
expectDoesNotThrow (OSCAddressPattern ("/[abcde]"));
|
||
|
expectDoesNotThrow (OSCAddressPattern ("/[a-e]"));
|
||
|
expectDoesNotThrow (OSCAddressPattern ("/foo/[a-z]x{foo,bar}/*BAZ42/"));
|
||
|
|
||
|
/* Note: If malformed expressions are used, e.g. "bracenotclosed{" or "{a-e}" or "[-foo]",
|
||
|
this should not throw at construction time. Instead it should simply fail any pattern match later.
|
||
|
So there is no need to test for those.
|
||
|
The reason is that we do not actually parse the expressions now, but only during matching.
|
||
|
*/
|
||
|
}
|
||
|
|
||
|
beginTest ("equality comparison");
|
||
|
{
|
||
|
{
|
||
|
OSCAddressPattern lhs ("/test/1");
|
||
|
OSCAddressPattern rhs ("/test/1");
|
||
|
expect (lhs == rhs);
|
||
|
expect (! (lhs != rhs));
|
||
|
}
|
||
|
{
|
||
|
OSCAddressPattern lhs ("/test/1");
|
||
|
OSCAddressPattern rhs ("/test/1/");
|
||
|
expect (lhs == rhs);
|
||
|
expect (! (lhs != rhs));
|
||
|
}
|
||
|
{
|
||
|
OSCAddressPattern lhs ("/test/1");
|
||
|
OSCAddressPattern rhs ("/test/2");
|
||
|
expect (! (lhs == rhs));
|
||
|
expect (lhs != rhs);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
beginTest ("basic string matching");
|
||
|
{
|
||
|
/* Note: The actual expression matching is tested in OSCPatternMatcher, so here we just
|
||
|
do some basic tests and check if the matching works with multi-part addresses.
|
||
|
*/
|
||
|
{
|
||
|
OSCAddressPattern pattern ("/foo/bar");
|
||
|
expect (! pattern.containsWildcards());
|
||
|
|
||
|
OSCAddress address ("/foo/bar");
|
||
|
expect (pattern.matches (address));
|
||
|
}
|
||
|
{
|
||
|
OSCAddressPattern pattern ("/foo/bar/");
|
||
|
expect (! pattern.containsWildcards());
|
||
|
|
||
|
OSCAddress address ("/foo/bar");
|
||
|
expect (pattern.matches (address));
|
||
|
}
|
||
|
{
|
||
|
OSCAddressPattern pattern ("/");
|
||
|
expect (! pattern.containsWildcards());
|
||
|
|
||
|
OSCAddress address ("/");
|
||
|
expect (pattern.matches (address));
|
||
|
}
|
||
|
{
|
||
|
OSCAddressPattern pattern ("/foo/bar");
|
||
|
expect (! pattern.containsWildcards());
|
||
|
|
||
|
expect (! pattern.matches (OSCAddress ("/foo/baz")));
|
||
|
expect (! pattern.matches (OSCAddress ("/foo/bar/baz")));
|
||
|
expect (! pattern.matches (OSCAddress ("/foo")));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
beginTest ("string matching with wildcards");
|
||
|
{
|
||
|
OSCAddressPattern pattern ("/*/*put/slider[0-9]");
|
||
|
expect (pattern.containsWildcards());
|
||
|
|
||
|
expect (pattern.matches (OSCAddress ("/mypatch/input/slider0")));
|
||
|
expect (pattern.matches (OSCAddress ("/myotherpatch/output/slider9")));
|
||
|
expect (! pattern.matches (OSCAddress ("/myotherpatch/output/slider10")));
|
||
|
expect (! pattern.matches (OSCAddress ("/output/slider9")));
|
||
|
expect (! pattern.matches (OSCAddress ("/myotherpatch/output/slider9/position")));
|
||
|
}
|
||
|
|
||
|
beginTest ("conversion to/from String");
|
||
|
{
|
||
|
{
|
||
|
OSCAddressPattern ap ("/this/is/a/very/long/address/");
|
||
|
expectEquals (ap.toString(), String ("/this/is/a/very/long/address"));
|
||
|
}
|
||
|
{
|
||
|
OSCAddressPattern ap ("/*/*put/{fader,slider,knob}[0-9]/ba?/");
|
||
|
expectEquals (ap.toString(), String ("/*/*put/{fader,slider,knob}[0-9]/ba?"));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
static OSCAddressPatternTests OSCAddressPatternUnitTests;
|
||
|
|
||
|
//==============================================================================
|
||
|
|
||
|
class OSCPatternMatcherTests : public UnitTest
|
||
|
{
|
||
|
public:
|
||
|
OSCPatternMatcherTests()
|
||
|
: UnitTest ("OSCAddress class / pattern matching", UnitTestCategories::osc)
|
||
|
{}
|
||
|
|
||
|
void runTest()
|
||
|
{
|
||
|
beginTest ("basic string matching");
|
||
|
{
|
||
|
expect (matchOscPattern ("", ""));
|
||
|
expect (! matchOscPattern ("", "x"));
|
||
|
expect (! matchOscPattern ("x", ""));
|
||
|
expect (matchOscPattern ("foo", "foo"));
|
||
|
expect (! matchOscPattern ("foo", "bar"));
|
||
|
expect (! matchOscPattern ("ooooo", "oooooo"));
|
||
|
}
|
||
|
|
||
|
beginTest ("string matching with '?' wildcard");
|
||
|
{
|
||
|
expect (matchOscPattern ("?", "x"));
|
||
|
expect (! matchOscPattern ("?", ""));
|
||
|
expect (! matchOscPattern ("?", "xx"));
|
||
|
expect (! matchOscPattern ("b?r", "br"));
|
||
|
expect (matchOscPattern ("b?r", "bar"));
|
||
|
expect (! matchOscPattern ("b?r", "baar"));
|
||
|
expect (matchOscPattern ("f???o", "fabco"));
|
||
|
expect (! matchOscPattern ("f???o", "fabo"));
|
||
|
}
|
||
|
|
||
|
beginTest ("string matching with '*' wildcard");
|
||
|
{
|
||
|
expect (matchOscPattern ("*", ""));
|
||
|
expect (matchOscPattern ("*", "x"));
|
||
|
expect (matchOscPattern ("*", "foo"));
|
||
|
expect (matchOscPattern ("*c", "aaaaaaabc"));
|
||
|
expect (matchOscPattern ("*c", "aaaaaaabbbcccc"));
|
||
|
expect (! matchOscPattern ("*c", "aaaaaaabbbcccca"));
|
||
|
expect (matchOscPattern ("c*", "ccccbbbbaaaa"));
|
||
|
expect (! matchOscPattern ("c*", "accccbbbaaaa"));
|
||
|
|
||
|
expect (matchOscPattern ("f*o", "fo"));
|
||
|
expect (matchOscPattern ("f*o", "fuo"));
|
||
|
expect (matchOscPattern ("f*o", "fuvwxyzo"));
|
||
|
|
||
|
expect (matchOscPattern ("*reallyreallylongstringstringstring", "reallyreallylongstringstringstrNOT"
|
||
|
"reallyreallylongstringstringstrNOT"
|
||
|
"reallyreallylongstringstringstrNOT"
|
||
|
"reallyreallylongstringstringstrNOT"
|
||
|
"reallyreallylongstringstringstrNOT"
|
||
|
"reallyreallylongstringstringstring"));
|
||
|
}
|
||
|
|
||
|
beginTest ("string matching with '{..., ...}' pattern");
|
||
|
{
|
||
|
expect (matchOscPattern ("{}", ""));
|
||
|
expect (! matchOscPattern ("{}", "x"));
|
||
|
expect (matchOscPattern ("{abcde}", "abcde"));
|
||
|
expect (matchOscPattern ("{abcde,f}", "f"));
|
||
|
expect (! matchOscPattern ("{abcde,f}", "ff"));
|
||
|
expect (matchOscPattern ("a{bcd}e", "abcde"));
|
||
|
expect (matchOscPattern ("a{bcd,bce}e", "abcde"));
|
||
|
expect (! matchOscPattern ("a{bce,bcf}e", "abcde"));
|
||
|
expect (! matchOscPattern ("a{bce,bcf}e", "ae"));
|
||
|
expect (matchOscPattern ("a{bce,,bcf}e", "ae"));
|
||
|
expect (matchOscPattern ("a{bcd,bcd,bcd}e", "abcde"));
|
||
|
expect (matchOscPattern ("aaa{bc,def,ghij,klmnopqrstu}eee", "aaaghijeee"));
|
||
|
expect (matchOscPattern ("{a,b,c}bcde", "abcde"));
|
||
|
expect (! matchOscPattern ("{abc}bcde", "abcde"));
|
||
|
expect (matchOscPattern ("bcde{a,b,c}", "bcdea"));
|
||
|
expect (! matchOscPattern ("bcde{abc}", "bcdea"));
|
||
|
expect (matchOscPattern ("f{o,}o", "fo"));
|
||
|
expect (matchOscPattern ("f{,,,,,}o", "fo"));
|
||
|
expect (matchOscPattern ("foo{b,ba,bar}x", "foobarx"));
|
||
|
expect (matchOscPattern ("a{bc,de}fg{hij,klm}{n}{}", "adefghijn"));
|
||
|
|
||
|
// should fail gracefully in case of wrong syntax:
|
||
|
expect (! matchOscPattern ("not{closing", "notclosing"));
|
||
|
expect (! matchOscPattern ("not}opening", "notopening"));
|
||
|
expect (! matchOscPattern ("{{nested}}", "nested"));
|
||
|
expect (! matchOscPattern ("{a-c}bcde", "abcde"));
|
||
|
expect (! matchOscPattern ("bcde{a-c}", "abcde"));
|
||
|
}
|
||
|
|
||
|
|
||
|
beginTest ("string matching with '[...]' pattern");
|
||
|
{
|
||
|
// using [] for a set of chars:
|
||
|
|
||
|
expect (matchOscPattern ("[]", ""));
|
||
|
expect (! matchOscPattern ("[]", "x"));
|
||
|
expect (! matchOscPattern ("[abcde]", "abcde"));
|
||
|
expect (matchOscPattern ("[abcde]", "a"));
|
||
|
expect (matchOscPattern ("[abcde]", "b"));
|
||
|
expect (matchOscPattern ("[abcde]", "c"));
|
||
|
expect (matchOscPattern ("[abcde]", "d"));
|
||
|
expect (matchOscPattern ("[abcde]", "e"));
|
||
|
expect (! matchOscPattern ("[abcde]", "f"));
|
||
|
|
||
|
expect (matchOscPattern ("f[oo]", "fo"));
|
||
|
expect (! matchOscPattern ("f[oo]", "foo"));
|
||
|
|
||
|
expect (matchOscPattern ("fooba[rxz]foo", "foobarfoo"));
|
||
|
expect (matchOscPattern ("fooba[rxz]foo", "foobaxfoo"));
|
||
|
expect (matchOscPattern ("fooba[rxz]foo", "foobazfoo"));
|
||
|
expect (! matchOscPattern ("fooba[rxz]foo", "foobasfoo"));
|
||
|
|
||
|
expect (matchOscPattern ("foo[abc]foo[defgh]foo[i]foo[]foo", "foobfoohfooifoofoo"));
|
||
|
|
||
|
// using [] for a range of chars:
|
||
|
|
||
|
expect (matchOscPattern ("fooba[r-z]foo", "foobarfoo"));
|
||
|
expect (matchOscPattern ("fooba[r-z]foo", "foobaxfoo"));
|
||
|
expect (matchOscPattern ("fooba[r-z]foo", "foobazfoo"));
|
||
|
expect (matchOscPattern ("fooba[r-z]foo", "foobasfoo"));
|
||
|
expect (! matchOscPattern ("fooba[r-z]foo", "foobaRfoo"));
|
||
|
|
||
|
expect (! matchOscPattern ("foo[1-8]bar", "foo0bar"));
|
||
|
expect (matchOscPattern ("foo[1-8]bar", "foo1bar"));
|
||
|
expect (matchOscPattern ("foo[1-8]bar", "foo6bar"));
|
||
|
expect (matchOscPattern ("foo[1-8]bar", "foo8bar"));
|
||
|
expect (! matchOscPattern ("foo[1-8]bar", "foo9bar"));
|
||
|
|
||
|
// special case: '-' does not have a special meaning if it is at the end of the set.
|
||
|
|
||
|
expect (matchOscPattern ("foo[abc-]bar", "fooabar"));
|
||
|
expect (matchOscPattern ("foo[abc-]bar", "foo-bar"));
|
||
|
expect (matchOscPattern ("foo[-]bar", "foo-bar"));
|
||
|
|
||
|
// mixing both set and range:
|
||
|
|
||
|
expect (matchOscPattern ("foo[ae-iko-uz1-8D-FX]b[a-b]r", "fooabar"));
|
||
|
expect (! matchOscPattern ("foo[ae-iko-uz1-8D-FX]b[a-a]r", "foobbar"));
|
||
|
expect (! matchOscPattern ("foo[ae-iko-uz1-8D-FX]b[aaaa-aaaa-aaaa]r", "foodbar"));
|
||
|
expect (matchOscPattern ("foo[ae-iko-uz1-8D-FX]bar", "fooebar"));
|
||
|
expect (matchOscPattern ("foo[ae-iko-uz1-8D-FX]bar", "foogbar"));
|
||
|
expect (matchOscPattern ("foo[ae-iko-uz1-8D-FX]bar", "fooibar"));
|
||
|
expect (! matchOscPattern ("foo[ae-iko-uz1-8D-FX]bar", "foojbar"));
|
||
|
expect (matchOscPattern ("foo[ae-iko-uz1-8D-FX]bar", "fookbar"));
|
||
|
expect (! matchOscPattern ("foo[ae-iko-uz1-8D-FX]bar", "foolbar"));
|
||
|
expect (matchOscPattern ("foo[ae-iko-uz1-8D-FX]bar", "fooobar"));
|
||
|
expect (matchOscPattern ("foo[ae-iko-uz1-8D-FX]bar", "foopbar"));
|
||
|
expect (matchOscPattern ("foo[ae-iko-uz1-8D-FX]bar", "fooubar"));
|
||
|
expect (! matchOscPattern ("foo[ae-iko-uz1-8D-FX]bar", "fooybar"));
|
||
|
expect (matchOscPattern ("foo[ae-iko-uz1-8D-FX]bar", "foozbar"));
|
||
|
expect (! matchOscPattern ("foo[ae-iko-uz1-8D-FX]bar", "foo0bar"));
|
||
|
expect (matchOscPattern ("foo[ae-iko-uz1-8D-FX]bar", "foo1bar"));
|
||
|
expect (matchOscPattern ("foo[ae-iko-uz1-8D-FX]bar", "foo5bar"));
|
||
|
expect (matchOscPattern ("foo[ae-iko-uz1-8D-FX]bar", "foo8bar"));
|
||
|
expect (! matchOscPattern ("foo[ae-iko-uz1-8D-FX]bar", "foo9bar"));
|
||
|
expect (! matchOscPattern ("foo[ae-iko-uz1-8D-FX]bar", "fooCbar"));
|
||
|
expect (matchOscPattern ("foo[ae-iko-uz1-8D-FX]bar", "fooDbar"));
|
||
|
expect (matchOscPattern ("foo[ae-iko-uz1-8D-FX]bar", "fooEbar"));
|
||
|
expect (matchOscPattern ("foo[ae-iko-uz1-8D-FX]bar", "fooFbar"));
|
||
|
expect (! matchOscPattern ("foo[ae-iko-uz1-8D-FX]bar", "fooGbar"));
|
||
|
expect (matchOscPattern ("foo[ae-iko-uz1-8D-FX]bar", "fooXbar"));
|
||
|
expect (! matchOscPattern ("foo[ae-iko-uz1-8D-FX]ba[Rr]", "fooZbar"));
|
||
|
expect (! matchOscPattern ("foo[ae-iko-uz1-8D-FX]ba[Rr]", "foobar"));
|
||
|
expect (! matchOscPattern ("foo[ae-iko-uz1-8D-FX]ba[Rr]", "fooFXbar"));
|
||
|
|
||
|
// using [!...] for a negated set or range of chars:
|
||
|
|
||
|
expect (! matchOscPattern ("fooba[!rxz]foo", "foobarfoo"));
|
||
|
expect (! matchOscPattern ("fooba[!rxz]foo", "foobaxfoo"));
|
||
|
expect (! matchOscPattern ("fooba[!rxz]foo", "foobazfoo"));
|
||
|
expect (matchOscPattern ("fooba[!rxz]foo", "foobasfoo"));
|
||
|
|
||
|
expect (! matchOscPattern ("fooba[!r-z]foo", "foobarfoo"));
|
||
|
expect (! matchOscPattern ("fooba[!r-z]foo", "foobaxfoo"));
|
||
|
expect (! matchOscPattern ("fooba[!r-z]foo", "foobazfoo"));
|
||
|
expect (! matchOscPattern ("fooba[!r-z]foo", "foobasfoo"));
|
||
|
expect (matchOscPattern ("fooba[!r-z]foo", "foobaRfoo"));
|
||
|
|
||
|
// special case: '!' does not have a special meaning if it is not the first char in the set.
|
||
|
|
||
|
expect (matchOscPattern ("foo[ab!c]bar", "fooabar"));
|
||
|
expect (matchOscPattern ("foo[ab!c]bar", "foo!bar"));
|
||
|
expect (! matchOscPattern ("foo[ab!c]bar", "fooxbar"));
|
||
|
expect (! matchOscPattern ("foo[!!]bar", "foo!bar"));
|
||
|
expect (matchOscPattern ("foo[!!]bar", "fooxbar"));
|
||
|
expect (! matchOscPattern ("foo[!!]bar", "foobar"));
|
||
|
|
||
|
// should fail gracefully in case of wrong syntax:
|
||
|
|
||
|
expect (! matchOscPattern ("notclosin[g", "notclosing"));
|
||
|
expect (! matchOscPattern ("n]otopening", "notopening"));
|
||
|
expect (! matchOscPattern ("[[nested]]", "nested"));
|
||
|
expect (! matchOscPattern ("norangestar[-t]", "norangestart"));
|
||
|
expect (! matchOscPattern ("norangestar[-t]", "norangestar-"));
|
||
|
}
|
||
|
|
||
|
beginTest ("string matching combining patterns");
|
||
|
{
|
||
|
expect (matchOscPattern ("*ea*ll[y-z0-9X-Zvwx]??m[o-q]l[e]x{fat,mat,pat}te{}r*?", "reallycomplexpattern"));
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
static OSCPatternMatcherTests OSCPatternMatcherUnitTests;
|
||
|
|
||
|
#endif
|
||
|
|
||
|
} // namespace juce
|