One Day Mode - Interpreter Mode

Keywords: Java

1. The concept of interpreter mode

Given a language, define a representation of grammar and define an interpreter that uses the representation to interpret sentences in the language.

2. When to use interpreter mode

Interpreter mode is the only design mode that handles grammar parsing among the 23 design modes.

When you need to be realistic, you need to interpret a grammar to perform your business, and you can represent sentences in that language as an abstract grammar tree, you can use the interpreter mode.

For the example mentioned in the section "Illustration Design Mode" in the Interpreter Mode, use the BNF-style description grammar, for example, to move the car through a grammar:

<program> ::= program <command list>
<command list> ::= <command>* end
<command> ::= <repeat command> | <primitive command>
<repeat command> ::= repeat <number> <command list>
<primitive command> ::= go | right | left

This grammar can be made into an abstract grammar tree, for example, to control the walking of a car, you can enter the grammar: program repeat 3 go right end.

It means to repeat go and right three times.

Here's how to implement the interpreter mode using a Java program.

3. How to use interpreter mode

3.1 Implementation

// Abstract Class of Grammar Tree Node
public abstract class Node {

    abstract void parse(Context context);

}

// <program> ::= program <command list>
public class ProgramNode extends Node {

    private Node commandListNode;

    void parse(Context context) {
        // Parser command, must be program to execute down
        // Equal to skipping program from command
        context.skipToken("program");
        // Deliver the following commands to <command list>for parsing
        commandListNode = new CommandListNode();
        commandListNode.parse(context);
    }

    public String toString() {
        return "[program " + commandListNode + "]";
    }

}

// <command list> ::= <command>* end
public class CommandListNode extends Node {

    ArrayList list = new ArrayList();

    void parse(Context context) {
        // Commands are made up of <command>* end, so loop through the * until the end
        while (true) {
            if (context.currentToken() == null) { // Missing end error
                throw new IllegalArgumentException("missing end");
            } else if (context.currentToken().equals("end")) { // Parse end, jump out of loop
                context.skipToken("end");
                break;
            } else { // Give command to <command>parse
                Node commandNode = new CommandNode();
                commandNode.parse(context);
                // Easy to print results defines a collection store command
                list.add(commandNode);
            }
        }
    }

    public String toString() {
        return list.toString();
    }

}

// <command> ::= <repeat command> | <primitive command>
public class CommandNode extends Node {

    private Node node;

    void parse(Context context) {
        // Determine whether <repeat command>or <primitive command> and find the parsed object you want to correspond to
        if (context.currentToken().equals("repeat")) {
            node = new RepeatCommandNode();
            node.parse(context);
        } else {
            node = new PrimitiveCommandNode();
            node.parse(context);
        }
    }

    public String toString() {
        return node.toString();
    }

}

// <repeat command> ::= repeat <number> <command list>
public class RepeatCommandNode extends Node {

    int number;
    private Node commandListNode;

    void parse(Context context) {
        // Parse repeat, get numbers for printing, and then parse numbers as well (nextToken used)
        context.skipToken("repeat");
        number = context.currentNumber();
        context.nextToken();
        // Then give the command to <command list>
        commandListNode = new CommandListNode();
        commandListNode.parse(context);
    }

    public String toString() {
        return "[repeat +" + number + " " + commandListNode + "]";
    }

}

// <primitive command> ::= go | right | left
public class PrimitiveCommandNode extends Node {

    String name;

    void parse(Context context) {
        // Resolve this command
        name = context.currentToken();
        context.skipToken(name);
        // Error if not recognized
        if (!name.equals("go") && !name.equals("right") && !name.equals("left")) {
            throw new IllegalArgumentException("token is undefined.");
        }
    }

    public String toString() {
        return name;
    }

}

public class Context {

    StringTokenizer tokenizer;
    String currentToken;

    public Context(String text) {
        tokenizer = new StringTokenizer(text);
        nextToken();
    }

    public String nextToken() {
        if (tokenizer.hasMoreTokens()) {
            currentToken = tokenizer.nextToken();
        } else {
            currentToken = null;
        }
        return currentToken;
    }

    public String currentToken() {
        return currentToken;
    }

    public void skipToken(String token) {
        if (!token.equals(currentToken)) {
            throw new IllegalArgumentException("token is expected, but currentToken is found.");
        }
        nextToken();
    }

    public int currentNumber() {
        int number = 0;
        try {
            number = Integer.parseInt(currentToken);
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("number is bad param.");
        }
        return number;
    }

}

Use it as follows:

public class Client {

    public static void main(String[] args) {
        String command5 = "program repeat 4 repeat 3 go right go right end right end end";
        Node node = new ProgramNode();
        node.parse(new Context(command5));
        System.out.println(node);
    }

}

// output
// [program [[repeat +4 [[repeat +3 [go, right, go, right]], right]]]]

Benefits of 3.2 Interpreter Mode

  • The method can be easily changed and extended because the pattern uses classes to represent method rules, and you can use inheritance to change or extend the method.
  • It is also easier to implement because classes that define the total nodes of an abstract grammar tree are generally implemented similarly, and they are easy to write directly.
  • The interpreter mode is simply to translate a sentence into the actual command program execution.It can also be analyzed without the Interpreter pattern itself, but by inheriting Abstract expressions, grammar expansion and maintenance are facilitated by relying on the principle of transpose.

3.3 Notes on Interpreter Mode

Interpreter mode defines at least one class for each rule in a method, so methods containing many rules may be difficult to manage and maintain.So when the method is very complex, use other techniques such as the parser or compiler generator to handle it.

4. Summary

The key to the Interpreter pattern is how to abstract the requirements into a grammar tree.

Its business implementation is simple, using classes to parse each command.

Interpreter mode is rarely used in actual system development because it can cause problems such as efficiency, performance and maintenance. Generally, it can be found in large and medium-sized framework projects, such as some data analysis tools, report design tools, scientific computing tools, and so on.

These are just some of my understandings of the interpreter model. There are some deficiencies. Please point out that thank you.

Posted by amitdubey2 on Thu, 11 Jun 2020 09:54:50 -0700