Design Patterns @ Python – Interpreter 解釋器模式筆記

設計模式–解釋器模式(Interpreter Pattern)

什麼是解釋器模式?

  • 解釋器模式 (Interpreter Pattern) 是一種用於當程式中有具備語言特質的物件(可能是重複、或是具備一定的規則),就可將這物件作為一個語言模型,將其經由 Laxing 拆分 以及 Parsing 解析 程序,以 將這個物件再重新轉譯成更具結構的物件 ,以 增加其擴充的彈性 ,大致就是這樣的一個設計模式。
  • 解釋器模式將語言的解釋和執行過程抽象為一個類,稱為 解釋器。解釋器可以 對任意語言進行拆分(Laxing)和解析(Parsing), 適用的對象物件包括自然語言、程式語言、數學公式、正則表達式等等。

解釋器模式的 UML 架構圖

  • 解釋器模式的 UML 架構圖如下所示:
classDiagram

class AbstractExpression {
  + interpret(): void
}

class TerminalExpression {
  + interpret(): void
}

class NonTerminalExpression {
  + interpret(): void
}

class Context {
  + execute(): void
}

AbstractExpression <|-- TerminalExpression
AbstractExpression <|-- NonTerminalExpression
Context o-- AbstractExpression
  • 其中:
    • AbstractExpression 是抽象的解釋器類別,它定義了解釋器的基本行為,即 interpret() 方法 (可參考第二則範例說明)。
    • TerminalExpression終端解釋器,它表示語法中的 終端符 ,即 不能再分解 的符號。
    • NonTerminalExpression非終端解釋器,它表示語法中的 非終端符 (表示還可再切分成更小單元),即可以 再分解 的符號。
    • Context上下文類別,它包含了 解釋器 所需要的環境信息。

範例說明:

  • 範例1: Udemy 上老師課程上的範例
class Token:
    '''
    Token 類別用來表示語法分析器所產生的 token
    '''

    class Type(Enum):
        '''
        Token 的類型 Enum
        '''

        INTEGER = 0 
        PLUS = 1
        MINUS = 2
        LPAREN = 3
        RPAREN = 4

    def __init__(self, type, text):
        '''
        建構函數,初始化 Token 類別
        '''
        self.type = type
        self.text = text

    def __str__(self):
        '''
        列印 Token 時的字串表示
        '''
        return f'`{self.text}`'


def lex(input):
    '''
    lex 函式用來將輸入的字串轉換為 token 的列表
    '''

    result = []

    i = 0
    while i < len(input):
        if input[i] == '+':
            result.append(Token(Token.Type.PLUS, '+'))
        elif input[i] == '-':
            result.append(Token(Token.Type.MINUS, '-'))
        elif input[i] == '(':
            result.append(Token(Token.Type.LPAREN, '('))
        elif input[i] == ')':
            result.append(Token(Token.Type.RPAREN, ')'))
        else:  # must be a number
            digits = [input[i]]
            for j in range(i + 1, len(input)):
                if input[j].isdigit():
                    digits.append(input[j])
                    i += 1
                else:
                    result.append(Token(Token.Type.INTEGER,
                                        ''.join(digits)))
                    break
        i += 1

    return result


# ↑↑↑ 詞彙解析 ↑↑↑

# ↓↓↓ 語法解析 ↓↓↓ 

class Integer:
    '''
    Integer 類別用來表示整數節點
    '''

    def __init__(self, value):
        self.value = value


class BinaryOperation:
    '''
    BinaryOperation 類別用來表示二元運算節點
    '''

    class Type(Enum):
        ADDITION = 0
        SUBTRACTION = 1

    def __init__(self):
        self.type = None
        self.left = None
        self.right = None

    @property
    def value(self):
        if self.type == self.Type.ADDITION:
            return self.left.value + self.right.value
        elif self.type == self.Type.SUBTRACTION:
            return self.left.value - self.right.value


def parse(tokens):
    '''
    將 Token 序列進行語法解析 --> 將 token 的列表轉換為語法樹
    '''
    
    result = BinaryOperation()
    have_lhs = False
    i = 0
    while i < len(tokens):
        token = tokens[i]

        if token.type == Token.Type.INTEGER:
            integer = Integer(int(token.text))
            if not have_lhs: # lhs --> left hand side
                # 將左邊的運算元設定為 integer 物件,並將 have_lhs 設定為 True。 
                result.left = integer
                have_lhs = True
            else:
                # 將右邊的運算元設定為 integer 物件。
                result.right = integer
                
        elif token.type == Token.Type.PLUS:
            result.type = BinaryOperation.Type.ADDITION
        elif token.type == Token.Type.MINUS:
            result.type = BinaryOperation.Type.SUBTRACTION
        elif token.type == Token.Type.LPAREN:  
            # 注:沒有 RPAREN 的 if
            j = i
            while j < len(tokens):
                if tokens[j].type == Token.Type.RPAREN:
                    break
                j += 1
            
            # 遞迴解析子運算式
            subexpression = tokens[i + 1:j] 
            element = parse(subexpression)
            if not have_lhs:
                result.left = element
                have_lhs = True
            else:
                result.right = element
                
            i = j  # 更新索引
        i += 1
        
    return result

def eval(input):
    '''
    評估運算式
    '''
    
    tokens = lex(input)
    print(' '.join(map(str, tokens)))

    parsed = parse(tokens) 
    print(f'{input} = {parsed.value}')

if __name__ == '__main__':
    eval('(13+4)-(12+1)')
    eval('1+(3-4)')

    # 這個不會工作
    eval('1+2+(3-4)')
# 執行結果:
# `(` `13` `+` `4` `)` `-` `(` `12` `+` `1` `)`
# (13+4)-(12+1) = 4
# `1` `+` `(` `3` `-` `4` `)`
# 1+(3-4) = 0
# `1` `+` `2` `+` `(` `3` `-` `4` `)`
# 1+2+(3-4) = 0
  • 範例2:四則運算(加法)的 Implement
from abc import ABC, abstractmethod

# AbstractExpression
class Expression(ABC):
    @abstractmethod
    def interpret(self, context):
        pass

# TerminalExpression for Numbers
class NumberExpression(Expression):
    def __init__(self, number):
        self.number = number

    def interpret(self, context):
        return self.number

# TerminalExpression for Variables
class VariableExpression(Expression):
    def __init__(self, variable_name):
        self.variable_name = variable_name

    def interpret(self, context):
        return context.get(self.variable_name)

# NonTerminalExpression for Addition
class AddExpression(Expression):
    def __init__(self, left, right):
        self.left = left
        self.right = right

    def interpret(self, context):
        return self.left.interpret(context) + self.right.interpret(context)

# Context class
class Context:
    def __init__(self):
        self.variables = {}

    def set(self, name, value):
        self.variables[name] = value

    def get(self, name):
        return self.variables[name]

# Client code
if __name__ == "__main__":
    # Set up the context
    context = Context()
    context.set("x", 5)
    context.set("y", 7)

    # Build the expression tree
    x = VariableExpression("x")
    y = VariableExpression("y")
    sum = AddExpression(x, y)

    # Interpret the expression
    result = sum.interpret(context)

    print("x + y = ", result)  # Output: x + y = 12
# 執行結果:
# x + y =  12

解釋器模式的優點及缺點分析

  • 優點:

    • 語言的解釋邏輯與語言的使用邏輯分離,使語言的解釋可以 靈活擴展
    • 可以 將語言的解釋邏輯進行封裝,提高了語言的 可維護性
  • 缺點:

    • 增加了語言的 複雜性
    • 可能會降低語言的執行效率。
  • 適用場景:

    • 領域特定語言
    • 複雜輸入解釋
    • 可擴展的語言結構
1個讚