Symptom

Ad-hoc string parsing the agent must read sequentially to understand evaluation semantics. Operator precedence is implicit; bugs in the parser cascade silently across every rule. Static analysis of 'what variables does this rule depend on' requires re-implementing the parser in the analysis tool.

Goal

One class per grammar rule, each with interpret(env). The agent reads the AST shape directly from the construction expression; static traversal returns complete answers; per-rule edits scope to one class file.

Before the pattern

function evaluateRule(ruleString, context) {
if (ruleString.includes(' AND ')) {
const [left, right] = ruleString.split(' AND ');
return evaluateRule(left, context) && evaluateRule(right, context);
}
if (ruleString.includes(' OR ')) {
const [left, right] = ruleString.split(' OR ');
return evaluateRule(left, context) || evaluateRule(right, context);
}
if (ruleString.startsWith('NOT ')) {
return !evaluateRule(ruleString.slice(4), context);
}
return Boolean(context[ruleString]);
}
const pass = evaluateRule('verified AND NOT banned', user);
// Strings as DSL, ad-hoc parser, operator precedence implicit in eval order,
// no caching, no static analysis, every consumer re-implements its own dialect.

After the pattern

class Variable {
constructor(name) {
this.name = name;
}
interpret(env) {
return Boolean(env[this.name]);
}
}
class And {
constructor(left, right) {
this.left = left;
this.right = right;
}
interpret(env) {
return this.left.interpret(env) && this.right.interpret(env);
}
}
class Or {
constructor(left, right) {
this.left = left;
this.right = right;
}
interpret(env) {
return this.left.interpret(env) || this.right.interpret(env);
}
}
class Not {
constructor(operand) {
this.operand = operand;
}
interpret(env) {
return !this.operand.interpret(env);
}
}
const rule = new And(new Variable('verified'), new Not(new Variable('banned')));
const pass = rule.interpret(user);
Example source: Illustrative example written for this site in the spirit of Design Patterns (Gamma, Helm, Johnson, Vlissides, Addison-Wesley, 1994), chapter 5. The book's running example is a regular-expression matcher; this JavaScript adaptation uses a tiny boolean rule language (AND, OR, NOT, variables) because the AST is small enough to read in one screen and the structural-vs-string-DSL contrast is sharp.
Pressure

String DSLs are opaque to the type system. The agent reasoning about rule correctness must trace through the parser's logic at every consumer; partial verification produces misleading confidence. New operators add cascading edits the agent must verify across every parsing path.

Tradeoff

AST construction in code is verbose and harder to read than the string DSL. The agent's reading cost on the construction site is higher; tracing a runtime error in an AND.interpret requires walking the recursive call chain through the tree, which can be deep.

Relief

Per-rule edits are one-file; static traversal of 'rules using variable X' is a complete enumeration; type system enforces interpret-method existence on every rule class. The agent's verification budget on grammar changes is bounded and structurally provable.

Trap

Grammars growing past ~10 operators turn into class proliferation the agent navigates as a graph rather than a hierarchy. Visitor or AST-walker tooling becomes mandatory at that scale; without it, every traversal-style analysis requires editing every rule class — the cross-cutting problem the pattern was supposed to avoid.