天天看點

如何寫一個簡單的解釋器(Interpreter)-12

“不怕慢,就怕停” - 中國諺語.
如何寫一個簡單的解釋器(Interpreter)-12

什麼是過程聲明procedure declaration?  過程聲明是一個語言子產品,定義了一個辨別符(過程的名字)和對應的Pascal代碼塊。有點像C語言的函數。

嚴格定義:

  • Pascal 過程沒有傳回值.
  • Pascal 過程可以嵌套
  • 可以有形參

這個是我們今天的測試Pascal程式:

PROGRAM Part12;
VAR
   a : INTEGER;

PROCEDURE P1;
VAR
   a : REAL;
   k : INTEGER;

   PROCEDURE P2;
   VAR
      a, z : INTEGER;
   BEGIN {P2}
      z := 777;
   END;  {P2}

BEGIN {P1}

END;  {P1}

BEGIN {Part12}
   a := 10;
END.  {Part12}
           

很清楚吧?我們定義了P1和P2兩個過程,P2嵌套在P1中。今天我們的任務就是怎麼樣解析這段代碼。

首先我們需要把過程聲明這個新物種加入我們的文法中。

如何寫一個簡單的解釋器(Interpreter)-12

過程聲明的定義裡面包含了一個block規則,一圖勝萬言。

更新後的句法圖是這樣的:

如何寫一個簡單的解釋器(Interpreter)-12

對于下面這個代碼,P1和P1A是同一個層次的聲明:

PROGRAM Test;
VAR
   a : INTEGER;

PROCEDURE P1;
BEGIN {P1}

END;  {P1}

PROCEDURE P1A;
BEGIN {P1A}

END;  {P1A}

BEGIN {Test}
   a := 10;
END.  {Test}
      

過程聲明當然也可以是嵌套的。因為過程聲明的規則裡面,有個block規則,而block規則是包含聲明規則的,聲明規則有包含着過程聲明規則。

如何寫一個簡單的解釋器(Interpreter)-12

好的,下面我們看解釋器的更新部分。

更新 Lexer

僅僅是增加一個新的 PROCEDURE token PROCEDURE = 'PROCEDURE'。

​​​另外把 ‘PROCEDURE’ 作為一個新的保留關鍵字。

加上PROCEDURE,所有的關鍵字如下:

RESERVED_KEYWORDS = {
    'PROGRAM': Token('PROGRAM', 'PROGRAM'),
    'VAR': Token('VAR', 'VAR'),
    'DIV': Token('INTEGER_DIV', 'DIV'),
    'INTEGER': Token('INTEGER', 'INTEGER'),
    'REAL': Token('REAL', 'REAL'),
    'BEGIN': Token('BEGIN', 'BEGIN'),
    'END': Token('END', 'END'),
    'PROCEDURE': Token('PROCEDURE', 'PROCEDURE'),
}
           

更新Parser

  1. 增加新的ProcedureDecl AST 節點
  2. 更新Parser的 declarations 方法,支援 procedure declarations

一下是細節:

  1. ProcedureDecl AST 節點表示 procedure declaration. 它實作方式是Python的類,構造函數接受過程的名字和代碼塊node(本身是AST node).
    class ProcedureDecl(AST):
        def __init__(self, proc_name, block_node):
            self.proc_name = proc_name
            self.block_node = block_node
               
  2. declarations 方法更新。這個方法還記得嗎?在 Parser 類裡面。
    def declarations(self):
        """declarations : VAR (variable_declaration SEMI)+
                        | (PROCEDURE ID SEMI block SEMI)*
                        | empty
        """
        declarations = []
    
        if self.current_token.type == VAR:
            self.eat(VAR)
            while self.current_token.type == ID:
                var_decl = self.variable_declaration()
                declarations.extend(var_decl)
                self.eat(SEMI)
    
        while self.current_token.type == PROCEDURE:
            self.eat(PROCEDURE)
            proc_name = self.current_token.value
            self.eat(ID)
            self.eat(SEMI)
            block_node = self.block()
            proc_decl = ProcedureDecl(proc_name, block_node)
            declarations.append(proc_decl)
            self.eat(SEMI)
    
        return declarations
               

更新 SymbolTable builder

這部分先留白給下一章。

def visit_ProcedureDecl(self, node):
    pass
           

更新 Interpreter

對 visit_ProcedureDecl 方法,我們也留白。

我們看看加入了ProcedureDecl 新節點後,AST長什麼樣子:

PROGRAM Part12;
VAR
   a : INTEGER;

PROCEDURE P1;
VAR
   a : REAL;
   k : INTEGER;

   PROCEDURE P2;
   VAR
      a, z : INTEGER;
   BEGIN {P2}
      z := 777;
   END;  {P2}

BEGIN {P1}

END;  {P1}

BEGIN {Part12}
   a := 10;
END.  {Part12}
      

用genastdot.py 工具來生成AST:

$ python genastdot.py part12.pas > ast.dot && dot -Tpng -o ast.png ast.dot
      
如何寫一個簡單的解釋器(Interpreter)-12

我們試試老的解釋器能不能工作。還行,沒有崩掉。

$ python spi.py part12.pas
Define: INTEGER
Define: REAL
Lookup: INTEGER
Define: <a:INTEGER>
Lookup: a

Symbol Table contents:
Symbols: [INTEGER, REAL, <a:INTEGER>]

Run-time GLOBAL_MEMORY contents:
a = 10
           

繼續閱讀