tl  tr
  Home | Tutorials | Articles | Videos | Products | Tools | Search
Interviews | Open Source | Tag Cloud | Follow Us | Bookmark | Contact   
 Design Patterns > Java Design Patterns > Interpreter

Interpreter 

Interpreter pattern comes under Behavioral design pattern. Its useful to interpret the statements of a language. It follows grammar rules and can be extended easily.

Behaviour & Advantages

  • Interprets the statements of a language as per grammar rules.
  • Can interpret and extend custom programming languages.

In this example we show a simple interpreted programming language. The grammar for this language is shown below,

 prog = [stmt]+
 stmt = print_stmt | int_assign_stmt
 int_assign_stmt = int_keyword variable = [number|variable|expression]
 print_stmt = print_keyword [ string | variable ]
 
 number = ['0'-'9']+
 variable = ['a'-'z' 'A'-'Z']+
 expression = number |  variable | expression + expression
 string = "[<any char>]*"

The input program for our interpreter is shown below,

File Name  :  
source/com/bethecoder/tutorials/dp/interpreter/prog.txt 
print "Script name ($self) : "
println $self

print "Number of arguments ($argc) : "
println $argc

print "First argument ($arg0) : "
println $arg0

print "Second argument ($arg1) : "
println $arg1

print "Third argument ($arg2) : "
println $arg2
println

int abc = 1
int def = 9999
int pqr = abc
int xyz = abc + def + pqr + 98 + $arg1

print "Value of 'abc' : "
println abc

print "Value of 'def' : "
println def

print "Value of 'pqr' : "
println pqr

print "Sum of (abc + def + pqr + 98 + $arg1) :"
println xyz

Each piece of programming language is considered interpretable and therefore implements Interpretable interface.

File Name  :  
com/bethecoder/tutorials/dp/interpreter/Interpretable.java 
   
package com.bethecoder.tutorials.dp.interpreter;

import java.util.Map;

/**
 * int a = 5
 * int b = 9999
 * int c = a + b
 * print "Sum :"
 * print c
 
 * Grammer
 
 * prog = [stmt]+
 * stmt = print_stmt | int_assign_stmt
 * int_assign_stmt = int_keyword variable = [number|variable|expression]
 * print_stmt = print_keyword [string | variable ]
 
 * number = ['0'-'9']+
 * variable = ['a'-'z' 'A'-'Z']+
 * expression = number |  variable | expression + expression
 * string = "[<any char>]*"
 *
 */
public interface Interpretable<T> {

  /**
   * Interpret the program element.
   
   @param programContext
   @return
   */
  public T interpret(Map<String, Interpretable<?>> programContext);

}
   

Here program is considered as a list of statements each of which is either a print statement or int assignment statement. It treats a statement starting with '#' char as comment line.

File Name  :  
com/bethecoder/tutorials/dp/interpreter/Program.java 
   
package com.bethecoder.tutorials.dp.interpreter;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;

public class Program implements Interpretable<Integer> {

  private String programFile;
  
  public Program(String programFile) {
    this.programFile = programFile;
  }
  
  public void run() {
    run(new String [] {});
  }

  public void run(String [] args) {
    /**
     * Create initial program context with user provided arguments. 
     */
    Map<String, Interpretable<?>> programContext = new HashMap<String, Interpretable<?>>();
    programContext.put("$self"new StringExpression(programFile));
    programContext.put("$argc"new NumExpression(args.length));
  
    /**
     * Assign appropriate type for args
     */
    NumExpression num = null;
    
    for (int i = ; i < args.length ; i ++) {
      num = new NumExpression(args[i]);
      
      if (num.isValidNumber()) {
        programContext.put("$arg" + i, num);
      else {
        programContext.put("$arg" + i, new StringExpression(args[i]));
      }
    }
    
    interpret(programContext);
  }
  
  @Override
  public Integer interpret(Map<String, Interpretable<?>> programContext) {
    
    try {
      BufferedReader br = new BufferedReader(
          new InputStreamReader(this.getClass().getResourceAsStream(programFile)));
      
      String statement = null;
      PrintStatement printStatement = null;
      IntAssignmentStatement intAssignmentStatement = null;
      
      while ((statement = br.readLine()) != null) {
        statement = statement.trim();
        
        //Ignore empty and comment lines.
        if (statement.length() == || 
            statement.startsWith("#")) {
          continue;
        }
        
        //System.out.println(statement);
        //Check whether this is print statement
        printStatement = new PrintStatement(statement);
        
        if (printStatement.isPrintStatement()) {
          printStatement.interpret(programContext);
          continue;
        }
        
        //Check whether this is int assignment statement
        intAssignmentStatement = new IntAssignmentStatement(statement);
        if (intAssignmentStatement.isIntAssignmentStatement()) {
          intAssignmentStatement.interpret(programContext);
          continue;
        }
        
        System.out.println("Invalid statement : " + statement);
      }
      
    catch (FileNotFoundException e) {
      e.printStackTrace();
    catch (IOException e) {
      e.printStackTrace();
    }
    
    return 0;
  }

  public String getProgramFile() {
    return programFile;
  }

  public void setProgramFile(String programFile) {
    this.programFile = programFile;
  }

}
   

This class represents a number.

File Name  :  
com/bethecoder/tutorials/dp/interpreter/NumExpression.java 
   
package com.bethecoder.tutorials.dp.interpreter;

import java.util.Map;

public class NumExpression implements Interpretable<Integer> {

  private static final String NUMS = "0123456789";
  private String value;

  public NumExpression(int value) {
    this(String.valueOf(value));
  }

  public NumExpression(String value) {
    super();
    this.value = value;
  }
  
  @Override
  public Integer interpret(Map<String, Interpretable<?>> programContext) {
    return Integer.parseInt(value);
  }

  public boolean isValidNumber() {

    for (int i = ; i < value.length() ; i ++) {
      if (NUMS.indexOf(value.charAt(i)) == -1) {
        return false;
      }
    }
    
    return true;
  }

}
   

This class represents a string.

File Name  :  
com/bethecoder/tutorials/dp/interpreter/StringExpression.java 
   
package com.bethecoder.tutorials.dp.interpreter;

import java.util.Map;

public class StringExpression implements Interpretable<String> {

  private String value;

  public StringExpression(String value) {
    super();
    this.value = value;
  }
  
  @Override
  public String interpret(Map<String, Interpretable<?>> programContext) {
    return value;
  }
}
   

This class prints either number or string to console.

File Name  :  
com/bethecoder/tutorials/dp/interpreter/PrintStatement.java 
   
package com.bethecoder.tutorials.dp.interpreter;

import java.util.Map;

public class PrintStatement implements Interpretable<Void> {

  private String statement;
  private boolean newLine;
  
  public PrintStatement(String statement) {
    super();
    this.statement = statement;
  }

  @Override
  public Void interpret(Map<String, Interpretable<?>> programContext) {
    
    int index = statement.indexOf(" ");
    
    if (index > 0) {
      String expression = statement.substring(index+1).trim();
      String value = expression;
      
      if (expression.startsWith("\""&& expression.endsWith("\"")) {
        value = expression.substring(1, expression.length() -1);
      else if (programContext.containsKey(expression)) {
        value = programContext.get(expression).interpret(programContext).toString();
      }
      
      System.out.print(value);
    else {
      //No expression, just print empty
      System.out.print("");
    }
    
    if (newLine) {
      System.out.println();
    }
    
    return null;
  }
  
  public boolean isPrintStatement() {
    if (statement != null && statement.startsWith("print")) {
      if (statement.startsWith("println")) {
        this.newLine = true;
      }
      return true;
    }
    
    return false;
  }
}
   

This class creates a number variable in the program context.

File Name  :  
com/bethecoder/tutorials/dp/interpreter/IntAssignmentStatement.java 
   
package com.bethecoder.tutorials.dp.interpreter;

import java.util.Map;

public class IntAssignmentStatement implements Interpretable<Void> {

  private String statement;
  
  public IntAssignmentStatement(String statement) {
    super();
    this.statement = statement;
  }

  @Override
  public Void interpret(Map<String, Interpretable<?>> programContext) {
    
    int index = statement.indexOf(" ");
    /**
     * Ex: int c = 100
     * Ex: int c = a + b
     */
    if (index > 0) {
      String expression = statement.substring(index+1).trim();
      index = expression.indexOf("=");
      
      String lValue = expression.substring(0, index).trim();
      String rValue = expression.substring(index+1).trim();
      programContext.put(lValue, new Expression(rValue));
    
    
    return null;
  }
  
  public boolean isIntAssignmentStatement() {
    if (statement != null && statement.startsWith("int")) {
      return true;
    }
    
    return false;
  }
}
   

Expression can be a number, variable or sum of two expressions.

File Name  :  
com/bethecoder/tutorials/dp/interpreter/Expression.java 
   
package com.bethecoder.tutorials.dp.interpreter;

import java.util.Map;

public class Expression implements Interpretable<Integer> {

  private String expression;

  public Expression(String expression) {
    super();
    this.expression = expression;
  }

  @Override
  public Integer interpret(Map<String, Interpretable<?>> programContext) {
    /**
     * The only operation currently supported is 
     * Sum(+) for simplicity.
     
     *  Ex: a+b
     *  Ex: a+b+100
     */
    int index = expression.indexOf("+");

    //Split and evaluate expression
    if (index > 0) {
      String lValue = expression.substring(0, index).trim();
      String rValue = expression.substring(index+1).trim();
      
      int lNum = new Expression(lValue).interpret(programContext);
      int rNum = new Expression(rValue).interpret(programContext);
      return lNum + rNum;
    
    
    //Expression can be either number or a variable
    NumExpression num = new NumExpression(expression);
    
    if (num.isValidNumber()) {
      return num.interpret(programContext);
    else if (programContext.containsKey(expression)) {
      Object value = programContext.get(expression).interpret(programContext);
      if (value instanceof Integer) {
        return (Integervalue;
      else {
        throw new IllegalArgumentException(
            "Evaluating expression '" + expression + "' : " + value);
      }
    }
    
    return 0;
  }

}
   

Interpreter usage is shown below,

File Name  :  
com/bethecoder/tutorials/dp/interpreter/Test.java 
   
package com.bethecoder.tutorials.dp.interpreter;

public class Test {

  /**
   @param args
   */
  public static void main(String[] args) {
    Program program = new Program("prog.txt");
    program.run(new String [] { "ONE""2""THREE"});
  }
}
   

It gives the following output,
Script name ($self) : prog.txt
Number of arguments ($argc) : 3
First argument ($arg0) : ONE
Second argument ($arg1) : 2
Third argument ($arg2) : THREE

Value of 'abc' : 1
Value of 'def' : 9999
Value of 'pqr' : 1
Sum of (abc + def + pqr + 98 + $arg1) :10101



 
  


  
bl  br