reapack

Package manager for REAPER
Log | Files | Refs | Submodules | README | LICENSE

commit 9a04357da6ce3e27babe3e186b937f460e2942bc
parent 77ca71712b3c6f5b58e3ef3da64c9e653ea7178a
Author: cfillion <cfillion@users.noreply.github.com>
Date:   Fri,  2 Sep 2016 02:58:40 -0400

filter: implement AND grouping ( with parenthesis )

Diffstat:
Msrc/filter.cpp | 157++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
Msrc/filter.hpp | 60+++++++++++++++++++++++++++++++++++++++++++++++++-----------
Mtest/filter.cpp | 30+++++++++++++++++++++++++++---
3 files changed, 183 insertions(+), 64 deletions(-)

diff --git a/src/filter.cpp b/src/filter.cpp @@ -22,58 +22,72 @@ using namespace std; +Filter::Filter(const string &input) + : m_root(Group::MatchAll) +{ + set(input); +} + void Filter::set(const string &input) { enum State { Default, DoubleQuote, SingleQuote }; m_input = input; - m_tokens.clear(); + m_root.clear(); - Token token{}; - vector<Token> group; + Group *group = &m_root; + auto token = make_shared<Token>(); State state = Default; - bool append = false; - auto push = [&] { - size_t size = token.buf.size(); + size_t size = token->buf.size(); if(!size) return; - if(!token.test(QuotedFlag)) { - if(token.buf == "OR") { - token = Token(); - append = true; + if(!token->test(Token::QuotedFlag)) { + if(token->buf == "NOT") { + token->buf.clear(); + token->flags ^= Token::NotFlag; + return; + } + else if(token->buf == "OR") { + token->buf.clear(); + if(!group->empty()) + group = group->subgroup_any(); + return; + } + else if(token->buf == "(") { + token->buf.clear(); + group = group->subgroup_all(); return; } - else if(token.buf == "NOT") { - token.buf.clear(); - token.flags ^= NotFlag; + else if(token->buf == ")") { + token->buf.clear(); + if(group != &m_root) + group = group->parent(); return; } } - if(size > 1 && token.buf[0] == '^') { - token.flags |= StartAnchorFlag; - token.buf.erase(0, 1); + if(size > 1 && token->buf[0] == '^') { + token->flags |= Token::StartAnchorFlag; + token->buf.erase(0, 1); size--; } - if(size > 1 && token.buf[size - 1] == '$') { - token.flags |= EndAnchorFlag; - token.buf.erase(size - 1, 1); + if(size > 1 && token->buf[size - 1] == '$') { + token->flags |= Token::EndAnchorFlag; + token->buf.erase(size - 1, 1); size--; } - if(append) - append = false; - else if(!group.empty()) { - m_tokens.emplace_back(group); - group = vector<Token>(); - } + group->push(token); + + // close previous OR group + if(!group->open()) + group = group->parent(); - group.emplace_back(token); - token = Token(); + token = make_shared<Token>(); }; for(const char c : input) { @@ -83,7 +97,7 @@ void Filter::set(const string &input) else state = DoubleQuote; - token.flags |= QuotedFlag; + token->flags |= Token::QuotedFlag; } else if(c == '\'' && state != DoubleQuote) { if(state == SingleQuote) @@ -91,47 +105,90 @@ void Filter::set(const string &input) else state = SingleQuote; - token.flags |= QuotedFlag; + token->flags |= Token::QuotedFlag; } else if(c == '\x20' && state == Default) push(); else - token.buf += c; + token->buf += c; } push(); - - if(!group.empty()) - m_tokens.emplace_back(group); } bool Filter::match(const vector<string> &rows) const { - for(const vector<Token> &group : m_tokens) { - bool match = false; - - for(const Token &token : group) { - for(const string &str : rows) { - if(token.match(str)) - match = true; - else if(token.test(NotFlag)) { - match = false; - break; - } - } + return m_root.match(rows); +} + +Filter::Group *Filter::Group::subgroup_any() +{ + if(m_type == Group::MatchAny) { + m_open = true; + return this; + } + + NodePtr prev; + if(!m_nodes.empty()) { + prev = m_nodes.back(); + m_nodes.pop_back(); + } + + auto newGroup = make_shared<Group>(Group::MatchAny, this); + m_nodes.push_back(newGroup); - if(match) - break; + if(prev) + newGroup->m_nodes.push_back(prev); + + return newGroup.get(); +} + +Filter::Group *Filter::Group::subgroup_all() +{ + // always make a subgroup of this type + + auto newGroup = make_shared<Group>(Group::MatchAll, this); + m_nodes.push_back(newGroup); + return newGroup.get(); +} + +void Filter::Group::push(const NodePtr &node) +{ + m_nodes.push_back(node); + + if(m_type == MatchAny) + m_open = false; +} + +bool Filter::Group::match(const vector<string> &rows) const +{ + for(const NodePtr &node : m_nodes) { + if(node->match(rows)) { + if(m_type == MatchAny) + return true; } + else if(m_type == MatchAll) + return false; + } + + return m_type == MatchAll; +} + +bool Filter::Token::match(const vector<string> &rows) const +{ + bool match = false; - if(!match) + for(const string &row : rows) { + if(matchRow(row)) + match = true; + else if(test(NotFlag)) return false; } - return true; + return match; } -bool Filter::Token::match(const string &str) const +bool Filter::Token::matchRow(const string &str) const { bool match = true; diff --git a/src/filter.hpp b/src/filter.hpp @@ -23,8 +23,7 @@ class Filter { public: - Filter() {} - Filter(const std::string &input) { set(input); } + Filter(const std::string & = {}); const std::string get() const { return m_input; } void set(const std::string &); @@ -36,23 +35,62 @@ public: bool operator!=(const std::string &f) const { return !(*this == f); } private: - enum Flag { - StartAnchorFlag = 1<<0, - EndAnchorFlag = 1<<1, - QuotedFlag = 1<<2, - NotFlag = 1<<3, + class Node { + public: + virtual bool match(const std::vector<std::string> &) const = 0; }; - struct Token { - std::string buf; + typedef std::shared_ptr<Node> NodePtr; + + class Group : public Node { + public: + enum Type { + MatchAll, + MatchAny, + }; + + Group(Type type, Group *parent = nullptr) + : m_parent(parent), m_type(type), m_open(true) {} + + Group *parent() const { return m_parent; } + Group *subgroup_any(); + Group *subgroup_all(); + + bool empty() const { return m_nodes.empty(); } + void clear() { m_nodes.clear(); } + void push(const NodePtr &); + + bool open() const { return m_open; } + bool match(const std::vector<std::string> &) const override; + + private: + Group *m_parent; + Type m_type; + bool m_open; + std::vector<NodePtr> m_nodes; + }; + + class Token : public Node { + public: + enum Flag { + StartAnchorFlag = 1<<0, + EndAnchorFlag = 1<<1, + QuotedFlag = 1<<2, + NotFlag = 1<<3, + }; + + Token() : flags(0) {} + int flags; + std::string buf; - bool match(const std::string &) const; + bool match(const std::vector<std::string> &) const override; + bool matchRow(const std::string &) const; bool test(Flag f) const { return (flags & f) != 0; } }; std::string m_input; - std::vector<std::vector<Token> > m_tokens; + Group m_root; }; #endif diff --git a/test/filter.cpp b/test/filter.cpp @@ -8,6 +8,7 @@ static const char *M = "[filter]"; TEST_CASE("basic matching", M) { Filter f; + REQUIRE(f.match({})); REQUIRE(f.match({"world"})); f.set("hello"); @@ -201,6 +202,19 @@ TEST_CASE("OR operator", M) { REQUIRE_FALSE(f.match({"hello world"})); REQUIRE(f.match({"hello OR bacon"})); } + + SECTION("reset") { + f.set("hello OR bacon world"); + + REQUIRE_FALSE(f.match({"world"})); + REQUIRE(f.match({"hello world"})); + REQUIRE(f.match({"bacon world"})); + } + + SECTION("single") { + f.set("OR"); + REQUIRE(f.match({"anything"})); + } } TEST_CASE("NOT operator", M) { @@ -240,8 +254,18 @@ TEST_CASE("NOT operator", M) { } } -TEST_CASE("empty filter", M) { - const Filter f(""); +TEST_CASE("AND grouping", M) { + Filter f; - REQUIRE(f.match({"hello world"})); + SECTION("normal") { + f.set("( hello world ) OR ( NOT hello bacon )"); + + REQUIRE(f.match({"hello world"})); + REQUIRE(f.match({"chunky bacon"})); + REQUIRE_FALSE(f.match({"hello chunky bacon"})); + } + + SECTION("close without opening") { + f.set(") test"); + } }