Repository: obsidian-tasks-group/obsidian-tasks
Pull request #2763 (2024-04-09 21:17:01)

feat: Support square brackets and curly braces to surround Boolean sub-expressions
# Description

Support square brackets (`[...]`) and curly braces (`{...}`)  to surround Boolean sub-expressions.

*Note: more improvements are coming so I am not updating the documentation yet.*

## Motivation and Context

To add some alternatives to `(...)` and `"..."`, for when a required filter ends with either `)` or `"`, which would otherwise be treated as part of the Boolean logic rather than the filter.

## How has this been tested?

- Automated tests
- Exploratory testing

## Screenshots (if appropriate)

Example new lines:

```test
[filter by function task.description.includes('a')] AND [description includes "hello world"]
{filter by function task.description.includes('a')} AND {description includes "hello world"}
```

## Types of changes

Changes visible to users:

- [x] **New feature** (prefix: `feat` - non-breaking change which adds functionality)
- [ ] **Documentation** (prefix: `docs` - improvements to any documentation content **for users**)

Internal changes:

- [x] **Refactor** (prefix: `refactor` - non-breaking change which only improves the design or structure of existing code, and making no changes to its external behaviour)
- [x] **Tests** (prefix: `test` - additions and improvements to unit tests and the smoke tests)

## Checklist

- [x] My code follows the code style of this project and passes `yarn run lint`.
- [x] My change requires a change to the documentation.
- [ ] I have [updated the documentation](https://publish.obsidian.md/tasks-contributing/Documentation/About+Documentation) accordingly.
    - *Will be done later*
- [x] My change has adequate [Unit Test coverage](https://publish.obsidian.md/tasks-contributing/Testing/About+Testing).

## Terms

- [x] My contribution follows this project's [contributing guide](https://publish.obsidian.md/tasks-contributing)
- [x] I agree to follow this project's [Code of Conduct](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/CODE_OF_CONDUCT.md)



Patch:
diff --git a/src/Query/Filter/BooleanDelimiters.ts b/src/Query/Filter/BooleanDelimiters.ts
index 3b167eeb..46cf682c 100644
--- a/src/Query/Filter/BooleanDelimiters.ts
+++ b/src/Query/Filter/BooleanDelimiters.ts
@@ -1,7 +1,16 @@
+import { escapeRegExp } from '../../lib/RegExpTools';
+
 export function anyOfTheseChars(allowedChars: string): string {
-    return new RegExp('[' + allowedChars + ']').source;
+    return new RegExp('[' + escapeRegExp(allowedChars) + ']').source;
 }
 
+const delimiterPairs = [
+    ['(', ')'],
+    ['[', ']'],
+    ['{', '}'],
+    ['"', '"'],
+];
+
 /**
  * A class to try to identify the type of delimiter used between Boolean operators.
  *
@@ -28,7 +37,15 @@ export class BooleanDelimiters {
     }
 
     public static allSupportedDelimiters(): BooleanDelimiters {
-        return new BooleanDelimiters('("', ')"', '()"');
+        let opening = '';
+        let closing = '';
+        let openingAndClosing = '';
+        for (const [openingDelimiter, closingDelimiter] of delimiterPairs) {
+            opening += openingDelimiter;
+            closing += closingDelimiter;
+            openingAndClosing += BooleanDelimiters.openAndClosing(openingDelimiter, closingDelimiter);
+        }
+        return new BooleanDelimiters(opening, closing, openingAndClosing);
     }
 
     public static fromInstructionLine(instruction: string) {
@@ -44,17 +61,30 @@ export class BooleanDelimiters {
             const firstChar = instructionWithoutAnyLeadingOperators[0];
             const lastChar = instructionWithoutAnyLeadingOperators.slice(-1);
 
-            if (firstChar === '(' && lastChar === ')') {
-                return new BooleanDelimiters('(', ')', '()');
-            }
-
-            if (firstChar === '"' && lastChar === '"') {
-                return new BooleanDelimiters('"', '"', '"');
+            for (const [openingDelimiter, closingDelimiter] of delimiterPairs) {
+                if (firstChar === openingDelimiter && lastChar === closingDelimiter) {
+                    const openingAndClosingDelimiters = this.openAndClosing(openingDelimiter, closingDelimiter);
+                    return new BooleanDelimiters(openingDelimiter, closingDelimiter, openingAndClosingDelimiters);
+                }
             }
         }
 
-        throw new Error(
-            'All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or "...". Combinations of those delimiters are no longer supported.',
-        );
+        const message =
+            'All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: ' +
+            delimiterPairs
+                .map(([open, close]) => {
+                    return open + '...' + close;
+                })
+                .join(' or ') +
+            '. Combinations of those delimiters are no longer supported.';
+        throw new Error(message);
+    }
+
+    private static openAndClosing(openingDelimiter: string, closingDelimiter: string) {
+        let openingAndClosingDelimiters = openingDelimiter;
+        if (closingDelimiter != openingDelimiter) {
+            openingAndClosingDelimiters += closingDelimiter;
+        }
+        return openingAndClosingDelimiters;
     }
 }
diff --git a/src/Query/Filter/BooleanPreprocessor.ts b/src/Query/Filter/BooleanPreprocessor.ts
index dea7fd03..ba516c2e 100644
--- a/src/Query/Filter/BooleanPreprocessor.ts
+++ b/src/Query/Filter/BooleanPreprocessor.ts
@@ -88,6 +88,16 @@ export class BooleanPreprocessor {
             }
         });
 
+        // convert any non-standard delimiters to standard ones:
+        const openChars = delimiters.openFilterChars;
+        if (openChars != '"' && openChars != '(') {
+            const openDelimiter = new RegExp(anyOfTheseChars(openChars), 'g');
+            simplifiedLine = simplifiedLine.replace(openDelimiter, '(');
+
+            const closeChars = delimiters.closeFilterChars;
+            const closeDelimiter = new RegExp(anyOfTheseChars(closeChars), 'g');
+            simplifiedLine = simplifiedLine.replace(closeDelimiter, ')');
+        }
         return { simplifiedLine, filters };
     }
 


Test patch:
diff --git a/tests/Query/Filter/BooleanDelimiters.test.ts b/tests/Query/Filter/BooleanDelimiters.test.ts
index cbcf99f3..5b3001a6 100644
--- a/tests/Query/Filter/BooleanDelimiters.test.ts
+++ b/tests/Query/Filter/BooleanDelimiters.test.ts
@@ -8,6 +8,22 @@ function shouldDelimitWithParentheses(line: string) {
     expect(delimiters.openAndCloseFilterChars).toEqual('()');
 }
 
+function shouldDelimitWithSquareBrackets(line: string) {
+    const delimiters = BooleanDelimiters.fromInstructionLine(line);
+
+    expect(delimiters.openFilterChars).toEqual('[');
+    expect(delimiters.closeFilterChars).toEqual(']');
+    expect(delimiters.openAndCloseFilterChars).toEqual('[]');
+}
+
+function shouldDelimitWithCurlyBraces(line: string) {
+    const delimiters = BooleanDelimiters.fromInstructionLine(line);
+
+    expect(delimiters.openFilterChars).toEqual('{');
+    expect(delimiters.closeFilterChars).toEqual('}');
+    expect(delimiters.openAndCloseFilterChars).toEqual('{}');
+}
+
 function shouldDelimitWithDoubleQuotes(line: string) {
     const delimiters = BooleanDelimiters.fromInstructionLine(line);
 
@@ -22,7 +38,7 @@ function shouldThrow(line: string) {
     };
     expect(t).toThrow(Error);
     expect(t).toThrowError(
-        'All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or "...". Combinations of those delimiters are no longer supported.',
+        'All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or [...] or {...} or "...". Combinations of those delimiters are no longer supported.',
     );
 }
 
@@ -30,13 +46,13 @@ describe('BooleanDelimiters', () => {
     it('construction - all delimiters', () => {
         const delimiters = BooleanDelimiters.allSupportedDelimiters();
 
-        expect(delimiters.openFilterChars).toEqual('("');
-        expect(delimiters.openFilter).toEqual('[("]');
+        expect(delimiters.openFilterChars).toEqual('([{"');
+        expect(delimiters.openFilter).toEqual('[\\(\\[\\{"]');
 
-        expect(delimiters.closeFilterChars).toEqual(')"');
-        expect(delimiters.closeFilter).toEqual('[)"]');
+        expect(delimiters.closeFilterChars).toEqual(')]}"');
+        expect(delimiters.closeFilter).toEqual('[\\)\\]\\}"]');
 
-        expect(delimiters.openAndCloseFilterChars).toEqual('()"');
+        expect(delimiters.openAndCloseFilterChars).toEqual('()[]{}"');
     });
 
     describe('construct from line with binary operators', () => {
@@ -44,20 +60,24 @@ describe('BooleanDelimiters', () => {
             shouldDelimitWithParentheses('(not done) OR (done)');
         });
 
-        it('does not recognise inconsistent delimiters in middle of line', () => {
-            shouldDelimitWithParentheses('(not done) OR "done" OR (done)');
-        });
-
         it('should recognise "" delimiters', () => {
             shouldDelimitWithDoubleQuotes('"not done" OR "done"');
         });
 
+        it('should recognise [] delimiters', () => {
+            shouldDelimitWithSquareBrackets('[not done] OR [done]');
+        });
+
+        it('should recognise {} delimiters', () => {
+            shouldDelimitWithCurlyBraces('{not done} OR {done}');
+        });
+
         it('should reject line with mixed delimiters', () => {
             shouldThrow('(not done) OR "done"');
         });
 
         it('should reject line with unknown delimiters', () => {
-            shouldThrow('{not done} OR {done}');
+            shouldThrow('<not done> OR <done>');
         });
     });
 
@@ -75,7 +95,7 @@ describe('BooleanDelimiters', () => {
         });
 
         it('should reject line with unknown delimiters', () => {
-            shouldThrow('NOT {not done}');
+            shouldThrow('NOT <not done>');
         });
     });
 
diff --git a/tests/Query/Filter/BooleanField.test.boolean_query_-_exhaustive_tests_explain.approved.txt b/tests/Query/Filter/BooleanField.test.boolean_query_-_exhaustive_tests_explain.approved.txt
index 61890bbe..cac5cd1f 100644
--- a/tests/Query/Filter/BooleanField.test.boolean_query_-_exhaustive_tests_explain.approved.txt
+++ b/tests/Query/Filter/BooleanField.test.boolean_query_-_exhaustive_tests_explain.approved.txt
@@ -104,7 +104,7 @@ Could not interpret the following instruction as a Boolean combination:
     "not done" AND (is recurring)
 
 The error message is:
-    All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or "...". Combinations of those delimiters are no longer supported.
+    All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or [...] or {...} or "...". Combinations of those delimiters are no longer supported.
 
 --------------------------------------------------------
 
@@ -117,7 +117,7 @@ Could not interpret the following instruction as a Boolean combination:
     "not done" OR (is recurring)
 
 The error message is:
-    All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or "...". Combinations of those delimiters are no longer supported.
+    All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or [...] or {...} or "...". Combinations of those delimiters are no longer supported.
 
 --------------------------------------------------------
 
@@ -130,7 +130,7 @@ Could not interpret the following instruction as a Boolean combination:
     "not done" XOR (is recurring)
 
 The error message is:
-    All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or "...". Combinations of those delimiters are no longer supported.
+    All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or [...] or {...} or "...". Combinations of those delimiters are no longer supported.
 
 --------------------------------------------------------
 
@@ -143,7 +143,7 @@ Could not interpret the following instruction as a Boolean combination:
     "not done" AND NOT (is recurring)
 
 The error message is:
-    All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or "...". Combinations of those delimiters are no longer supported.
+    All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or [...] or {...} or "...". Combinations of those delimiters are no longer supported.
 
 --------------------------------------------------------
 
@@ -156,7 +156,7 @@ Could not interpret the following instruction as a Boolean combination:
     "not done" OR NOT (is recurring)
 
 The error message is:
-    All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or "...". Combinations of those delimiters are no longer supported.
+    All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or [...] or {...} or "...". Combinations of those delimiters are no longer supported.
 
 --------------------------------------------------------
 
@@ -170,7 +170,7 @@ Could not interpret the following instruction as a Boolean combination:
     (not done) AND "is recurring"
 
 The error message is:
-    All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or "...". Combinations of those delimiters are no longer supported.
+    All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or [...] or {...} or "...". Combinations of those delimiters are no longer supported.
 
 --------------------------------------------------------
 
@@ -183,7 +183,7 @@ Could not interpret the following instruction as a Boolean combination:
     ((not done)) AND "is recurring"
 
 The error message is:
-    All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or "...". Combinations of those delimiters are no longer supported.
+    All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or [...] or {...} or "...". Combinations of those delimiters are no longer supported.
 
 --------------------------------------------------------
 
@@ -196,7 +196,7 @@ Could not interpret the following instruction as a Boolean combination:
     (not done) OR "is recurring"
 
 The error message is:
-    All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or "...". Combinations of those delimiters are no longer supported.
+    All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or [...] or {...} or "...". Combinations of those delimiters are no longer supported.
 
 --------------------------------------------------------
 
@@ -209,7 +209,7 @@ Could not interpret the following instruction as a Boolean combination:
     (not done) XOR "is recurring"
 
 The error message is:
-    All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or "...". Combinations of those delimiters are no longer supported.
+    All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or [...] or {...} or "...". Combinations of those delimiters are no longer supported.
 
 --------------------------------------------------------
 
@@ -222,7 +222,7 @@ Could not interpret the following instruction as a Boolean combination:
     (not done) AND NOT "is recurring"
 
 The error message is:
-    All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or "...". Combinations of those delimiters are no longer supported.
+    All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or [...] or {...} or "...". Combinations of those delimiters are no longer supported.
 
 --------------------------------------------------------
 
@@ -235,7 +235,7 @@ Could not interpret the following instruction as a Boolean combination:
     (not done) OR NOT "is recurring"
 
 The error message is:
-    All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or "...". Combinations of those delimiters are no longer supported.
+    All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or [...] or {...} or "...". Combinations of those delimiters are no longer supported.
 
 --------------------------------------------------------
 
@@ -249,7 +249,7 @@ Could not interpret the following instruction as a Boolean combination:
     "HAS DUE DATE" OR (DESCRIPTION INCLUDES SPECIAL)
 
 The error message is:
-    All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or "...". Combinations of those delimiters are no longer supported.
+    All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or [...] or {...} or "...". Combinations of those delimiters are no longer supported.
 
 --------------------------------------------------------
 
@@ -275,7 +275,7 @@ Could not interpret the following instruction as a Boolean combination:
     "has due date" OR (description includes special)
 
 The error message is:
-    All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or "...". Combinations of those delimiters are no longer supported.
+    All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or [...] or {...} or "...". Combinations of those delimiters are no longer supported.
 
 --------------------------------------------------------
 
@@ -585,7 +585,7 @@ Could not interpret the following instruction as a Boolean combination:
     (HAS START DATE) AND "DESCRIPTION INCLUDES SOME"
 
 The error message is:
-    All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or "...". Combinations of those delimiters are no longer supported.
+    All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or [...] or {...} or "...". Combinations of those delimiters are no longer supported.
 
 --------------------------------------------------------
 
@@ -1749,7 +1749,7 @@ Could not interpret the following instruction as a Boolean combination:
     (has start date) AND "description includes some"
 
 The error message is:
-    All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or "...". Combinations of those delimiters are no longer supported.
+    All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or [...] or {...} or "...". Combinations of those delimiters are no longer supported.
 
 --------------------------------------------------------
 
@@ -3127,6 +3127,64 @@ Result:
 --------------------------------------------------------
 
 
+Input:
+'[due this week] AND [description includes I use square brackets]'
+=>
+Result:
+  [due this week] AND [description includes I use square brackets] =>
+    AND (All of):
+      due this week =>
+        due date is between:
+          2024-03-25 (Monday 25th March 2024) and
+          2024-03-31 (Sunday 31st March 2024) inclusive
+      description includes I use square brackets
+
+
+--------------------------------------------------------
+
+
+Input:
+'[filter by function task.description.includes('a')] AND [description includes "hello world"]'
+=>
+Result:
+  [filter by function task.description.includes('a')] AND [description includes "hello world"] =>
+    AND (All of):
+      filter by function task.description.includes('a')
+      description includes "hello world"
+
+
+--------------------------------------------------------
+
+
+Input:
+'{due this week} AND {description includes I use curly braces}'
+=>
+Result:
+  {due this week} AND {description includes I use curly braces} =>
+    AND (All of):
+      due this week =>
+        due date is between:
+          2024-03-25 (Monday 25th March 2024) and
+          2024-03-31 (Sunday 31st March 2024) inclusive
+      description includes I use curly braces
+
+
+--------------------------------------------------------
+
+
+Input:
+'{filter by function task.description.includes('a')} AND {description includes "hello world"}'
+=>
+Result:
+  {filter by function task.description.includes('a')} AND {description includes "hello world"} =>
+    AND (All of):
+      filter by function task.description.includes('a')
+      description includes "hello world"
+
+
+--------------------------------------------------------
+
+
 Input:
 'AND (description includes d1)'
 =>
diff --git a/tests/Query/Filter/BooleanField.test.boolean_query_-_exhaustive_tests_preprocess_-_rewrite.approved.txt b/tests/Query/Filter/BooleanField.test.boolean_query_-_exhaustive_tests_preprocess_-_rewrite.approved.txt
index 528165f0..b44bfb27 100644
--- a/tests/Query/Filter/BooleanField.test.boolean_query_-_exhaustive_tests_preprocess_-_rewrite.approved.txt
+++ b/tests/Query/Filter/BooleanField.test.boolean_query_-_exhaustive_tests_preprocess_-_rewrite.approved.txt
@@ -114,7 +114,7 @@ Input:
 Result:
 Error: Parsing expression.
 The error message was:
-    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or "...". Combinations of those delimiters are no longer supported."
+    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or [...] or {...} or "...". Combinations of those delimiters are no longer supported."
 
 --------------------------------------------------------
 
@@ -125,7 +125,7 @@ Input:
 Result:
 Error: Parsing expression.
 The error message was:
-    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or "...". Combinations of those delimiters are no longer supported."
+    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or [...] or {...} or "...". Combinations of those delimiters are no longer supported."
 
 --------------------------------------------------------
 
@@ -136,7 +136,7 @@ Input:
 Result:
 Error: Parsing expression.
 The error message was:
-    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or "...". Combinations of those delimiters are no longer supported."
+    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or [...] or {...} or "...". Combinations of those delimiters are no longer supported."
 
 --------------------------------------------------------
 
@@ -147,7 +147,7 @@ Input:
 Result:
 Error: Parsing expression.
 The error message was:
-    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or "...". Combinations of those delimiters are no longer supported."
+    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or [...] or {...} or "...". Combinations of those delimiters are no longer supported."
 
 --------------------------------------------------------
 
@@ -158,7 +158,7 @@ Input:
 Result:
 Error: Parsing expression.
 The error message was:
-    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or "...". Combinations of those delimiters are no longer supported."
+    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or [...] or {...} or "...". Combinations of those delimiters are no longer supported."
 
 --------------------------------------------------------
 
@@ -170,7 +170,7 @@ Input:
 Result:
 Error: Parsing expression.
 The error message was:
-    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or "...". Combinations of those delimiters are no longer supported."
+    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or [...] or {...} or "...". Combinations of those delimiters are no longer supported."
 
 --------------------------------------------------------
 
@@ -181,7 +181,7 @@ Input:
 Result:
 Error: Parsing expression.
 The error message was:
-    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or "...". Combinations of those delimiters are no longer supported."
+    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or [...] or {...} or "...". Combinations of those delimiters are no longer supported."
 
 --------------------------------------------------------
 
@@ -192,7 +192,7 @@ Input:
 Result:
 Error: Parsing expression.
 The error message was:
-    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or "...". Combinations of those delimiters are no longer supported."
+    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or [...] or {...} or "...". Combinations of those delimiters are no longer supported."
 
 --------------------------------------------------------
 
@@ -203,7 +203,7 @@ Input:
 Result:
 Error: Parsing expression.
 The error message was:
-    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or "...". Combinations of those delimiters are no longer supported."
+    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or [...] or {...} or "...". Combinations of those delimiters are no longer supported."
 
 --------------------------------------------------------
 
@@ -214,7 +214,7 @@ Input:
 Result:
 Error: Parsing expression.
 The error message was:
-    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or "...". Combinations of those delimiters are no longer supported."
+    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or [...] or {...} or "...". Combinations of those delimiters are no longer supported."
 
 --------------------------------------------------------
 
@@ -225,7 +225,7 @@ Input:
 Result:
 Error: Parsing expression.
 The error message was:
-    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or "...". Combinations of those delimiters are no longer supported."
+    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or [...] or {...} or "...". Combinations of those delimiters are no longer supported."
 
 --------------------------------------------------------
 
@@ -237,7 +237,7 @@ Input:
 Result:
 Error: Parsing expression.
 The error message was:
-    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or "...". Combinations of those delimiters are no longer supported."
+    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or [...] or {...} or "...". Combinations of those delimiters are no longer supported."
 
 --------------------------------------------------------
 
@@ -263,7 +263,7 @@ Input:
 Result:
 Error: Parsing expression.
 The error message was:
-    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or "...". Combinations of those delimiters are no longer supported."
+    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or [...] or {...} or "...". Combinations of those delimiters are no longer supported."
 
 --------------------------------------------------------
 
@@ -562,7 +562,7 @@ Input:
 Result:
 Error: Parsing expression.
 The error message was:
-    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or "...". Combinations of those delimiters are no longer supported."
+    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or [...] or {...} or "...". Combinations of those delimiters are no longer supported."
 
 --------------------------------------------------------
 
@@ -1703,7 +1703,7 @@ Input:
 Result:
 Error: Parsing expression.
 The error message was:
-    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or "...". Combinations of those delimiters are no longer supported."
+    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or [...] or {...} or "...". Combinations of those delimiters are no longer supported."
 
 --------------------------------------------------------
 
@@ -3001,6 +3001,66 @@ Result:
 --------------------------------------------------------
 
 
+Input:
+'[due this week] AND [description includes I use square brackets]'
+=>
+Result:
+{
+    "simplifiedLine": "(f1) AND (f2)",
+    "filters": {
+        "f1": "due this week",
+        "f2": "description includes I use square brackets"
+    }
+}
+
+--------------------------------------------------------
+
+
+Input:
+'[filter by function task.description.includes('a')] AND [description includes "hello world"]'
+=>
+Result:
+{
+    "simplifiedLine": "(f1) AND (f2)",
+    "filters": {
+        "f1": "filter by function task.description.includes('a')",
+        "f2": "description includes \"hello world\""
+    }
+}
+
+--------------------------------------------------------
+
+
+Input:
+'{due this week} AND {description includes I use curly braces}'
+=>
+Result:
+{
+    "simplifiedLine": "(f1) AND (f2)",
+    "filters": {
+        "f1": "due this week",
+        "f2": "description includes I use curly braces"
+    }
+}
+
+--------------------------------------------------------
+
+
+Input:
+'{filter by function task.description.includes('a')} AND {description includes "hello world"}'
+=>
+Result:
+{
+    "simplifiedLine": "(f1) AND (f2)",
+    "filters": {
+        "f1": "filter by function task.description.includes('a')",
+        "f2": "description includes \"hello world\""
+    }
+}
+
+--------------------------------------------------------
+
+
 Input:
 'AND (description includes d1)'
 =>
diff --git a/tests/Query/Filter/BooleanField.test.boolean_query_-_exhaustive_tests_preprocess_-_split_line.approved.txt b/tests/Query/Filter/BooleanField.test.boolean_query_-_exhaustive_tests_preprocess_-_split_line.approved.txt
index 5ff9ada3..f2912a9d 100644
--- a/tests/Query/Filter/BooleanField.test.boolean_query_-_exhaustive_tests_preprocess_-_split_line.approved.txt
+++ b/tests/Query/Filter/BooleanField.test.boolean_query_-_exhaustive_tests_preprocess_-_split_line.approved.txt
@@ -119,7 +119,7 @@ Input:
 Result:
 Error: Parsing expression.
 The error message was:
-    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or "...". Combinations of those delimiters are no longer supported."
+    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or [...] or {...} or "...". Combinations of those delimiters are no longer supported."
 
 --------------------------------------------------------
 
@@ -130,7 +130,7 @@ Input:
 Result:
 Error: Parsing expression.
 The error message was:
-    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or "...". Combinations of those delimiters are no longer supported."
+    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or [...] or {...} or "...". Combinations of those delimiters are no longer supported."
 
 --------------------------------------------------------
 
@@ -141,7 +141,7 @@ Input:
 Result:
 Error: Parsing expression.
 The error message was:
-    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or "...". Combinations of those delimiters are no longer supported."
+    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or [...] or {...} or "...". Combinations of those delimiters are no longer supported."
 
 --------------------------------------------------------
 
@@ -152,7 +152,7 @@ Input:
 Result:
 Error: Parsing expression.
 The error message was:
-    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or "...". Combinations of those delimiters are no longer supported."
+    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or [...] or {...} or "...". Combinations of those delimiters are no longer supported."
 
 --------------------------------------------------------
 
@@ -163,7 +163,7 @@ Input:
 Result:
 Error: Parsing expression.
 The error message was:
-    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or "...". Combinations of those delimiters are no longer supported."
+    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or [...] or {...} or "...". Combinations of those delimiters are no longer supported."
 
 --------------------------------------------------------
 
@@ -175,7 +175,7 @@ Input:
 Result:
 Error: Parsing expression.
 The error message was:
-    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or "...". Combinations of those delimiters are no longer supported."
+    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or [...] or {...} or "...". Combinations of those delimiters are no longer supported."
 
 --------------------------------------------------------
 
@@ -186,7 +186,7 @@ Input:
 Result:
 Error: Parsing expression.
 The error message was:
-    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or "...". Combinations of those delimiters are no longer supported."
+    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or [...] or {...} or "...". Combinations of those delimiters are no longer supported."
 
 --------------------------------------------------------
 
@@ -197,7 +197,7 @@ Input:
 Result:
 Error: Parsing expression.
 The error message was:
-    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or "...". Combinations of those delimiters are no longer supported."
+    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or [...] or {...} or "...". Combinations of those delimiters are no longer supported."
 
 --------------------------------------------------------
 
@@ -208,7 +208,7 @@ Input:
 Result:
 Error: Parsing expression.
 The error message was:
-    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or "...". Combinations of those delimiters are no longer supported."
+    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or [...] or {...} or "...". Combinations of those delimiters are no longer supported."
 
 --------------------------------------------------------
 
@@ -219,7 +219,7 @@ Input:
 Result:
 Error: Parsing expression.
 The error message was:
-    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or "...". Combinations of those delimiters are no longer supported."
+    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or [...] or {...} or "...". Combinations of those delimiters are no longer supported."
 
 --------------------------------------------------------
 
@@ -230,7 +230,7 @@ Input:
 Result:
 Error: Parsing expression.
 The error message was:
-    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or "...". Combinations of those delimiters are no longer supported."
+    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or [...] or {...} or "...". Combinations of those delimiters are no longer supported."
 
 --------------------------------------------------------
 
@@ -242,7 +242,7 @@ Input:
 Result:
 Error: Parsing expression.
 The error message was:
-    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or "...". Combinations of those delimiters are no longer supported."
+    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or [...] or {...} or "...". Combinations of those delimiters are no longer supported."
 
 --------------------------------------------------------
 
@@ -270,7 +270,7 @@ Input:
 Result:
 Error: Parsing expression.
 The error message was:
-    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or "...". Combinations of those delimiters are no longer supported."
+    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or [...] or {...} or "...". Combinations of those delimiters are no longer supported."
 
 --------------------------------------------------------
 
@@ -667,7 +667,7 @@ Input:
 Result:
 Error: Parsing expression.
 The error message was:
-    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or "...". Combinations of those delimiters are no longer supported."
+    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or [...] or {...} or "...". Combinations of those delimiters are no longer supported."
 
 --------------------------------------------------------
 
@@ -1941,7 +1941,7 @@ Input:
 Result:
 Error: Parsing expression.
 The error message was:
-    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or "...". Combinations of those delimiters are no longer supported."
+    "Error: All filters in a Boolean instruction must be inside one of these pairs of delimiter characters: (...) or [...] or {...} or "...". Combinations of those delimiters are no longer supported."
 
 --------------------------------------------------------
 
@@ -3385,6 +3385,66 @@ Result:
 --------------------------------------------------------
 
 
+Input:
+'[due this week] AND [description includes I use square brackets]'
+=>
+Result:
+[
+    "[",
+    "due this week",
+    "] AND [",
+    "description includes I use square brackets",
+    "]"
+]
+
+--------------------------------------------------------
+
+
+Input:
+'[filter by function task.description.includes('a')] AND [description includes "hello world"]'
+=>
+Result:
+[
+    "[",
+    "filter by function task.description.includes('a')",
+    "] AND [",
+    "description includes \"hello world\"",
+    "]"
+]
+
+--------------------------------------------------------
+
+
+Input:
+'{due this week} AND {description includes I use curly braces}'
+=>
+Result:
+[
+    "{",
+    "due this week",
+    "} AND {",
+    "description includes I use curly braces",
+    "}"
+]
+
+--------------------------------------------------------
+
+
+Input:
+'{filter by function task.description.includes('a')} AND {description includes "hello world"}'
+=>
+Result:
+[
+    "{",
+    "filter by function task.description.includes('a')",
+    "} AND {",
+    "description includes \"hello world\"",
+    "}"
+]
+
+--------------------------------------------------------
+
+
 Input:
 'AND (description includes d1)'
 =>
diff --git a/tests/Query/Filter/BooleanField.test.ts b/tests/Query/Filter/BooleanField.test.ts
index cca346a0..63a57d79 100644
--- a/tests/Query/Filter/BooleanField.test.ts
+++ b/tests/Query/Filter/BooleanField.test.ts
@@ -30,6 +30,8 @@ describe('boolean query - filter', () => {
     describe('basic operators', () => {
         it.each([
             '(description includes d1) AND (description includes d2)',
+            '[description includes d1] AND [description includes d2]',
+            '{description includes d1} AND {description includes d2}',
             '"description includes d1" AND "description includes d2"',
         ])('instruction: "%s"', (line: string) => {
             // Arrange
@@ -292,6 +294,15 @@ describe('boolean query - filter', () => {
 });
 
 describe('boolean query - explain', () => {
+    beforeAll(() => {
+        jest.useFakeTimers();
+        jest.setSystemTime(new Date('2024-04-07'));
+    });
+
+    afterAll(() => {
+        jest.useRealTimers();
+    });
+
     function explainFilters(indentationLevel: number, source: string) {
         const indentation = ' '.repeat(indentationLevel);
         const path = 'some/sample/note.md';
@@ -299,6 +310,34 @@ describe('boolean query - explain', () => {
         return new Explainer(indentation).explainFilters(query1);
     }
 
+    it('should explain [] delimiters', () => {
+        const line = '[due this week] AND [description includes I use square brackets]';
+        expect(explainFilters(0, line)).toMatchInlineSnapshot(`
+            "[due this week] AND [description includes I use square brackets] =>
+              AND (All of):
+                due this week =>
+                  due date is between:
+                    2024-04-01 (Monday 1st April 2024) and
+                    2024-04-07 (Sunday 7th April 2024) inclusive
+                description includes I use square brackets
+            "
+        `);
+    });
+
+    it('should explain {} delimiters', () => {
+        const line = '{due this week} AND {description includes I use curly braces}';
+        expect(explainFilters(0, line)).toMatchInlineSnapshot(`
+            "{due this week} AND {description includes I use curly braces} =>
+              AND (All of):
+                due this week =>
+                  due date is between:
+                    2024-04-01 (Monday 1st April 2024) and
+                    2024-04-07 (Sunday 7th April 2024) inclusive
+                description includes I use curly braces
+            "
+        `);
+    });
+
     it('should make multi-line explanations consistent in and out of Boolean filter - issue #2707', () => {
         // See https://github.com/obsidian-tasks-group/obsidian-tasks/issues/2707
         const filter = 'description regex matches /buy/i';
diff --git a/tests/Query/Filter/BooleanFieldVerify.ts b/tests/Query/Filter/BooleanFieldVerify.ts
index 42969a47..d6ac474d 100644
--- a/tests/Query/Filter/BooleanFieldVerify.ts
+++ b/tests/Query/Filter/BooleanFieldVerify.ts
@@ -207,6 +207,10 @@ NOT (not done)
 (tags do not include sometag) OR NOT (tags do not include sometag)
 (tags include #sometag) OR NOT (tags include #sometag)
 (tags include sometag) OR NOT (tags include sometag)
+[due this week] AND [description includes I use square brackets]
+[filter by function task.description.includes('a')] AND [description includes "hello world"]
+{due this week} AND {description includes I use curly braces}
+{filter by function task.description.includes('a')} AND {description includes "hello world"}
 AND (description includes d1)
 NOT   (  happens before blahblahblah  )
 NOT  ( description includes d1 )
diff --git a/tests/Query/Filter/BooleanPreprocessor.test.ts b/tests/Query/Filter/BooleanPreprocessor.test.ts
index 10f003ae..a7cee471 100644
--- a/tests/Query/Filter/BooleanPreprocessor.test.ts
+++ b/tests/Query/Filter/BooleanPreprocessor.test.ts
@@ -2,7 +2,7 @@ import { BooleanPreprocessor } from '../../../src/Query/Filter/BooleanPreprocess
 import { BooleanDelimiters } from '../../../src/Query/Filter/BooleanDelimiters';
 
 function preprocess(line: string) {
-    return BooleanPreprocessor.preprocessExpression(line, BooleanDelimiters.allSupportedDelimiters());
+    return BooleanPreprocessor.preprocessExpression(line, BooleanDelimiters.fromInstructionLine(line));
 }
 
 describe('BooleanPreprocessor', () => {
@@ -130,16 +130,14 @@ describe('BooleanPreprocessor', () => {
     describe('filters ending with delimiters', () => {
         it('swallows last character if filter ends with closing delimiter character )', () => {
             const result = preprocess('"description includes (maybe)"');
-            // TODO Fix imbalanced delimiters by requiring the same delimiter set to be used in Boolean lines.
-            expect(result.simplifiedLine).toEqual('"f1)"');
-            expect(result.filters['f1']).toEqual('description includes (maybe');
+            expect(result.simplifiedLine).toEqual('"f1"');
+            expect(result.filters['f1']).toEqual('description includes (maybe)');
         });
 
         it('swallows last character if filter ends with closing delimiter character "', () => {
             const result = preprocess('(description includes "maybe")');
-            // TODO Fix imbalanced delimiters by requiring the same delimiter set to be used in Boolean lines.
-            expect(result.simplifiedLine).toEqual('(f1")');
-            expect(result.filters['f1']).toEqual('description includes "maybe');
+            expect(result.simplifiedLine).toEqual('(f1)');
+            expect(result.filters['f1']).toEqual('description includes "maybe"');
         });
     });
 
diff --git a/tests/Query/Query.test.ts b/tests/Query/Query.test.ts
index 6250a561..f29fcb60 100644
--- a/tests/Query/Query.test.ts
+++ b/tests/Query/Query.test.ts
@@ -43,7 +43,10 @@ describe('Query parsing', () => {
     // In alphabetical order, please
     const filters: ReadonlyArray<string> = [
         // NEW_QUERY_INSTRUCTION_EDIT_REQUIRED
+        '"due this week" AND "description includes Hello World"',
         '(due this week) AND (description includes Hello World)',
+        '[due this week] AND [description includes Hello World]',
+        '{due this week} AND {description includes Hello World}',
         'cancelled after 2021-12-27',
         'cancelled before 2021-12-27',
         'cancelled date is invalid',