From 2746cd503accc4d995a9c11f294dbbc560997292 Mon Sep 17 00:00:00 2001 From: Julian Andres Klode Date: Tue, 28 Jan 2020 22:38:24 +0100 Subject: patterns: Make offset a size_t instead of off_t This allows comparing against sentence.size() --- apt-pkg/cachefilter-patterns.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'apt-pkg/cachefilter-patterns.h') diff --git a/apt-pkg/cachefilter-patterns.h b/apt-pkg/cachefilter-patterns.h index bd8ce7e7e..33595dde2 100644 --- a/apt-pkg/cachefilter-patterns.h +++ b/apt-pkg/cachefilter-patterns.h @@ -71,7 +71,7 @@ struct PatternTreeParser struct State { - off_t offset = 0; + size_t offset = 0; }; APT::StringView sentence; -- cgit v1.2.3-70-g09d2 From bcb9ecb2cd2208d12b83c4e111be7c126b49dc7e Mon Sep 17 00:00:00 2001 From: Julian Andres Klode Date: Sat, 1 Feb 2020 17:04:58 +0100 Subject: patterns: Provide Node constructor, simplify error throwing By having a node constructor, we can construct a node inline for error reporting needs, simplifying the code a bit. --- apt-pkg/cachefilter-patterns.cc | 13 +++---------- apt-pkg/cachefilter-patterns.h | 2 ++ 2 files changed, 5 insertions(+), 10 deletions(-) (limited to 'apt-pkg/cachefilter-patterns.h') diff --git a/apt-pkg/cachefilter-patterns.cc b/apt-pkg/cachefilter-patterns.cc index a9f76ff1d..b21f70d6a 100644 --- a/apt-pkg/cachefilter-patterns.cc +++ b/apt-pkg/cachefilter-patterns.cc @@ -33,13 +33,7 @@ std::unique_ptr PatternTreeParser::parseTop() skipSpace(); if (node->end != sentence.size()) - { - Node node2; - - node2.start = node->end; - node2.end = sentence.size(); - throw Error{node2, "Expected end of file"}; - } + throw Error{Node{node->end, sentence.size()}, "Expected end of file"}; return node; } @@ -55,9 +49,8 @@ std::unique_ptr PatternTreeParser::parse() if ((node = parseWord()) != nullptr) return node; - Node eNode; - eNode.end = eNode.start = state.offset; - throw Error{eNode, "Expected pattern, quoted word, or word"}; + throw Error{Node{state.offset, sentence.size()}, + "Expected pattern, quoted word, or word"}; } // Parse a list pattern (or function call pattern) diff --git a/apt-pkg/cachefilter-patterns.h b/apt-pkg/cachefilter-patterns.h index 33595dde2..8317665bc 100644 --- a/apt-pkg/cachefilter-patterns.h +++ b/apt-pkg/cachefilter-patterns.h @@ -39,6 +39,8 @@ struct PatternTreeParser size_t start = 0; size_t end = 0; + explicit Node(size_t start = 0, size_t end = 0) : start(start), end(end) {} + virtual std::ostream &render(std::ostream &os) { return os; }; std::nullptr_t error(std::string message); }; -- cgit v1.2.3-70-g09d2 From fd43b1694f1382a3a47f5dc546ebe3d39fcd6e7d Mon Sep 17 00:00:00 2001 From: Julian Andres Klode Date: Mon, 20 Jan 2020 14:14:49 +0100 Subject: Implement short patterns (patterns starting with ~) Also make pattern detector in cacheset and private's list accept such patterns. We probably should just try to parse and see if it is a (start of a) pattern. --- apt-pkg/cachefilter-patterns.cc | 57 +++++++++++++++++++++++++++++++++++ apt-pkg/cachefilter-patterns.h | 1 + apt-pkg/cacheset.cc | 2 +- apt-private/private-list.cc | 4 +-- doc/apt-patterns.7.xml | 36 +++++++++++----------- test/libapt/pattern_test.cc | 66 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 145 insertions(+), 21 deletions(-) (limited to 'apt-pkg/cachefilter-patterns.h') diff --git a/apt-pkg/cachefilter-patterns.cc b/apt-pkg/cachefilter-patterns.cc index 7abc4536d..11ad5d723 100644 --- a/apt-pkg/cachefilter-patterns.cc +++ b/apt-pkg/cachefilter-patterns.cc @@ -17,6 +17,32 @@ namespace APT namespace Internal { +static const constexpr struct +{ + APT::StringView shortName; + APT::StringView longName; + bool takesArgument; +} shortPatterns[] = { + {"r"_sv, "?architecture"_sv, true}, + {"A"_sv, "?archive"_sv, true}, + {"M"_sv, "?automatic"_sv, false}, + {"b"_sv, "?broken"_sv, false}, + {"c"_sv, "?config-files"_sv, false}, + {"E"_sv, "?essential"_sv, false}, + {"F"_sv, "?false"_sv, false}, + {"g"_sv, "?garbage"_sv, false}, + {"i"_sv, "?installed"_sv, false}, + {"n"_sv, "?name"_sv, true}, + {"o"_sv, "?obsolete"_sv, false}, + {"O"_sv, "?origin"_sv, true}, + {"s"_sv, "?section"_sv, true}, + {"e"_sv, "?source-package"_sv, true}, + {"T"_sv, "?true"_sv, false}, + {"U"_sv, "?upgradable"_sv, false}, + {"V"_sv, "?version"_sv, true}, + {"v"_sv, "?virtual"_sv, false}, +}; + template std::string rstrprintf(Args... args) { @@ -42,6 +68,8 @@ std::unique_ptr PatternTreeParser::parseTop() std::unique_ptr PatternTreeParser::parse() { std::unique_ptr node; + if ((node = parseShortPattern()) != nullptr) + return node; if ((node = parsePattern()) != nullptr) return node; if ((node = parseQuotedWord()) != nullptr) @@ -53,6 +81,35 @@ std::unique_ptr PatternTreeParser::parse() "Expected pattern, quoted word, or word"}; } +// Parse a short pattern +std::unique_ptr PatternTreeParser::parseShortPattern() +{ + if (sentence[state.offset] != '~') + return nullptr; + + for (auto &sp : shortPatterns) + { + if (sentence.substr(state.offset + 1, sp.shortName.size()) != sp.shortName) + continue; + + auto node = std::make_unique(); + node->end = node->start = state.offset; + node->term = sp.longName; + + state.offset += sp.shortName.size() + 1; + if (sp.takesArgument) + { + node->arguments.push_back(parse()); + node->haveArgumentList = true; + } + node->end = state.offset; + + return node; + } + + throw Error{Node{state.offset, sentence.size()}, "Unknown short pattern"}; +} + // Parse a list pattern (or function call pattern) std::unique_ptr PatternTreeParser::parsePattern() { diff --git a/apt-pkg/cachefilter-patterns.h b/apt-pkg/cachefilter-patterns.h index 8317665bc..0d6e9d99e 100644 --- a/apt-pkg/cachefilter-patterns.h +++ b/apt-pkg/cachefilter-patterns.h @@ -96,6 +96,7 @@ struct PatternTreeParser private: std::unique_ptr parse(); std::unique_ptr parsePattern(); + std::unique_ptr parseShortPattern(); std::unique_ptr parseWord(); std::unique_ptr parseQuotedWord(); }; diff --git a/apt-pkg/cacheset.cc b/apt-pkg/cacheset.cc index f5251eda8..ae1d5ee3e 100644 --- a/apt-pkg/cacheset.cc +++ b/apt-pkg/cacheset.cc @@ -295,7 +295,7 @@ bool CacheSetHelper::PackageFromPackageName(PackageContainerInterface * const pc bool CacheSetHelper::PackageFromPattern(PackageContainerInterface *const pci, pkgCacheFile &Cache, std::string const &pattern) { - if (pattern.size() < 1 || pattern[0] != '?') + if (pattern.size() < 1 || (pattern[0] != '?' && pattern[0] != '~')) return false; auto compiledPattern = APT::CacheFilter::ParsePattern(pattern, &Cache); diff --git a/apt-private/private-list.cc b/apt-private/private-list.cc index 6071129a7..f5c31bbcd 100644 --- a/apt-private/private-list.cc +++ b/apt-private/private-list.cc @@ -48,8 +48,8 @@ class PackageNameMatcher : public Matcher { std::string pattern = patterns[i]; APT::CacheFilter::Matcher *cachefilter = NULL; - if (pattern.size() > 0 && pattern[0] == '?') - cachefilter = APT::CacheFilter::ParsePattern(pattern, &cacheFile).release(); + if (pattern.size() > 0 && (pattern[0] == '?' || pattern[0] == '~')) + cachefilter = APT::CacheFilter::ParsePattern(pattern, &cacheFile).release(); else if(_config->FindB("APT::Cmd::Use-Regexp", false) == true) cachefilter = new APT::CacheFilter::PackageNameMatchesRegEx(pattern); else diff --git a/doc/apt-patterns.7.xml b/doc/apt-patterns.7.xml index b94a9b226..f18fe6a19 100644 --- a/doc/apt-patterns.7.xml +++ b/doc/apt-patterns.7.xml @@ -46,7 +46,7 @@ ?and(PATTERN, PATTERN, ...) Selects objects where all specified patterns match. - ?false + ?false~F Selects nothing. ?not(PATTERN) @@ -55,7 +55,7 @@ ?or(PATTERN, PATTERN, ...) Selects objects where at least one of the specified patterns match. - ?true + ?true~T Selects all objects. @@ -83,40 +83,40 @@ These patterns select specific packages. - ?architecture(WILDCARD) + ?architecture(WILDCARD)~rWILDCARD Selects packages matching the specified architecture, which may contain wildcards using any. - ?automatic + ?automatic~M Selects packages that were installed automatically. - ?broken + ?broken~b Selects packages that have broken dependencies. - ?config-files + ?config-files~c Selects packages that are not fully installed, but have solely residual configuration files left. - ?essential + ?essential~E Selects packages that have Essential: yes set in their control file. ?exact-name(NAME) Selects packages with the exact specified name. - ?garbage + ?garbage~g Selects packages that can be removed automatically. - ?installed + ?installed~i Selects packages that are currently installed. - ?name(REGEX) + ?name(REGEX)~nREGEX Selects packages where the name matches the given regular expression. - ?obsolete + ?obsolete~o Selects packages that no longer exist in repositories. - ?upgradable + ?upgradable~U Selects packages that can be upgraded (have a newer candidate). - ?virtual + ?virtual~v Selects all virtual packages; that is packages without a version. These exist when they are referenced somewhere in the archive, for example because something depends on that name. @@ -129,22 +129,22 @@ These patterns select specific versions of a package. - ?archive(REGEX) + ?archive(REGEX)~AREGEX Selects versions that come from the archive that matches the specified regular expression. Archive, here, means the values after a= in apt-cache policy. - ?origin(REGEX) + ?origin(REGEX)~OREGEX Selects versions that come from the origin that matches the specified regular expression. Origin, here, means the values after o= in apt-cache policy. - ?section(REGEX) + ?section(REGEX)~sREGEX Selects versions where the section matches the specified regular expression. - ?source-package(REGEX) + ?source-package(REGEX)~eREGEX Selects versions where the source package name matches the specified regular expression. ?source-version(REGEX) Selects versions where the source package version matches the specified regular expression. - ?version(REGEX) + ?version(REGEX)~VREGEX Selects versions where the version string matching the specified regular expression. diff --git a/test/libapt/pattern_test.cc b/test/libapt/pattern_test.cc index de2fbceb9..492a29eac 100644 --- a/test/libapt/pattern_test.cc +++ b/test/libapt/pattern_test.cc @@ -93,3 +93,69 @@ TEST(TreeParserTest, ParseWithManyArgsWithSpacesWithTrailingComma) EXPECT_EQ(patternNode->term, "?hello"); EXPECT_EQ(2u, patternNode->arguments.size()); } + +// Helper +static bool samePattern(const std::unique_ptr &a, const std::unique_ptr &b) +{ + auto pa = dynamic_cast(a.get()); + auto pb = dynamic_cast(b.get()); + + if (pa && pb) + { + if (pa->term != pb->term || pa->haveArgumentList != pb->haveArgumentList || pa->arguments.size() != pb->arguments.size()) + return false; + + for (size_t i = 0; i < pa->arguments.size(); i++) + { + if (!samePattern(pa->arguments[i], pb->arguments[i])) + return false; + } + return true; + } + + auto wa = dynamic_cast(a.get()); + auto wb = dynamic_cast(b.get()); + if (wa && wb) + return wa->word == wb->word && wa->quoted == wb->quoted; + + return false; +} + +#define EXPECT_PATTERN_EQ(shrt, lng) \ + EXPECT_TRUE(samePattern(PatternTreeParser(shrt).parseTop(), PatternTreeParser(lng).parseTop())) +#define EXPECT_PATTERN_EQ_ATOMIC(shrt, lng) \ + EXPECT_TRUE(PatternTreeParser(shrt).parseTop()); \ + caught = false; \ + try \ + { \ + PatternTreeParser(shrt "XXX").parseTop(); \ + } \ + catch (PatternTreeParser::Error & e) \ + { \ + caught = true; \ + }; \ + EXPECT_TRUE(caught) << shrt "XXX should have thrown an exception"; \ + EXPECT_PATTERN_EQ(shrt, lng) + +TEST(TreeParserTest, ParseShortPattern) +{ + bool caught; + EXPECT_PATTERN_EQ("~ramd64", "?architecture(amd64)"); + EXPECT_PATTERN_EQ("~AanArchive", "?archive(anArchive)"); + EXPECT_PATTERN_EQ_ATOMIC("~M", "?automatic"); + EXPECT_PATTERN_EQ_ATOMIC("~b", "?broken"); + EXPECT_PATTERN_EQ_ATOMIC("~c", "?config-files"); + EXPECT_PATTERN_EQ_ATOMIC("~E", "?essential"); + EXPECT_PATTERN_EQ_ATOMIC("~F", "?false"); + EXPECT_PATTERN_EQ_ATOMIC("~g", "?garbage"); + EXPECT_PATTERN_EQ_ATOMIC("~i", "?installed"); + EXPECT_PATTERN_EQ("~napt", "?name(apt)"); + EXPECT_PATTERN_EQ_ATOMIC("~o", "?obsolete"); + EXPECT_PATTERN_EQ("~Obar", "?origin(bar)"); + EXPECT_PATTERN_EQ("~sfoo", "?section(foo)"); + EXPECT_PATTERN_EQ("~esourcename", "?source-package(sourcename)"); + EXPECT_PATTERN_EQ_ATOMIC("~T", "?true"); + EXPECT_PATTERN_EQ_ATOMIC("~U", "?upgradable"); + EXPECT_PATTERN_EQ("~Vverstr", "?version(verstr)"); + EXPECT_PATTERN_EQ_ATOMIC("~v", "?virtual"); +} -- cgit v1.2.3-70-g09d2 From 8d4967d3a187dd66cf14b070a9db63f8ea21b21f Mon Sep 17 00:00:00 2001 From: Julian Andres Klode Date: Tue, 28 Jan 2020 21:46:10 +0100 Subject: patterns: Implement unary ! --- apt-pkg/cachefilter-patterns.cc | 28 +++++++++++++++++++++++++++- apt-pkg/cachefilter-patterns.h | 2 ++ doc/apt-patterns.7.xml | 2 +- test/libapt/pattern_test.cc | 1 + 4 files changed, 31 insertions(+), 2 deletions(-) (limited to 'apt-pkg/cachefilter-patterns.h') diff --git a/apt-pkg/cachefilter-patterns.cc b/apt-pkg/cachefilter-patterns.cc index 11ad5d723..9fab0281d 100644 --- a/apt-pkg/cachefilter-patterns.cc +++ b/apt-pkg/cachefilter-patterns.cc @@ -66,6 +66,32 @@ std::unique_ptr PatternTreeParser::parseTop() // Parse any pattern std::unique_ptr PatternTreeParser::parse() +{ + return parseUnary(); +} + +std::unique_ptr PatternTreeParser::parseUnary() +{ + + if (sentence[state.offset] != '!') + return parsePrimary(); + + auto start = ++state.offset; + auto primary = parsePrimary(); + + if (primary == nullptr) + throw Error{Node{start, sentence.size()}, "Expected pattern"}; + + auto node = std::make_unique(); + node->start = start; + node->end = primary->end; + node->term = "?not"; + node->arguments.push_back(std::move(primary)); + node->haveArgumentList = true; + return node; +} + +std::unique_ptr PatternTreeParser::parsePrimary() { std::unique_ptr node; if ((node = parseShortPattern()) != nullptr) @@ -198,7 +224,7 @@ std::unique_ptr PatternTreeParser::parseQuotedWord() // Parse a bare word atom std::unique_ptr PatternTreeParser::parseWord() { - static const constexpr auto DISALLOWED_START = "?~,()\0"_sv; + static const constexpr auto DISALLOWED_START = "!?~,()\0"_sv; static const constexpr auto DISALLOWED = ",()\0"_sv; if (DISALLOWED_START.find(sentence[state.offset]) != APT::StringView::npos) return nullptr; diff --git a/apt-pkg/cachefilter-patterns.h b/apt-pkg/cachefilter-patterns.h index 0d6e9d99e..76318eafa 100644 --- a/apt-pkg/cachefilter-patterns.h +++ b/apt-pkg/cachefilter-patterns.h @@ -95,6 +95,8 @@ struct PatternTreeParser private: std::unique_ptr parse(); + std::unique_ptr parseUnary(); + std::unique_ptr parsePrimary(); std::unique_ptr parsePattern(); std::unique_ptr parseShortPattern(); std::unique_ptr parseWord(); diff --git a/doc/apt-patterns.7.xml b/doc/apt-patterns.7.xml index f18fe6a19..72f1ccbce 100644 --- a/doc/apt-patterns.7.xml +++ b/doc/apt-patterns.7.xml @@ -49,7 +49,7 @@ ?false~F Selects nothing. - ?not(PATTERN) + ?not(PATTERN)!PATTERN Selects objects where PATTERN does not match. ?or(PATTERN, PATTERN, ...) diff --git a/test/libapt/pattern_test.cc b/test/libapt/pattern_test.cc index 492a29eac..39959cd31 100644 --- a/test/libapt/pattern_test.cc +++ b/test/libapt/pattern_test.cc @@ -158,4 +158,5 @@ TEST(TreeParserTest, ParseShortPattern) EXPECT_PATTERN_EQ_ATOMIC("~U", "?upgradable"); EXPECT_PATTERN_EQ("~Vverstr", "?version(verstr)"); EXPECT_PATTERN_EQ_ATOMIC("~v", "?virtual"); + EXPECT_PATTERN_EQ("!foo", "?not(foo)"); } -- cgit v1.2.3-70-g09d2 From ebe5f39bfbb64921d5d31e0a6e49287356a5e6e2 Mon Sep 17 00:00:00 2001 From: Julian Andres Klode Date: Tue, 28 Jan 2020 23:06:08 +0100 Subject: patterns: Allow bare words only in arguments This changes the syntax from approximately expr = unary unary = '!'? primary primary = pattern | short-pattern | word | quoted-word pattern = '?' name [ '(' expr [',' expr]* ')' ] short-pattern = ~ name | ~name expr to: primary = pattern | short-pattern argument = word | quoted-word | expr pattern = '?' name [ '(' argument [',' argument]* ')' ] short-pattern = ~ name | ~name argument --- apt-pkg/cachefilter-patterns.cc | 18 +++++++++++++++--- apt-pkg/cachefilter-patterns.h | 1 + test/libapt/pattern_test.cc | 31 ++++++++++++++++++++++++------- 3 files changed, 40 insertions(+), 10 deletions(-) (limited to 'apt-pkg/cachefilter-patterns.h') diff --git a/apt-pkg/cachefilter-patterns.cc b/apt-pkg/cachefilter-patterns.cc index 9fab0281d..cf3e59ac6 100644 --- a/apt-pkg/cachefilter-patterns.cc +++ b/apt-pkg/cachefilter-patterns.cc @@ -58,6 +58,9 @@ std::unique_ptr PatternTreeParser::parseTop() auto node = parse(); skipSpace(); + if (node == nullptr) + throw Error{Node{0, sentence.size()}, "Expected pattern"}; + if (node->end != sentence.size()) throw Error{Node{node->end, sentence.size()}, "Expected end of file"}; @@ -98,10 +101,19 @@ std::unique_ptr PatternTreeParser::parsePrimary() return node; if ((node = parsePattern()) != nullptr) return node; + + return nullptr; +} + +std::unique_ptr PatternTreeParser::parseArgument() +{ + std::unique_ptr node; if ((node = parseQuotedWord()) != nullptr) return node; if ((node = parseWord()) != nullptr) return node; + if ((node = parse()) != nullptr) + return node; throw Error{Node{state.offset, sentence.size()}, "Expected pattern, quoted word, or word"}; @@ -125,7 +137,7 @@ std::unique_ptr PatternTreeParser::parseShortPattern() state.offset += sp.shortName.size() + 1; if (sp.takesArgument) { - node->arguments.push_back(parse()); + node->arguments.push_back(parseArgument()); node->haveArgumentList = true; } node->end = state.offset; @@ -173,7 +185,7 @@ std::unique_ptr PatternTreeParser::parsePattern() return node; } - node->arguments.push_back(parse()); + node->arguments.push_back(parseArgument()); skipSpace(); while (sentence[state.offset] == ',') { @@ -182,7 +194,7 @@ std::unique_ptr PatternTreeParser::parsePattern() // This was a trailing comma - allow it and break the loop if (sentence[state.offset] == ')') break; - node->arguments.push_back(parse()); + node->arguments.push_back(parseArgument()); skipSpace(); } diff --git a/apt-pkg/cachefilter-patterns.h b/apt-pkg/cachefilter-patterns.h index 76318eafa..1770c7307 100644 --- a/apt-pkg/cachefilter-patterns.h +++ b/apt-pkg/cachefilter-patterns.h @@ -99,6 +99,7 @@ struct PatternTreeParser std::unique_ptr parsePrimary(); std::unique_ptr parsePattern(); std::unique_ptr parseShortPattern(); + std::unique_ptr parseArgument(); std::unique_ptr parseWord(); std::unique_ptr parseQuotedWord(); }; diff --git a/test/libapt/pattern_test.cc b/test/libapt/pattern_test.cc index 39959cd31..7fc6a1f8f 100644 --- a/test/libapt/pattern_test.cc +++ b/test/libapt/pattern_test.cc @@ -16,19 +16,25 @@ using namespace APT::Internal; TEST(TreeParserTest, ParseWord) { - auto node = PatternTreeParser("word").parseTop(); - auto wordNode = dynamic_cast(node.get()); + auto node = PatternTreeParser("?word(word)").parseTop(); + auto patternNode = dynamic_cast(node.get()); + + ASSERT_EQ(patternNode->arguments.size(), 1u); + auto wordNode = dynamic_cast(patternNode->arguments[0].get()); - EXPECT_EQ(node.get(), wordNode); + EXPECT_EQ(patternNode->arguments[0].get(), wordNode); EXPECT_EQ(wordNode->word, "word"); } TEST(TreeParserTest, ParseQuotedWord) { - auto node = PatternTreeParser("\"a word\"").parseTop(); - auto wordNode = dynamic_cast(node.get()); + auto node = PatternTreeParser("?word(\"a word\")").parseTop(); + auto patternNode = dynamic_cast(node.get()); + + ASSERT_EQ(patternNode->arguments.size(), 1u); + auto wordNode = dynamic_cast(patternNode->arguments[0].get()); - EXPECT_EQ(node.get(), wordNode); + EXPECT_EQ(patternNode->arguments[0].get(), wordNode); EXPECT_EQ(wordNode->word, "a word"); } @@ -158,5 +164,16 @@ TEST(TreeParserTest, ParseShortPattern) EXPECT_PATTERN_EQ_ATOMIC("~U", "?upgradable"); EXPECT_PATTERN_EQ("~Vverstr", "?version(verstr)"); EXPECT_PATTERN_EQ_ATOMIC("~v", "?virtual"); - EXPECT_PATTERN_EQ("!foo", "?not(foo)"); + EXPECT_PATTERN_EQ("!?foo", "?not(?foo)"); + + caught = false; + try + { + PatternTreeParser("!x").parseTop(); + } + catch (PatternTreeParser::Error &e) + { + caught = true; + }; + EXPECT_TRUE(caught) << "!X should have thrown an exception"; } -- cgit v1.2.3-70-g09d2 From 250119362e44599aad7e75462fa4298ad1ab1ad9 Mon Sep 17 00:00:00 2001 From: Julian Andres Klode Date: Tue, 28 Jan 2020 22:38:46 +0100 Subject: patterns: Parse sequence of patterns as ?and --- apt-pkg/cachefilter-patterns.cc | 32 +++++++++++++++++++++++++++++++- apt-pkg/cachefilter-patterns.h | 1 + doc/apt-patterns.7.xml | 2 +- test/libapt/pattern_test.cc | 8 ++++++++ 4 files changed, 41 insertions(+), 2 deletions(-) (limited to 'apt-pkg/cachefilter-patterns.h') diff --git a/apt-pkg/cachefilter-patterns.cc b/apt-pkg/cachefilter-patterns.cc index cf3e59ac6..dbf58e2a9 100644 --- a/apt-pkg/cachefilter-patterns.cc +++ b/apt-pkg/cachefilter-patterns.cc @@ -70,7 +70,37 @@ std::unique_ptr PatternTreeParser::parseTop() // Parse any pattern std::unique_ptr PatternTreeParser::parse() { - return parseUnary(); + return parseAnd(); +} + +std::unique_ptr PatternTreeParser::parseAnd() +{ + auto start = state.offset; + std::vector> nodes; + + for (skipSpace(); state.offset < sentence.size(); skipSpace()) + { + auto node = parseUnary(); + + if (node == nullptr) + break; + + nodes.push_back(std::move(node)); + } + + if (nodes.size() == 0) + return nullptr; + if (nodes.size() == 1) + return std::move(nodes[0]); + + auto node = std::make_unique(); + node->start = start; + node->end = nodes[nodes.size() - 1]->end; + node->term = "?and"; + node->arguments = std::move(nodes); + node->haveArgumentList = true; + + return node; } std::unique_ptr PatternTreeParser::parseUnary() diff --git a/apt-pkg/cachefilter-patterns.h b/apt-pkg/cachefilter-patterns.h index 1770c7307..7f30a3ea0 100644 --- a/apt-pkg/cachefilter-patterns.h +++ b/apt-pkg/cachefilter-patterns.h @@ -95,6 +95,7 @@ struct PatternTreeParser private: std::unique_ptr parse(); + std::unique_ptr parseAnd(); std::unique_ptr parseUnary(); std::unique_ptr parsePrimary(); std::unique_ptr parsePattern(); diff --git a/doc/apt-patterns.7.xml b/doc/apt-patterns.7.xml index 72f1ccbce..3de9b00fc 100644 --- a/doc/apt-patterns.7.xml +++ b/doc/apt-patterns.7.xml @@ -43,7 +43,7 @@ patterns. - ?and(PATTERN, PATTERN, ...) + ?and(PATTERN, PATTERN, ...)PATTERN PATTERN ... Selects objects where all specified patterns match. ?false~F diff --git a/test/libapt/pattern_test.cc b/test/libapt/pattern_test.cc index 7fc6a1f8f..00d356d47 100644 --- a/test/libapt/pattern_test.cc +++ b/test/libapt/pattern_test.cc @@ -176,4 +176,12 @@ TEST(TreeParserTest, ParseShortPattern) caught = true; }; EXPECT_TRUE(caught) << "!X should have thrown an exception"; + + EXPECT_PATTERN_EQ("?a?b", "?and(?a, ?b)"); + EXPECT_PATTERN_EQ("~T~F", "?and(?true, ?false)"); + EXPECT_PATTERN_EQ("~T ~F", "?and(?true, ?false)"); + EXPECT_PATTERN_EQ("~T !~F", "?and(?true, ?not(?false))"); + EXPECT_PATTERN_EQ("!~F ~T", "?and(?not(?false), ?true)"); + EXPECT_PATTERN_EQ("!~F~T", "?and(?not(?false), ?true)"); + } -- cgit v1.2.3-70-g09d2 From d6f38436a229dc4421e77b58bf42d07bdb28b808 Mon Sep 17 00:00:00 2001 From: Julian Andres Klode Date: Sat, 1 Feb 2020 17:12:35 +0100 Subject: Implement | as or --- apt-pkg/cachefilter-patterns.cc | 44 ++++++++++++++++++++++++++++++++++++++--- apt-pkg/cachefilter-patterns.h | 1 + test/libapt/pattern_test.cc | 3 +++ 3 files changed, 45 insertions(+), 3 deletions(-) (limited to 'apt-pkg/cachefilter-patterns.h') diff --git a/apt-pkg/cachefilter-patterns.cc b/apt-pkg/cachefilter-patterns.cc index dbe42b83f..8c0b35de2 100644 --- a/apt-pkg/cachefilter-patterns.cc +++ b/apt-pkg/cachefilter-patterns.cc @@ -70,7 +70,45 @@ std::unique_ptr PatternTreeParser::parseTop() // Parse any pattern std::unique_ptr PatternTreeParser::parse() { - return parseAnd(); + return parseOr(); +} + +std::unique_ptr PatternTreeParser::parseOr() +{ + auto start = state.offset; + std::vector> nodes; + + auto firstNode = parseAnd(); + + if (firstNode == nullptr) + return nullptr; + + nodes.push_back(std::move(firstNode)); + for (skipSpace(); sentence[state.offset] == '|'; skipSpace()) + { + state.offset++; + skipSpace(); + auto node = parseAnd(); + + if (node == nullptr) + throw Error{Node{state.offset, sentence.size()}, "Expected pattern after |"}; + + nodes.push_back(std::move(node)); + } + + if (nodes.size() == 0) + return nullptr; + if (nodes.size() == 1) + return std::move(nodes[0]); + + auto node = std::make_unique(); + node->start = start; + node->end = nodes[nodes.size() - 1]->end; + node->term = "?or"; + node->arguments = std::move(nodes); + node->haveArgumentList = true; + + return node; } std::unique_ptr PatternTreeParser::parseAnd() @@ -266,8 +304,8 @@ std::unique_ptr PatternTreeParser::parseQuotedWord() // Parse a bare word atom std::unique_ptr PatternTreeParser::parseWord() { - static const constexpr auto DISALLOWED_START = "!?~,()\0"_sv; - static const constexpr auto DISALLOWED = ",()\0"_sv; + static const constexpr auto DISALLOWED_START = "!?~|,()\0"_sv; + static const constexpr auto DISALLOWED = "|,()\0"_sv; if (DISALLOWED_START.find(sentence[state.offset]) != APT::StringView::npos) return nullptr; diff --git a/apt-pkg/cachefilter-patterns.h b/apt-pkg/cachefilter-patterns.h index 7f30a3ea0..c6e701880 100644 --- a/apt-pkg/cachefilter-patterns.h +++ b/apt-pkg/cachefilter-patterns.h @@ -95,6 +95,7 @@ struct PatternTreeParser private: std::unique_ptr parse(); + std::unique_ptr parseOr(); std::unique_ptr parseAnd(); std::unique_ptr parseUnary(); std::unique_ptr parsePrimary(); diff --git a/test/libapt/pattern_test.cc b/test/libapt/pattern_test.cc index 00d356d47..d8d962758 100644 --- a/test/libapt/pattern_test.cc +++ b/test/libapt/pattern_test.cc @@ -184,4 +184,7 @@ TEST(TreeParserTest, ParseShortPattern) EXPECT_PATTERN_EQ("!~F ~T", "?and(?not(?false), ?true)"); EXPECT_PATTERN_EQ("!~F~T", "?and(?not(?false), ?true)"); + EXPECT_PATTERN_EQ("!~F~T | ~T", "?or(?and(?not(?false), ?true), ?true)"); + EXPECT_PATTERN_EQ("~ramd64|~rall", "?or(?architecture(amd64), ?architecture(all))"); + } -- cgit v1.2.3-70-g09d2 From 8886ea163032fb8bf64211a94c5dc252a4572a9c Mon Sep 17 00:00:00 2001 From: Julian Andres Klode Date: Sat, 1 Feb 2020 17:21:40 +0100 Subject: patterns: Implement parsing of (...) groups --- apt-pkg/cachefilter-patterns.cc | 26 ++++++++++++++++++++++++++ apt-pkg/cachefilter-patterns.h | 1 + test/libapt/pattern_test.cc | 4 ++++ 3 files changed, 31 insertions(+) (limited to 'apt-pkg/cachefilter-patterns.h') diff --git a/apt-pkg/cachefilter-patterns.cc b/apt-pkg/cachefilter-patterns.cc index 8c0b35de2..c6875d995 100644 --- a/apt-pkg/cachefilter-patterns.cc +++ b/apt-pkg/cachefilter-patterns.cc @@ -169,10 +169,36 @@ std::unique_ptr PatternTreeParser::parsePrimary() return node; if ((node = parsePattern()) != nullptr) return node; + if ((node = parseGroup()) != nullptr) + return node; return nullptr; } +std::unique_ptr PatternTreeParser::parseGroup() +{ + if (sentence[state.offset] != '(') + return nullptr; + + auto start = state.offset++; + + skipSpace(); + auto node = parse(); + if (node == nullptr) + throw Error{Node{state.offset, sentence.size()}, + "Expected pattern after '('"}; + skipSpace(); + + if (sentence[state.offset] != ')') + throw Error{Node{state.offset, sentence.size()}, + "Expected closing parenthesis"}; + + auto end = ++state.offset; + node->start = start; + node->end = end; + return node; +} + std::unique_ptr PatternTreeParser::parseArgument() { std::unique_ptr node; diff --git a/apt-pkg/cachefilter-patterns.h b/apt-pkg/cachefilter-patterns.h index c6e701880..1b7e70da5 100644 --- a/apt-pkg/cachefilter-patterns.h +++ b/apt-pkg/cachefilter-patterns.h @@ -99,6 +99,7 @@ struct PatternTreeParser std::unique_ptr parseAnd(); std::unique_ptr parseUnary(); std::unique_ptr parsePrimary(); + std::unique_ptr parseGroup(); std::unique_ptr parsePattern(); std::unique_ptr parseShortPattern(); std::unique_ptr parseArgument(); diff --git a/test/libapt/pattern_test.cc b/test/libapt/pattern_test.cc index d8d962758..ca77959e3 100644 --- a/test/libapt/pattern_test.cc +++ b/test/libapt/pattern_test.cc @@ -187,4 +187,8 @@ TEST(TreeParserTest, ParseShortPattern) EXPECT_PATTERN_EQ("!~F~T | ~T", "?or(?and(?not(?false), ?true), ?true)"); EXPECT_PATTERN_EQ("~ramd64|~rall", "?or(?architecture(amd64), ?architecture(all))"); + EXPECT_PATTERN_EQ("(?A|?B)?C", "?and(?or(?A, ?B), ?C)"); + EXPECT_PATTERN_EQ("?A|?B?C", "?or(?A, ?and(?B, ?C))"); + EXPECT_PATTERN_EQ("?A|(?B?C)", "?or(?A, ?and(?B, ?C))"); + EXPECT_PATTERN_EQ("(?B?C)|?A", "?or(?and(?B, ?C), ?A)"); } -- cgit v1.2.3-70-g09d2 From 11a40ab11f72f85e905bdba4d3274870fbcaeaee Mon Sep 17 00:00:00 2001 From: Julian Andres Klode Date: Sat, 1 Feb 2020 17:33:08 +0100 Subject: Correctly stop parsing short form arguments on space, also on ? we have to stop parsing on space so that things like ~ramd64 | ~rall work correctly. aptitude does not stop parsing on ?, but we'll do as it gets very confusing otherwise if you write stuff like ~ramd64?name(foo), and it resolves to ?and(?architecture(amd64?name), (foo))... --- apt-pkg/cachefilter-patterns.cc | 19 +++++++++++-------- apt-pkg/cachefilter-patterns.h | 4 ++-- test/libapt/pattern_test.cc | 2 ++ 3 files changed, 15 insertions(+), 10 deletions(-) (limited to 'apt-pkg/cachefilter-patterns.h') diff --git a/apt-pkg/cachefilter-patterns.cc b/apt-pkg/cachefilter-patterns.cc index c6875d995..1c92a7b1f 100644 --- a/apt-pkg/cachefilter-patterns.cc +++ b/apt-pkg/cachefilter-patterns.cc @@ -199,12 +199,12 @@ std::unique_ptr PatternTreeParser::parseGroup() return node; } -std::unique_ptr PatternTreeParser::parseArgument() +std::unique_ptr PatternTreeParser::parseArgument(bool shrt) { std::unique_ptr node; if ((node = parseQuotedWord()) != nullptr) return node; - if ((node = parseWord()) != nullptr) + if ((node = parseWord(shrt)) != nullptr) return node; if ((node = parse()) != nullptr) return node; @@ -231,7 +231,7 @@ std::unique_ptr PatternTreeParser::parseShortPattern() state.offset += sp.shortName.size() + 1; if (sp.takesArgument) { - node->arguments.push_back(parseArgument()); + node->arguments.push_back(parseArgument(true)); node->haveArgumentList = true; } node->end = state.offset; @@ -279,7 +279,7 @@ std::unique_ptr PatternTreeParser::parsePattern() return node; } - node->arguments.push_back(parseArgument()); + node->arguments.push_back(parseArgument(false)); skipSpace(); while (sentence[state.offset] == ',') { @@ -288,7 +288,7 @@ std::unique_ptr PatternTreeParser::parsePattern() // This was a trailing comma - allow it and break the loop if (sentence[state.offset] == ')') break; - node->arguments.push_back(parseArgument()); + node->arguments.push_back(parseArgument(false)); skipSpace(); } @@ -328,10 +328,13 @@ std::unique_ptr PatternTreeParser::parseQuotedWord() } // Parse a bare word atom -std::unique_ptr PatternTreeParser::parseWord() +std::unique_ptr PatternTreeParser::parseWord(bool shrt) { - static const constexpr auto DISALLOWED_START = "!?~|,()\0"_sv; - static const constexpr auto DISALLOWED = "|,()\0"_sv; + static const constexpr auto DISALLOWED_START = "!?~|,() \0"_sv; + static const constexpr auto DISALLOWED_LONG = "|,()\0"_sv; + static const constexpr auto DISALLOWED_SHRT = "|,() ?\0"_sv; + const auto DISALLOWED = shrt ? DISALLOWED_SHRT : DISALLOWED_LONG; + if (DISALLOWED_START.find(sentence[state.offset]) != APT::StringView::npos) return nullptr; diff --git a/apt-pkg/cachefilter-patterns.h b/apt-pkg/cachefilter-patterns.h index 1b7e70da5..e79702af8 100644 --- a/apt-pkg/cachefilter-patterns.h +++ b/apt-pkg/cachefilter-patterns.h @@ -102,8 +102,8 @@ struct PatternTreeParser std::unique_ptr parseGroup(); std::unique_ptr parsePattern(); std::unique_ptr parseShortPattern(); - std::unique_ptr parseArgument(); - std::unique_ptr parseWord(); + std::unique_ptr parseArgument(bool shrt); + std::unique_ptr parseWord(bool shrt); std::unique_ptr parseQuotedWord(); }; diff --git a/test/libapt/pattern_test.cc b/test/libapt/pattern_test.cc index ca77959e3..84d09351c 100644 --- a/test/libapt/pattern_test.cc +++ b/test/libapt/pattern_test.cc @@ -186,6 +186,8 @@ TEST(TreeParserTest, ParseShortPattern) EXPECT_PATTERN_EQ("!~F~T | ~T", "?or(?and(?not(?false), ?true), ?true)"); EXPECT_PATTERN_EQ("~ramd64|~rall", "?or(?architecture(amd64), ?architecture(all))"); + EXPECT_PATTERN_EQ("~ramd64 | ~rall", "?or(?architecture(amd64), ?architecture(all))"); + EXPECT_PATTERN_EQ("~ramd64?name(foo)", "?and(?architecture(amd64), ?name(foo))"); EXPECT_PATTERN_EQ("(?A|?B)?C", "?and(?or(?A, ?B), ?C)"); EXPECT_PATTERN_EQ("?A|?B?C", "?or(?A, ?and(?B, ?C))"); -- cgit v1.2.3-70-g09d2 From 404771d0ec11f26a0b631018719e2918a049455b Mon Sep 17 00:00:00 2001 From: Julian Andres Klode Date: Mon, 3 Feb 2020 12:15:07 +0100 Subject: patterns: test for empty terms, reject them --- apt-pkg/cachefilter-patterns.cc | 3 +++ apt-pkg/cachefilter-patterns.h | 2 +- test/libapt/pattern_test.cc | 26 ++++++++++++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) (limited to 'apt-pkg/cachefilter-patterns.h') diff --git a/apt-pkg/cachefilter-patterns.cc b/apt-pkg/cachefilter-patterns.cc index 1c92a7b1f..5a58a9767 100644 --- a/apt-pkg/cachefilter-patterns.cc +++ b/apt-pkg/cachefilter-patterns.cc @@ -263,6 +263,9 @@ std::unique_ptr PatternTreeParser::parsePattern() node->term = sentence.substr(node->start, state.offset - node->start); + if (node->term.size() <= 1) + throw Error{*node, "Pattern must have a term/name"}; + node->end = skipSpace(); // We don't have any arguments, return node; if (sentence[state.offset] != '(') diff --git a/apt-pkg/cachefilter-patterns.h b/apt-pkg/cachefilter-patterns.h index e79702af8..4eeb68594 100644 --- a/apt-pkg/cachefilter-patterns.h +++ b/apt-pkg/cachefilter-patterns.h @@ -92,9 +92,9 @@ struct PatternTreeParser /// There may not be anything before or after the pattern, except for /// whitespace. std::unique_ptr parseTop(); + std::unique_ptr parse(); // public for test cases only private: - std::unique_ptr parse(); std::unique_ptr parseOr(); std::unique_ptr parseAnd(); std::unique_ptr parseUnary(); diff --git a/test/libapt/pattern_test.cc b/test/libapt/pattern_test.cc index 84d09351c..bfcaf2093 100644 --- a/test/libapt/pattern_test.cc +++ b/test/libapt/pattern_test.cc @@ -14,6 +14,32 @@ using namespace APT::Internal; +#define EXPECT_EXCEPTION(exp, exc, msg) \ + caught = false; \ + try \ + { \ + exp; \ + } \ + catch (exc & e) \ + { \ + caught = true; \ + EXPECT_TRUE(e.message.find(msg) != std::string::npos) << msg << " not in " << e.message; \ + }; \ + EXPECT_TRUE(caught) << #exp "should have thrown an exception" + +TEST(TreeParserTest, ParseInvalid) +{ + bool caught = false; + + // Not a valid pattern: Reject + EXPECT_EXCEPTION(PatternTreeParser("?").parse(), PatternTreeParser::Error, "Pattern must have a term"); + EXPECT_EXCEPTION(PatternTreeParser("?AB?").parse(), PatternTreeParser::Error, "Pattern must have a term"); + EXPECT_EXCEPTION(PatternTreeParser("~").parse(), PatternTreeParser::Error, "Unknown short pattern"); + + // Not a pattern at all: Report nullptr + EXPECT_EQ(PatternTreeParser("A?").parse(), nullptr); +} + TEST(TreeParserTest, ParseWord) { auto node = PatternTreeParser("?word(word)").parseTop(); -- cgit v1.2.3-70-g09d2