Skip to content

// Design Patterns · 15

Interpreter

Rules as textleads toExpression tree you evaluate

Represent a small language as a tree of objects that evaluate.

How it fits together

Interpreter structure«client»DiscountEngineApplies(order)«record»RuleContexttotal, countryRuleParserParse(text)Comparefield, op, value«interface»RuleExpressionEvaluate(context)Andleft, rightalso Or, Notrule textbuilds treeEvaluate(ctx)childrenreads
uses / callscreatesimplementswraps / holds
  • DiscountEngine uses / calls RuleParser (rule text)
  • RuleParser creates RuleExpression (builds tree)
  • DiscountEngine uses / calls RuleExpression (Evaluate(ctx))
  • DiscountEngine creates RuleContext
  • Compare implements RuleExpression
  • And implements RuleExpression
  • And wraps / holds RuleExpression (children)
  • Compare uses / calls RuleContext (reads)

Spot it

  • Rules stored as strings and parsed ad hoc
  • A growing set of combinable conditions
  • Same rule language evaluated in many places

It costs

Fine for small grammars; big ones get slow and hard to maintain.

In code

public sealed record RuleContext(decimal Total, string Country);​public interface IRuleExpression{    bool Evaluate(RuleContext context);}​public sealed record Compare(string Field, string Op, string Value) : IRuleExpression{    public bool Evaluate(RuleContext context) => (Field, Op) switch    {        ("total", ">") => context.Total > decimal.Parse(Value, CultureInfo.InvariantCulture),        ("country", "=") => context.Country == Value,        _ => throw new NotSupportedException($"Unknown rule: {Field} {Op}"),    };}​public sealed record And(IRuleExpression Left, IRuleExpression Right) : IRuleExpression{    public bool Evaluate(RuleContext context) =>        Left.Evaluate(context) && Right.Evaluate(context);}
Go deeperThe full explanation and what to ask for · 1 min readExpand

Interpreter models each grammar rule as a class. Terminal expressions, such as a comparison or a literal, evaluate directly; non-terminal ones, such as and, or and not, hold child expressions and combine their results. A parser turns text into this tree once, and evaluation walks it against a context.

It suits small, stable languages: filter rules, feature flag conditions, simple formulas. For anything bigger, reach for a parser generator or an existing expression or rules engine instead of growing hand-written classes, and never run untrusted rule text as real code.

The expression tree is a Composite, and extra operations over it, such as printing or validating, are a job for Visitor. Many platforms build expression trees that can be compiled or translated to SQL: the same idea, with a compiler in place of the interpreter.

Ask for it

  • grammar
  • abstract syntax tree
  • terminal expression
  • non-terminal expression
  • context
  • parser

Discount rules like "total > 100 and country = DE" are checked with string matching. Define a RuleExpression interface with Evaluate(context), terminal Compare nodes and And/Or/Not nodes, plus a small RuleParser that builds the tree once. Add tests for precedence and unknown fields.