0166

The Filter That Crashed the Nightly Job

Algorithm
Medium
text
parsing
filters

The Filter That Crashed the Nightly Job

Your extension's setup table has a text field where consultants type a customer filter — 1000..2000|3000, the platform's own filter idiom — and a nightly job later slams that text straight into SetFilter. Last Tuesday somebody saved 1000..2000| with a trailing pipe, and the job died at 02:13 with an error nobody saw until morning. You are writing the syntax check the setup page will run before accepting the value, so a malformed filter is rejected at entry time instead of at 2 AM.

Requirements

Create a codeunit named "Filter Expression Check" with one public procedure:

procedure IsValid(Expression: Text): Boolean

The procedure returns true when the expression follows the mini filter grammar below, and false otherwise — it must never raise an error, no matter how mangled the input is.

The grammar is a deliberate subset of Business Central's real filter syntax (<>, *, @, & and friends are out of scope — and therefore illegal here). All seven rules are graded:

  1. An expression is one or more alternatives separated by |. Every alternative must be non-empty: a leading |, a trailing |, or a doubled || makes the expression invalid, and the empty expression is invalid too.
  2. An alternative is either a group — a complete expression wrapped in parentheses, like (1000..2000|3000) — or a single value or range. A group is the whole alternative: it cannot be glued to other values or groups.
  3. A range is low..high, low.., or ..high; every bound that is present must be a single value. A bare .., a second .. in the same alternative (1..2..3), or a group used as a bound ((1|2)..5) is invalid.
  4. A value is either an unquoted token or a quoted value. An unquoted token is one or more letters A–Z/a–z or digits 0–9 — nothing else, no exceptions.
  5. A quoted value is wrapped in single quotes '. Inside the quotes every character is plain data — |, parentheses, dots and spaces do not act as operators there. A literal quote inside is written doubled ('O''Brien'), and '' is a valid, empty value. A quote that never closes makes the expression invalid, and after a closing quote only .., |, ), or the end of the expression may follow.
  6. Parentheses outside quotes must be balanced and properly nested, and a group must contain a valid expression — (), (1000..2000, 1000) and )( are all invalid.
  7. Outside quotes, only these characters are legal: letters, digits, |, (, ), ', and . as one half of a .. operator. A space, a -, a lone dot (1.5) — anything else anywhere outside quotes — invalidates the whole expression.

The check is purely syntactic: whether low actually sorts before high, or whether any customer matches, is not its business.

What the tests check

The tests call IsValid on valid expressions — a single token, token alternatives, 1000..2000|3000, open-ended ranges in both directions, a quoted value full of operator characters, a doubled quote, the empty quoted value '', quoted range bounds 'A'..'M', groups, nested groups, and a parenthesis inside quotes that must not count toward balancing — and on invalid ones: the empty expression, leading/trailing/doubled |, bare .., 1..2..3, a space outside quotes, a hyphen, a wildcard *, an @, an &, a lone dot, an unclosed (, a stray ), )(, (), an unterminated quote, a quote "closed" only by the escape '', text glued after a closing quote, and a group used as a range bound. Two more expressions are assembled from randomized tokens — one valid, one corrupted — so hardcoding the examples fails.

Learn More

Hint 1
Three independent scans — count parentheses, count quotes, check the characters — will always leak: a parenthesis inside quotes is data, a pipe inside quotes is data, and '1..2..3' breaks no count at all. Read the expression once, left to right, always knowing whether you are inside quotes right now.
Hint 2
Mirror the grammar in code: one small local procedure per rule — an expression is alternatives separated by '|'; an alternative is a group in parentheses or a range; a range is a value, optionally '..' and a second value; a value is a token or a quoted text. Each procedure consumes characters from a shared current position and returns whether it matched.
Hint 3
Keep the expression, its length, and the current position as codeunit variables. On '(' recurse back into the expression parser and require the matching ')'. Inside quotes, when you meet a quote look at the NEXT character: another quote is an escaped literal (skip both), anything else closes the value. The expression is valid only if parsing succeeds AND the position has moved past the last character — leftover text like a stray ')' or a second '..' is the easiest miss.
ALBusiness Central 28.5
Press Compile to check your code compiles — Submit runs the tests.
The code editor is desktop-only
Open this problem on a computer to write and run code. Reading the description, tests and discussion works fine here.