interpreter

star 0

Apply Interpreter when you see Primitive Obsession, Repeated Switches, Replace Conditional with Polymorphism. One class per grammar rule, each with interpret(env).

wallacedrew By wallacedrew schedule Updated 6/1/2026

name: interpreter description: Apply Interpreter when you see Primitive Obsession, Repeated Switches, Replace Conditional with Polymorphism. One class per grammar rule, each with interpret(env).

Apply: 15 — Interpreter

Announce first: name the chain of refactorings pointing at Interpreter and that you're applying it before the next edit. The user reads the announcement as your contract.

Or decline first: if you don't see a chain pointing at Interpreter, name the decline type — no chain, taste call, cost-benefit, constraint-blocked, or insufficient context.

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:
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:
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.

Triggered by: Primitive Obsession (smells), Repeated Switches (smells), Replace Conditional with Polymorphism (refactorings)

Install via CLI
npx skills add https://github.com/wallacedrew/ritl --skill interpreter
Repository Details
star Stars 0
call_split Forks 0
navigation Branch main
article Path SKILL.md
More from Creator