線上實習|“問答模組”前端作品評審「吾研第二期」
一、定義
直譯器模式(Interpreter Pattern) 是指給定一門語言, 定義它的語法的一種表示, 並定義一個直譯器,該直譯器使用該表示來解釋語言中的句子。是一種按照規定的語法進行解析的模式,屬於行為型模式。就比如編譯器可以將原始碼編譯解釋為機器碼, 讓CPU能進行識別並執行。直譯器模式的作用其實與編譯器一樣,都是將一些固定的語法進行解釋,構建出一個解釋句子的直譯器。簡單理解,直譯器是一個簡單語法分析工具,它可以識別句子語義,分離終結符號和非終結符號,提取出需要的資訊,讓我們能針對不同的資訊做出相應的處理。其核心思想是識別語法,構建解釋。
直譯器模式主要包含四種角色:
- 抽象表示式(Expression) :負責定義一個解釋方法interpret, 交由具體子類進行具體解釋;
- 終結符表示式(Terminal Expression) :實現文法中與終結符有關的解釋操作。文法中的每一個終結符都有一個具體終結表示式與之相對應,比如公式R=R1+R2,R1和R2就是終結符,對應的解析R1和R2的直譯器就是終結符表示式。通常一個直譯器模式中只有一個終結符表示式,但有多個例項,對應不同的終結符(R1,R2);
- 非終結符表示式(Nonterminal Expression) :實現文法中與非終結符有關的解釋操作。文法中的每條規則都對應於一個非終結符表示式。非終結符表示式一般是文法中的運算子或者其他關鍵字,比如公式R=R1+R2中, “+”就是非終結符,解析“+”的直譯器就是一個非終結符表示式。非終結符表示式根據邏輯的複雜程度而增加,原則上每個文法規則都對應一個非終結符表示式;
- 上下文環境類(Context) :包含直譯器之外的全域性資訊。它的任務一般是用來存放文法中各個終結符所對應的具體值,比如R=R1+R2,給R1賦值100,給R2賦值200,這些資訊需要存放到環境中。
二、直譯器模式的案例
1.標準寫法
// 抽象表示式 public interface IExpression { // 對錶達式進行解釋 Object interpret(Context context); }
// 終結符表示式 public class TerminalExpression implements IExpression { private Object value;public Object interpret(Context context) { // 實現文法中與終結符有關的操作 context.put("",""); return null; } }
// 非終結符表示式 public class NonterminalExpression implements IExpression { private IExpression [] expressions; public NonterminalExpression(IExpression... expressions) { // 每個非終結符表示式都會對其他表示式產生依賴 this.expressions = expressions; } public Object interpret(Context context) { // 進行文法處理 context.put("",""); return null; } }
// 上下文環境類 public class Context extends HashMap { }
public class Test { public static void main(String[] args) { try { Context context = new Context(); // 定義一個語法容器,用於儲存一個具體表達式 Stack<IExpression> stack = new Stack<IExpression>(); // for (; ; ) { // // 進行語法解析,併產生遞迴呼叫 // } // 獲取得到最終的解析表示式:完整語法樹 IExpression expression = stack.pop(); // 遞迴呼叫獲取結果 expression.interpret(context); }catch (Exception e){ e.printStackTrace(); } } }
上面我只寫了標準寫法,上下文內容我沒做補充,只是讓大家明白這個模板怎麼寫,具體的案例在菜鳥教程也有https://www.runoob.com/design-pattern/interpreter-pattern.html
2.使用直譯器模式解析數學表示式:
下面用直譯器模式來實現一個數學表示式計算器,包含加減乘除運算。首先定義抽象表示式角色IArithmeticInterpreter介面:
//抽象表示式角色 public interface IArithmeticInterpreter { int interpret(); }
//終結表示式角色AbstractInterpreter抽象類 public abstract class Interpreter implements IArithmeticInterpreter { protected IArithmeticInterpreter left; protected IArithmeticInterpreter right; public Interpreter(IArithmeticInterpreter left, IArithmeticInterpreter right) { this.left = left; this.right = right; } }
//加 public class AddInterpreter extends Interpreter { public AddInterpreter(IArithmeticInterpreter left, IArithmeticInterpreter right) { super(left, right); } public int interpret() { return this.left.interpret() + this.right.interpret(); } }
//減 public class SubInterpreter extends Interpreter { public SubInterpreter(IArithmeticInterpreter left, IArithmeticInterpreter right) { super(left, right); } public int interpret() { return this.left.interpret() - this.right.interpret(); } }
//乘法 public class MultiInterpreter extends Interpreter { public MultiInterpreter(IArithmeticInterpreter left, IArithmeticInterpreter right){ super(left,right); } public int interpret() { return this.left.interpret() * this.right.interpret(); } }
//除法 public class DivInterpreter extends Interpreter { public DivInterpreter(IArithmeticInterpreter left, IArithmeticInterpreter right){ super(left,right); } public int interpret() { return this.left.interpret() / this.right.interpret(); } }
//建立數字表達式類 public class NumInterpreter implements IArithmeticInterpreter { private int value; public NumInterpreter(int value) { this.value = value; } public int interpret() { return this.value; } }
//計算器類 public class Calculator { private Stack<IArithmeticInterpreter> stack = new Stack<IArithmeticInterpreter>(); public Calculator(String expression) { this.parse(expression); } private void parse(String expression) { String [] elements = expression.split(" "); IArithmeticInterpreter leftExpr, rightExpr; for (int i = 0; i < elements.length ; i++) { String operator = elements[i]; if (OperatorUtil.isOperator(operator)){ leftExpr = this.stack.pop(); rightExpr = new NumInterpreter(Integer.valueOf(elements[++i])); System.out.println("出棧: " + leftExpr.interpret() + " 和 " + rightExpr.interpret()); this.stack.push(OperatorUtil.getInterpreter(leftExpr, rightExpr,operator)); System.out.println("應用運算子: " + operator); } else{ NumInterpreter numInterpreter = new NumInterpreter(Integer.valueOf(elements[i])); this.stack.push(numInterpreter); System.out.println("入棧: " + numInterpreter.interpret()); } } } public int calculate() { return this.stack.pop().interpret(); } }
//操作工具類 public class OperatorUtil { public static boolean isOperator(String symbol) { return (symbol.equals("+") || symbol.equals("-") || symbol.equals("*")); } public static Interpreter getInterpreter(IArithmeticInterpreter left, IArithmeticInterpreter right, String symbol) { if (symbol.equals("+")) { return new AddInterpreter(left, right); } else if (symbol.equals("-")) { return new SubInterpreter(left, right); } else if (symbol.equals("*")) { return new MultiInterpreter(left, right); } else if (symbol.equals("/")) { return new DivInterpreter(left, right); } return null; } }
public class Test { public static void main(String[] args) { ExpressionParser parser = new SpelExpressionParser(); Expression expression = parser.parseExpression("100 * 2 + 400 * 1 + 66"); int result = (Integer) expression.getValue(); System.out.println("計算結果是:" + result); System.out.println("result: " + new Calculator("10 + 30").calculate()); System.out.println("result: " + new Calculator("10 + 30 - 20").calculate()); System.out.println("result: " + new Calculator("100 * 2 + 400 * 1 + 66").calculate()); } }
三、直譯器模式在原始碼中的體現
在JDK原始碼中的Pattern對正則表示式的編譯和解析就體現到了解析器模式
private void compile() { // Handle canonical equivalences if (has(CANON_EQ) && !has(LITERAL)) { normalize(); } else { normalizedPattern = pattern; } patternLength = normalizedPattern.length(); // Copy pattern to int array for convenience // Use double zero to terminate pattern temp = new int[patternLength + 2]; hasSupplementary = false; int c, count = 0; // Convert all chars into code points for (int x = 0; x < patternLength; x += Character.charCount(c)) { c = normalizedPattern.codePointAt(x); if (isSupplementary(c)) { hasSupplementary = true; } temp[count++] = c; } patternLength = count; // patternLength now in code points if (! has(LITERAL)) RemoveQEQuoting(); // Allocate all temporary objects here. buffer = new int[32]; groupNodes = new GroupHead[10]; namedGroups = null; if (has(LITERAL)) { // Literal pattern handling matchRoot = newSlice(temp, patternLength, hasSupplementary); matchRoot.next = lastAccept; } else { // Start recursive descent parsing matchRoot = expr(lastAccept); // Check extra pattern characters if (patternLength != cursor) { if (peek() == ')') { throw error("Unmatched closing ')'"); } else { throw error("Unexpected internal error"); } } } // Peephole optimization if (matchRoot instanceof Slice) { root = BnM.optimize(matchRoot); if (root == matchRoot) { root = hasSupplementary ? new StartS(matchRoot) : new Start(matchRoot); } } else if (matchRoot instanceof Begin || matchRoot instanceof First) { root = matchRoot; } else { root = hasSupplementary ? new StartS(matchRoot) : new Start(matchRoot); } // Release temporary storage temp = null; buffer = null; groupNodes = null; patternLength = 0; compiled = true; } Map<String, Integer> namedGroups() { if (namedGroups == null) namedGroups = new HashMap<>(2); return namedGroups; }
private Pattern(String p, int f) { //儲存資料 pattern = p; flags = f; // to use UNICODE_CASE if UNICODE_CHARACTER_CLASS present if ((flags & UNICODE_CHARACTER_CLASS) != 0) flags |= UNICODE_CASE; // Reset group index count capturingGroupCount = 1; localCount = 0; if (pattern.length() > 0) { //呼叫編譯方法 compile(); } else { root = new Start(lastAccept); matchRoot = lastAccept; } }
四、總結
優點:
擴充套件性強:在直譯器模式中由於語法是由很多類表示的,當語法規則更改時,只需修改相應的非終結符表示式即可;若擴充套件語法時,只需新增相應非終結符類即可;
增加了新的解釋表示式的方式;
易於實現文法:直譯器模式對應的文法應當是比較簡單且易於實現的,過於複雜的語法並不適合使用直譯器模式。
缺點:
語法規則較複雜時,會引起類膨脹:直譯器模式每個語法都要產生一個非終結符表示式當語法規則比較複雜時,就會產生大量的解釋類,增加系統維護困難;
執行效率比較低:直譯器模式採用遞迴呼叫方法,每個非終結符表示式只關心與自己有關的表示式,每個表示式需要知道最終的結果,因此完整表示式的最終結果是通過從後往前遞迴呼叫的方式獲取得到。當完整表示式層級較深時,解釋效率下降,且出錯時除錯困難,因為遞迴迭代層級太深。