From 595503cfc35c8db3cb4ee4b28f82b7c91ee75dff Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 30 Nov 2023 00:30:46 +0100 Subject: [PATCH] Fix: improve optimization; add tests --- src/Logic/Tags/And.ts | 32 ++++++++++++++++++++++++++------ src/Logic/Tags/Or.ts | 15 +++++++++++++-- src/Logic/Tags/TagUtils.ts | 8 ++++++++ 3 files changed, 47 insertions(+), 8 deletions(-) diff --git a/src/Logic/Tags/And.ts b/src/Logic/Tags/And.ts index 98da75ae0..dff0919f8 100644 --- a/src/Logic/Tags/And.ts +++ b/src/Logic/Tags/And.ts @@ -233,6 +233,15 @@ export class And extends TagsFilter { return And.construct(newAnds) } + /** + * const raw = {"and": [{"or":["leisure=playground","playground!=forest"]},{"or":["leisure=playground","playground!=forest"]}]} + * const parsed = TagUtils.Tag(raw) + * parsed.optimize().asJson() // => {"or":["leisure=playground","playground!=forest"]} + * + * const raw = {"and": [{"and":["advertising=screen"]}, {"and":["advertising~*"]}]}] + * const parsed = TagUtils.Tag(raw) + * parsed.optimize().asJson() // => "advertising=screen" + */ optimize(): TagsFilter | boolean { if (this.and.length === 0) { return true @@ -294,9 +303,17 @@ export class And extends TagsFilter { optimized.splice(i, 1) i-- } - } else if (v !== opt.value) { - // detected an internal conflict - return false + } else { + if (!v.match(opt.value)) { + // We _know_ that for the key of the RegexTag `opt`, the value will be `v`. + // As such, if `opt.value` cannot match `v`, we detected an internal conflict and can fail + + return false + } else { + // Another tag already provided a _stricter_ value then this regex, so we can remove this one! + optimized.splice(i, 1) + i-- + } } } } @@ -374,10 +391,13 @@ export class And extends TagsFilter { const elements = containedOr.or.filter( (candidate) => !commonValues.some((cv) => cv.shadows(candidate)) ) - newOrs.push(Or.construct(elements)) + if (elements.length > 0) { + newOrs.push(Or.construct(elements)) + } + } + if (newOrs.length > 0) { + commonValues.push(And.construct(newOrs)) } - - commonValues.push(And.construct(newOrs)) const result = new Or(commonValues).optimize() if (result === false) { return false diff --git a/src/Logic/Tags/Or.ts b/src/Logic/Tags/Or.ts index da932049a..a0c0f6622 100644 --- a/src/Logic/Tags/Or.ts +++ b/src/Logic/Tags/Or.ts @@ -162,6 +162,12 @@ export class Or extends TagsFilter { return Or.construct(newOrs) } + /** + * const raw = {"or": [{"and":["leisure=playground","playground!=forest"]},{"and":["leisure=playground","playground!=forest"]}]} + * const parsed = TagUtils.Tag(raw) + * parsed.optimize().asJson() // => {"and":["leisure=playground","playground!=forest"]} + * + */ optimize(): TagsFilter | boolean { if (this.or.length === 0) { return false @@ -241,16 +247,21 @@ export class Or extends TagsFilter { const elements = containedAnd.and.filter( (candidate) => !commonValues.some((cv) => cv.shadows(candidate)) ) + if (elements.length == 0) { + continue + } newAnds.push(And.construct(elements)) } + if (newAnds.length > 0) { + commonValues.push(Or.construct(newAnds)) + } - commonValues.push(Or.construct(newAnds)) const result = new And(commonValues).optimize() if (result === true) { return true } else if (result === false) { // neutral element: skip - } else { + } else if (commonValues.length > 0) { newOrs.push(And.construct(commonValues)) } } diff --git a/src/Logic/Tags/TagUtils.ts b/src/Logic/Tags/TagUtils.ts index 91d3a4ca6..0a8de68d6 100644 --- a/src/Logic/Tags/TagUtils.ts +++ b/src/Logic/Tags/TagUtils.ts @@ -325,6 +325,14 @@ export class TagUtils { return tags } + static optimzeJson(json: TagConfigJson): TagConfigJson | boolean { + const optimized = TagUtils.Tag(json).optimize() + if (optimized === true || optimized === false) { + return optimized + } + return optimized.asJson() + } + /** * Given multiple tagsfilters which can be used as answer, will take the tags with the same keys together as set. *