Define a grammar for a language and provide an interpreter to evaluate sentences in that language.
Interpreter defines a representation for a language's grammar using a class hierarchy, and provides an interpreter that uses the representation to interpret sentences in that language. Each grammar rule becomes a class; evaluating an expression means calling interpret() on the tree of rule objects.
It is most useful for simple, domain-specific languages (DSLs) — mathematical expressions, query languages, configuration rule engines, boolean filters.
3 + 5 * 2 is parsed into a tree: Add(3, Multiply(5, 2)). Each node is an Interpreter class. Calling interpret() on the root evaluates the whole expression recursively.You need to evaluate boolean search expressions like java AND (spring OR hibernate) NOT legacy. The grammar is recursive — expressions can be nested. A single massive if-else parser becomes unmaintainable when the grammar grows.
Interpreter maps each grammar rule to a class: AndExpression, OrExpression, NotExpression, TerminalExpression. The expression tree is built from these, and interpret(context) evaluates it naturally.
interpret(context) method that all concrete expressions implement.interpret() on its children and combines results.interpret(context) on the root.// AbstractExpression public interface Expression { boolean interpret(String context); } // TerminalExpression — leaf node, checks if word is in context public class TerminalExpression implements Expression { private final String word; public TerminalExpression(String word) { this.word = word; } public boolean interpret(String ctx) { return ctx.contains(word); // base case } } // NonterminalExpression — AND public class AndExpression implements Expression { private final Expression left, right; public AndExpression(Expression l, Expression r) { left=l; right=r; } public boolean interpret(String ctx) { return left.interpret(ctx) && right.interpret(ctx); } } // NonterminalExpression — OR public class OrExpression implements Expression { private final Expression left, right; public OrExpression(Expression l, Expression r) { left=l; right=r; } public boolean interpret(String ctx) { return left.interpret(ctx) || right.interpret(ctx); } } // NonterminalExpression — NOT public class NotExpression implements Expression { private final Expression expr; public NotExpression(Expression e) { expr = e; } public boolean interpret(String ctx) { return !expr.interpret(ctx); } } // Client builds AST and evaluates public class Main { public static void main(String[] args) { // Build: "java AND (spring OR hibernate) AND NOT legacy" Expression java = new TerminalExpression("java"); Expression spring = new TerminalExpression("spring"); Expression hibernate = new TerminalExpression("hibernate"); Expression legacy = new TerminalExpression("legacy"); Expression query = new AndExpression(java, new AndExpression( new OrExpression(spring, hibernate), new NotExpression(legacy))); System.out.println(query.interpret("java spring microservices")); // true System.out.println(query.interpret("java legacy code")); // false (has legacy) System.out.println(query.interpret("python django")); // false (no java) } }
java.util.regex.Pattern — regex grammar interpreted as an expression tree internally#{order.price > 100} evaluated via interpreterinterpret() calls on a deeply nested expression tree exhaust the call stacknew AndExpression(left, null)), interpret() throws NPE deep in recursion with no useful stack traceObjects.requireNonNull()interpret() with an explicit evaluation stack (Deque) for grammars that may produce deep trees// ❌ BREAKING — null child expression Expression bad = new AndExpression(java, null); bad.interpret("java spring"); // NPE inside AndExpression.interpret() // ✅ FIX — null-guard in constructor public AndExpression(Expression l, Expression r) { this.left = Objects.requireNonNull(l, "left expression required"); this.right = Objects.requireNonNull(r, "right expression required"); } // ❌ BREAKING — no depth limit, malicious input → StackOverflow public boolean interpret(String ctx) { return left.interpret(ctx) && right.interpret(ctx); // unlimited depth } // ✅ FIX — depth-aware context public class Context { private int depth = 0; private static final int MAX_DEPTH = 100; public void enterExpression() { if (++depth > MAX_DEPTH) throw new IllegalStateException("Expression too deeply nested"); } public void exitExpression() { depth--; } } public boolean interpret(Context ctx) { ctx.enterExpression(); try { return left.interpret(ctx) && right.interpret(ctx); } finally { ctx.exitExpression(); } }
interpret(context) on the root to evaluate the expression recursively.interpret(ctx). TerminalExpression = leaf (base case). NonterminalExpression = composite (delegates to children). Client builds tree, calls root.interpret().