如何写一个简单的解释器(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中。今天我们的任务就是怎么样解析这段代码。

首先我们需要把过程声明这个新物种加入我们的语法中。

过程声明的定义里面包含了一个block规则,一图胜万言。

更新后的句法图是这样的:

对于下面这个代码,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规则是包含声明规则的,声明规则有包含着过程声明规则。


好的,下面我们看解释器的更新部分。

更新 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

我们试试老的解释器能不能工作。还行,没有崩掉。

$ 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

 

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值