google-site-verification: google61fe8ba583a51912.html
Interpreter Pattern for DSL in Java

Overview

The Interpreter pattern is used to define a grammatical representation for a language and an interpreter to interpret sentences in that language. It's particularly useful for implementing Domain Specific Languages (DSLs).

Basic Structure

1. Basic Expression Interface and Implementations

import java.util.Map; // Abstract Expression interface Expression { int interpret(Map<String, Integer> context); } // Terminal Expressions class Number implements Expression { private int number; public Number(int number) { this.number = number; } @Override public int interpret(Map<String, Integer> context) { return number; } } class Variable implements Expression { private String name; public Variable(String name) { this.name = name; } @Override public int interpret(Map<String, Integer> context) { return context.getOrDefault(name, 0); } } // Non-terminal Expressions class Add implements Expression { private Expression left; private Expression right; public Add(Expression left, Expression right) { this.left = left; this.right = right; } @Override public int interpret(Map<String, Integer> context) { return left.interpret(context) + right.interpret(context); } } class Subtract implements Expression { private Expression left; private Expression right; public Subtract(Expression left, Expression right) { this.left = left; this.right = right; } @Override public int interpret(Map<String, Integer> context) { return left.interpret(context) - right.interpret(context); } } class Multiply implements Expression { private Expression left; private Expression right; public Multiply(Expression left, Expression right) { this.left = left; this.right = right; } @Override public int interpret(Map<String, Integer> context) { return left.interpret(context) * right.interpret(context); } } class Divide implements Expression { private Expression left; private Expression right; public Divide(Expression left, Expression right) { this.left = left; this.right = right; } @Override public int interpret(Map<String, Integer> context) { int divisor = right.interpret(context); if (divisor == 0) throw new ArithmeticException("Division by zero"); return left.interpret(context) / divisor; } }

2. Basic Usage Example

import java.util.HashMap; import java.util.Map; public class MathInterpreterExample { public static void main(String[] args) { // Create context with variables Map<String, Integer> context = new HashMap<>(); context.put("x", 10); context.put("y", 5); context.put("z", 2); // Build expression: (x + y) * z Expression expression = new Multiply( new Add(new Variable("x"), new Variable("y")), new Variable("z") ); int result = expression.interpret(context); System.out.println("(x + y) * z = " + result); // Output: (10 + 5) * 2 = 30 // More complex expression: (x * y) - (z / 2) Expression complexExpr = new Subtract( new Multiply(new Variable("x"), new Variable("y")), new Divide(new Variable("z"), new Number(2)) ); result = complexExpr.interpret(context); System.out.println("(x * y) - (z / 2) = " + result); // Output: (10 * 5) - (2 / 2) = 49 } }

Advanced DSL Examples

3. SQL-like Query DSL

import java.util.List; import java.util.ArrayList; import java.util.stream.Collectors; // Expression interface for SQL conditions interface SQLExpression { String interpret(); } // Terminal expressions class Column implements SQLExpression { private String name; public Column(String name) { this.name = name; } @Override public String interpret() { return name; } } class Value implements SQLExpression { private Object value; public Value(Object value) { this.value = value; } @Override public String interpret() { if (value instanceof String) { return "'" + value + "'"; } return value.toString(); } } // Non-terminal expressions class Equals implements SQLExpression { private SQLExpression left; private SQLExpression right; public Equals(SQLExpression left, SQLExpression right) { this.left = left; this.right = right; } @Override public String interpret() { return left.interpret() + " = " + right.interpret(); } } class GreaterThan implements SQLExpression { private SQLExpression left; private SQLExpression right; public GreaterThan(SQLExpression left, SQLExpression right) { this.left = left; this.right = right; } @Override public String interpret() { return left.interpret() + " > " + right.interpret(); } } class And implements SQLExpression { private List<SQLExpression> expressions; public And(SQLExpression... expressions) { this.expressions = List.of(expressions); } @Override public String interpret() { return expressions.stream() .map(SQLExpression::interpret) .collect(Collectors.joining(" AND ")); } } class Or implements SQLExpression { private List<SQLExpression> expressions; public Or(SQLExpression... expressions) { this.expressions = List.of(expressions); } @Override public String interpret() { return expressions.stream() .map(SQLExpression::interpret) .collect(Collectors.joining(" OR ")); } } // Query builder class Query { private String table; private SQLExpression where; public Query from(String table) { this.table = table; return this; } public Query where(SQLExpression condition) { this.where = condition; return this; } public String build() { StringBuilder sql = new StringBuilder("SELECT * FROM " + table); if (where != null) { sql.append(" WHERE ").append(where.interpret()); } return sql.toString(); } } // Usage public class SQLDSLExample { public static void main(String[] args) { // Build query: SELECT * FROM users WHERE age > 18 AND (country = 'US' OR country = 'CA') Query query = new Query() .from("users") .where( new And( new GreaterThan(new Column("age"), new Value(18)), new Or( new Equals(new Column("country"), new Value("US")), new Equals(new Column("country"), new Value("CA")) ) ) ); String sql = query.build(); System.out.println("Generated SQL: " + sql); // Output: SELECT * FROM users WHERE age > 18 AND (country = 'US' OR country = 'CA') } }

4. Business Rule DSL

import java.time.LocalDate; import java.util.function.Predicate; // Expression interface for business rules interface RuleExpression { boolean evaluate(RuleContext context); } // Context class class RuleContext { private LocalDate currentDate; private double amount; private String customerType; private int loyaltyPoints; // Constructor, getters, and setters public RuleContext(LocalDate currentDate, double amount, String customerType, int loyaltyPoints) { this.currentDate = currentDate; this.amount = amount; this.customerType = customerType; this.loyaltyPoints = loyaltyPoints; } public LocalDate getCurrentDate() { return currentDate; } public double getAmount() { return amount; } public String getCustomerType() { return customerType; } public int getLoyaltyPoints() { return loyaltyPoints; } } // Terminal expressions class AmountGreaterThan implements RuleExpression { private double threshold; public AmountGreaterThan(double threshold) { this.threshold = threshold; } @Override public boolean evaluate(RuleContext context) { return context.getAmount() > threshold; } } class CustomerTypeEquals implements RuleExpression { private String expectedType; public CustomerTypeEquals(String expectedType) { this.expectedType = expectedType; } @Override public boolean evaluate(RuleContext context) { return expectedType.equals(context.getCustomerType()); } } class LoyaltyPointsGreaterThan implements RuleExpression { private int threshold; public LoyaltyPointsGreaterThan(int threshold) { this.threshold = threshold; } @Override public boolean evaluate(RuleContext context) { return context.getLoyaltyPoints() > threshold; } } // Non-terminal expressions class AndRule implements RuleExpression { private RuleExpression left; private RuleExpression right; public AndRule(RuleExpression left, RuleExpression right) { this.left = left; this.right = right; } @Override public boolean evaluate(RuleContext context) { return left.evaluate(context) && right.evaluate(context); } } class OrRule implements RuleExpression { private RuleExpression left; private RuleExpression right; public OrRule(RuleExpression left, RuleExpression right) { this.left = left; this.right = right; } @Override public boolean evaluate(RuleContext context) { return left.evaluate(context) || right.evaluate(context); } } class NotRule implements RuleExpression { private RuleExpression expression; public NotRule(RuleExpression expression) { this.expression = expression; } @Override public boolean evaluate(RuleContext context) { return !expression.evaluate(context); } } // Rule builder for fluent DSL class RuleBuilder { public static RuleExpression amountGreaterThan(double threshold) { return new AmountGreaterThan(threshold); } public static RuleExpression customerType(String type) { return new CustomerTypeEquals(type); } public static RuleExpression loyaltyPointsGreaterThan(int points) { return new LoyaltyPointsGreaterThan(points); } public static RuleExpression and(RuleExpression... rules) { RuleExpression result = rules[0]; for (int i = 1; i < rules.length; i++) { result = new AndRule(result, rules[i]); } return result; } public static RuleExpression or(RuleExpression... rules) { RuleExpression result = rules[0]; for (int i = 1; i < rules.length; i++) { result = new OrRule(result, rules[i]); } return result; } public static RuleExpression not(RuleExpression rule) { return new NotRule(rule); } } // Usage public class BusinessRuleDSL { public static void main(String[] args) { // Create a complex business rule: // (amount > 1000 AND customerType = "PREMIUM") OR loyaltyPoints > 500 RuleExpression discountRule = RuleBuilder.or( RuleBuilder.and( RuleBuilder.amountGreaterThan(1000), RuleBuilder.customerType("PREMIUM") ), RuleBuilder.loyaltyPointsGreaterThan(500) ); // Test the rule with different contexts RuleContext context1 = new RuleContext( LocalDate.now(), 1200, "PREMIUM", 300 ); RuleContext context2 = new RuleContext( LocalDate.now(), 800, "REGULAR", 600 ); RuleContext context3 = new RuleContext( LocalDate.now(), 500, "REGULAR", 200 ); System.out.println("Context 1 qualifies for discount: " + discountRule.evaluate(context1)); // true System.out.println("Context 2 qualifies for discount: " + discountRule.evaluate(context2)); // true System.out.println("Context 3 qualifies for discount: " + discountRule.evaluate(context3)); // false } }

5. JSON Path Expression DSL

import java.util.List; import java.util.Map; import java.util.ArrayList; // JSON Path Expression interface JSONPathExpression { Object evaluate(Map<String, Object> json); } // Terminal expressions class FieldAccess implements JSONPathExpression { private String fieldName; public FieldAccess(String fieldName) { this.fieldName = fieldName; } @Override public Object evaluate(Map<String, Object> json) { return json.get(fieldName); } } class ArrayIndex implements JSONPathExpression { private int index; private JSONPathExpression arrayExpression; public ArrayIndex(JSONPathExpression arrayExpression, int index) { this.arrayExpression = arrayExpression; this.index = index; } @Override @SuppressWarnings("unchecked") public Object evaluate(Map<String, Object> json) { Object array = arrayExpression.evaluate(json); if (array instanceof List) { List<Object> list = (List<Object>) array; if (index >= 0 && index < list.size()) { return list.get(index); } } return null; } } // Non-terminal expressions class Root implements JSONPathExpression { @Override public Object evaluate(Map<String, Object> json) { return json; } } class ChildAccess implements JSONPathExpression { private JSONPathExpression parent; private String childField; public ChildAccess(JSONPathExpression parent, String childField) { this.parent = parent; this.childField = childField; } @Override @SuppressWarnings("unchecked") public Object evaluate(Map<String, Object> json) { Object parentObj = parent.evaluate(json); if (parentObj instanceof Map) { Map<String, Object> parentMap = (Map<String, Object>) parentObj; return parentMap.get(childField); } return null; } } // JSON Path Builder class JSONPath { private JSONPathExpression expression; private JSONPath(JSONPathExpression expression) { this.expression = expression; } public static JSONPath root() { return new JSONPath(new Root()); } public JSONPath field(String fieldName) { return new JSONPath(new ChildAccess(this.expression, fieldName)); } public JSONPath index(int index) { return new JSONPath(new ArrayIndex(this.expression, index)); } public Object evaluate(Map<String, Object> json) { return expression.evaluate(json); } } // Usage public class JSONPathDSL { public static void main(String[] args) { // Sample JSON-like structure Map<String, Object> json = Map.of( "users", List.of( Map.of("name", "John", "age", 30), Map.of("name", "Jane", "age", 25) ), "metadata", Map.of("version", "1.0", "count", 2) ); // Build JSONPath expressions JSONPath userCountPath = JSONPath.root().field("metadata").field("count"); JSONPath firstNamePath = JSONPath.root().field("users").index(0).field("name"); JSONPath secondUserAgePath = JSONPath.root().field("users").index(1).field("age"); System.out.println("User count: " + userCountPath.evaluate(json)); // 2 System.out.println("First user name: " + firstNamePath.evaluate(json)); // John System.out.println("Second user age: " + secondUserAgePath.evaluate(json)); // 25 } }

6. Calculator DSL with Parser

import java.util.Stack; import java.util.EmptyStackException; // Enhanced Expression interface with toString interface MathExpression { int interpret(); String toString(); } // Terminal expressions class Constant implements MathExpression { private int value; public Constant(int value) { this.value = value; } @Override public int interpret() { return value; } @Override public String toString() { return String.valueOf(value); } } // Non-terminal expressions class Addition implements MathExpression { private MathExpression left; private MathExpression right; public Addition(MathExpression left, MathExpression right) { this.left = left; this.right = right; } @Override public int interpret() { return left.interpret() + right.interpret(); } @Override public String toString() { return "(" + left + " + " + right + ")"; } } class Subtraction implements MathExpression { private MathExpression left; private MathExpression right; public Subtraction(MathExpression left, MathExpression right) { this.left = left; this.right = right; } @Override public int interpret() { return left.interpret() - right.interpret(); } @Override public String toString() { return "(" + left + " - " + right + ")"; } } class Multiplication implements MathExpression { private MathExpression left; private MathExpression right; public Multiplication(MathExpression left, MathExpression right) { this.left = left; this.right = right; } @Override public int interpret() { return left.interpret() * right.interpret(); } @Override public String toString() { return "(" + left + " * " + right + ")"; } } class Division implements MathExpression { private MathExpression left; private MathExpression right; public Division(MathExpression left, MathExpression right) { this.left = left; this.right = right; } @Override public int interpret() { int divisor = right.interpret(); if (divisor == 0) throw new ArithmeticException("Division by zero"); return left.interpret() / divisor; } @Override public String toString() { return "(" + left + " / " + right + ")"; } } // Parser for mathematical expressions class MathExpressionParser { public static MathExpression parse(String expression) { String[] tokens = expression.split("\\s+"); Stack<MathExpression> stack = new Stack<>(); for (String token : tokens) { switch (token) { case "+": applyBinaryOperator(stack, Addition::new); break; case "-": applyBinaryOperator(stack, Subtraction::new); break; case "*": applyBinaryOperator(stack, Multiplication::new); break; case "/": applyBinaryOperator(stack, Division::new); break; default: try { int value = Integer.parseInt(token); stack.push(new Constant(value)); } catch (NumberFormatException e) { throw new IllegalArgumentException("Invalid token: " + token); } break; } } if (stack.size() != 1) { throw new IllegalArgumentException("Invalid expression"); } return stack.pop(); } private static void applyBinaryOperator(Stack<MathExpression> stack, BinaryOperator constructor) { try { MathExpression right = stack.pop(); MathExpression left = stack.pop(); stack.push(constructor.apply(left, right)); } catch (EmptyStackException e) { throw new IllegalArgumentException("Invalid expression - not enough operands"); } } @FunctionalInterface private interface BinaryOperator { MathExpression apply(MathExpression left, MathExpression right); } } // Calculator DSL class Calculator { public static int evaluate(String expression) { MathExpression parsed = MathExpressionParser.parse(expression); return parsed.interpret(); } public static String toExpressionString(String expression) { MathExpression parsed = MathExpressionParser.parse(expression); return parsed.toString(); } } // Usage public class CalculatorDSL { public static void main(String[] args) { // Test the calculator with postfix notation String expression1 = "3 4 + 2 *"; // (3 + 4) * 2 String expression2 = "10 5 - 3 * 2 /"; // ((10 - 5) * 3) / 2 String expression3 = "15 3 1 + /"; // 15 / (3 + 1) System.out.println("Expression: " + expression1); System.out.println("Parsed as: " + Calculator.toExpressionString(expression1)); System.out.println("Result: " + Calculator.evaluate(expression1)); System.out.println(); System.out.println("Expression: " + expression2); System.out.println("Parsed as: " + Calculator.toExpressionString(expression2)); System.out.println("Result: " + Calculator.evaluate(expression2)); System.out.println(); System.out.println("Expression: " + expression3); System.out.println("Parsed as: " + Calculator.toExpressionString(expression3)); System.out.println("Result: " + Calculator.evaluate(expression3)); } }

7. Configuration DSL

import java.util.HashMap; import java.util.Map; import java.util.function.Function; // Configuration Expression interface ConfigExpression { Object evaluate(ConfigContext context); } // Context class ConfigContext { private Map<String, Object> variables; private Map<String, Object> systemProperties; public ConfigContext() { this.variables = new HashMap<>(); this.systemProperties = new HashMap<>(); } public void setVariable(String name, Object value) { variables.put(name, value); } public Object getVariable(String name) { return variables.get(name); } public void setSystemProperty(String name, Object value) { systemProperties.put(name, value); } public Object getSystemProperty(String name) { return systemProperties.get(name); } } // Terminal expressions class VariableReference implements ConfigExpression { private String variableName; public VariableReference(String variableName) { this.variableName = variableName; } @Override public Object evaluate(ConfigContext context) { return context.getVariable(variableName); } } class SystemProperty implements ConfigExpression { private String propertyName; public SystemProperty(String propertyName) { this.propertyName = propertyName; } @Override public Object evaluate(ConfigContext context) { return context.getSystemProperty(propertyName); } } class Literal implements ConfigExpression { private Object value; public Literal(Object value) { this.value = value; } @Override public Object evaluate(ConfigContext context) { return value; } } // Non-terminal expressions class Concatenation implements ConfigExpression { private ConfigExpression left; private ConfigExpression right; public Concatenation(ConfigExpression left, ConfigExpression right) { this.left = left; this.right = right; } @Override public String evaluate(ConfigContext context) { return left.evaluate(context).toString() + right.evaluate(context).toString(); } } class Conditional implements ConfigExpression { private ConfigExpression condition; private ConfigExpression trueBranch; private ConfigExpression falseBranch; public Conditional(ConfigExpression condition, ConfigExpression trueBranch, ConfigExpression falseBranch) { this.condition = condition; this.trueBranch = trueBranch; this.falseBranch = falseBranch; } @Override public Object evaluate(ConfigContext context) { Object conditionValue = condition.evaluate(context); if (conditionValue instanceof Boolean && (Boolean) conditionValue) { return trueBranch.evaluate(context); } else { return falseBranch.evaluate(context); } } } // Configuration Builder DSL class ConfigBuilder { private ConfigContext context = new ConfigContext(); public ConfigBuilder set(String variable, Object value) { context.setVariable(variable, value); return this; } public ConfigBuilder systemProperty(String property, Object value) { context.setSystemProperty(property, value); return this; } public Object evaluate(ConfigExpression expression) { return expression.evaluate(context); } // Factory methods for expressions public static ConfigExpression var(String name) { return new VariableReference(name); } public static ConfigExpression prop(String name) { return new SystemProperty(name); } public static ConfigExpression lit(Object value) { return new Literal(value); } public static ConfigExpression concat(ConfigExpression left, ConfigExpression right) { return new Concatenation(left, right); } public static ConfigExpression when(ConfigExpression condition, ConfigExpression trueExpr, ConfigExpression falseExpr) { return new Conditional(condition, trueExpr, falseExpr); } } // Usage public class ConfigurationDSL { public static void main(String[] args) { ConfigBuilder config = new ConfigBuilder() .set("environment", "production") .set("appName", "MyApp") .set("debug", false) .systemProperty("java.version", "11") .systemProperty("os.name", "Linux"); // Build complex configuration expressions ConfigExpression dbUrl = ConfigBuilder.when( ConfigBuilder.var("environment").equals("production"), ConfigBuilder.concat( ConfigBuilder.lit("jdbc:mysql://prod-db:3306/"), ConfigBuilder.var("appName") ), ConfigBuilder.lit("jdbc:mysql://localhost:3306/dev") ); ConfigExpression logLevel = ConfigBuilder.when( ConfigBuilder.var("debug"), ConfigBuilder.lit("DEBUG"), ConfigBuilder.lit("INFO") ); System.out.println("Database URL: " + config.evaluate(dbUrl)); System.out.println("Log Level: " + config.evaluate(logLevel)); // Change context and re-evaluate config.set("environment", "development").set("debug", true); System.out.println("After context change:"); System.out.println("Database URL: " + config.evaluate(dbUrl)); System.out.println("Log Level: " + config.evaluate(logLevel)); } // Helper method for equality comparison (simplified) private static ConfigExpression equals(ConfigExpression left, Object right) { return new ConfigExpression() { @Override public Object evaluate(ConfigContext context) { return left.evaluate(context).equals(right); } }; } }

Best Practices and Patterns

8. Visitor Pattern for Expression Evaluation

import java.util.Map; import java.util.HashMap; // Expression interface with visitor support interface VisitableExpression { int accept(ExpressionVisitor visitor); } interface ExpressionVisitor { int visit(Number number); int visit(Variable variable); int visit(Add add); int visit(Subtract subtract); int visit(Multiply multiply); int visit(Divide divide); } // Updated expressions with visitor support class Number implements VisitableExpression { private int value; public Number(int value) { this.value = value; } public int getValue() { return value; } @Override public int accept(ExpressionVisitor visitor) { return visitor.visit(this); } } class Variable implements VisitableExpression { private String name; public Variable(String name) { this.name = name; } public String getName() { return name; } @Override public int accept(ExpressionVisitor visitor) { return visitor.visit(this); } } class Add implements VisitableExpression { private VisitableExpression left, right; public Add(VisitableExpression left, VisitableExpression right) { this.left = left; this.right = right; } public VisitableExpression getLeft() { return left; } public VisitableExpression getRight() { return right; } @Override public int accept(ExpressionVisitor visitor) { return visitor.visit(this); } } // Implement other operations similarly... // Evaluation Visitor class EvaluationVisitor implements ExpressionVisitor { private Map<String, Integer> context; public EvaluationVisitor(Map<String, Integer> context) { this.context = context; } @Override public int visit(Number number) { return number.getValue(); } @Override public int visit(Variable variable) { return context.getOrDefault(variable.getName(), 0); } @Override public int visit(Add add) { return add.getLeft().accept(this) + add.getRight().accept(this); } @Override public int visit(Subtract subtract) { return subtract.getLeft().accept(this) - subtract.getRight().accept(this); } @Override public int visit(Multiply multiply) { return multiply.getLeft().accept(this) * multiply.getRight().accept(this); } @Override public int visit(Divide divide) { int divisor = divide.getRight().accept(this); if (divisor == 0) throw new ArithmeticException("Division by zero"); return divide.getLeft().accept(this) / divisor; } } // Usage with Visitor public class VisitorPatternExample { public static void main(String[] args) { Map<String, Integer> context = new HashMap<>(); context.put("x", 10); context.put("y", 5); VisitableExpression expression = new Add( new Multiply(new Variable("x"), new Number(2)), new Variable("y") ); EvaluationVisitor visitor = new EvaluationVisitor(context); int result = expression.accept(visitor); System.out.println("Result: " + result); // Output: (10 * 2) + 5 = 25 } }

Key Benefits of Interpreter Pattern for DSL

  1. Separation of Concerns: Grammar rules are separated from interpretation logic
  2. Extensibility: Easy to add new expressions and operations
  3. Maintainability: Clear structure makes the code easy to understand and modify
  4. Type Safety: Compile-time checking of expression structure
  5. Flexibility: Can easily combine simple expressions into complex ones

Common Use Cases

  • Mathematical expression evaluation
  • Query language implementation (SQL, OQL)
  • Business rule engines
  • Configuration file parsers
  • Template engines
  • Regular expression engines

The Interpreter pattern is particularly powerful when combined with other patterns like Visitor for complex operations, and when building fluent DSLs that are both expressive and type-safe.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper