Skip to content

Interpreter ​

Interpreter defines a small language and evaluates expressions in that language. It is useful when users save rules or filters that must be applied repeatedly. It is usually excessive for one hard-coded condition.

iOS problem and example ​

A task app saves filters such as “flagged AND due soon.” Model the grammar as Swift values rather than evaluating a raw string.

swift
import Foundation

struct TaskItem {
    let isFlagged: Bool
    let daysUntilDue: Int
}

indirect enum FilterExpression {
    case flagged
    case dueWithin(Int)
    case and(FilterExpression, FilterExpression)
    case or(FilterExpression, FilterExpression)

    func matches(_ item: TaskItem) -> Bool {
        switch self {
        case .flagged:
            return item.isFlagged
        case .dueWithin(let days):
            return item.daysUntilDue <= days
        case .and(let left, let right):
            return left.matches(item) && right.matches(item)
        case .or(let left, let right):
            return left.matches(item) || right.matches(item)
        }
    }
}

let filter = FilterExpression.and(.flagged, .dueWithin(3))
let visible = filter.matches(TaskItem(isFlagged: true, daysUntilDue: 2))

The enum is an abstract syntax tree. The recursive matches method is its interpreter. A filter editor can build this tree from controlled UI choices and persist a Codable representation.

Production decisions ​

Define what “due within” means for overdue tasks and time zones. If you accept typed text, write a parser that validates tokens and limits nesting; never turn user text into executable Swift or SQL. For large task sets, translate the expression into a database predicate instead of evaluating every row in memory.

Read the expression tree ​

FilterExpression is a tiny grammar. Its terminal nodes are flagged and dueWithin; and and or combine smaller expressions into larger ones. The enum is indirect because composite cases contain more FilterExpression values. matches(_:) recursively evaluates the tree for one task, short-circuiting through Swift's Boolean operators. This is safer and easier to test than interpreting arbitrary text or assembling string predicates.

From example to an iOS filter editor ​

A filter editor can present controlled chips such as Flagged, Due within 3 days, and Match all/any. It turns those selections into an expression tree, stores a versioned representation, and evaluates it when the task list changes. Define whether overdue tasks count as due soon and compute dates in the user's calendar and time zone. For a large data set, translate the same validated tree into a database query so every page uses one consistent filter.

Failure modes and verification ​

Test nested and and or, empty task sets, boundary day counts, and persisted expression migration. If users type a query language, parser errors must be shown clearly and recursion depth must be limited. An Interpreter is justified when expressions are user-configurable and composable; one fixed filter is simply a predicate.

All patterns

Released under the MIT License.