如何写一个简单的解释器(Interpreter)-10

今天的目标是:搭建一个支持Pascal编程语言子集的全功能解释器。我们使用的Pascal的例子程序,可以被 Free Pascal compiler, fpc.编译通过。当然,我们希望我们的解释器可以成功解释通过。

 

例子程序代码如下:

PROGRAM Part10;
VAR
   number     : INTEGER;
   a, b, c, x : INTEGER;
   y          : REAL;

BEGIN {Part10}
   BEGIN
      number := 2;
      a := number;
      b := 10 * a + 10 * number DIV 4;
      c := a - - b
   END;
   x := 11;
   y := 20 / 7 + 3.14;
   { writeln('a = ', a); }
   { writeln('b = ', b); }
   { writeln('c = ', c); }
   { writeln('number = ', number); }
   { writeln('x = ', x); }
   { writeln('y = ', y); }
END.  {Part10}

先执行看看结果:

$ python spi.py part10.pas
a = 2
b = 25
c = 27
number = 2
x = 11
y = 5.99714285714


用fpc编译后的执行结果是一样的。

$ fpc part10.pas
$ ./part10
a = 2
b = 25
c = 27
number = 2
x = 11
y =  5.99714285714286E+000


我们今天要完成的目标是:

  1. 解析Pascal PROGRAM 头。
  2. 解析Pascal 变量声明。
  3. 让解释器支持DIV 整数除法的关键字, / 作为浮点数的除法。
  4. 支持Pascal的comments。


  1. 程序 在Pascal语言中的例子是:

    PROGRAM Part10;
    BEGIN
    END.
    
  2. 规则整合了声明规则和复合语句规则。这条规则在后面讲到“过程声明”的时候也会被用到。 块的例子:

    VAR
       number : INTEGER;
    
    BEGIN
    END
    
    另一个例子:
    BEGIN
    END
    
  3. Pascal 的声明分好几块,每一块都是可选的。 我们在本文中只谈谈declaration 这部分。 declarations 规则要么就是变量声明,要么就空白的。

  4. Pascal 是静态语言,所有变量必须在VAR这个部分事先声明,然后才能使用:

    VAR
       number     : INTEGER;
       a, b, c, x : INTEGER;
       y          : REAL;
    
  5. type_spec 规则是为了处理INTEGER 和 REAL 两种类型,而且是用在变量声明里面:

    VAR
       a : INTEGER;
       b : REAL;
    

    这部分是可选的。

  6. term 规则也升级了。现在它支持 DIV 关键字了。

    斜杠只支持浮点数的除法,DIV只支持整数除法。

    20 / 7 = 2.85714285714
    20 DIV 7 = 2
  7. factor 规则也升级了。现在他支持整数和浮点数常量了。 同时,我把INTEGER 子规则给删除了,因为常量有了可以区别自己的名字—— INTEGER_CONST 和 REAL_CONST tokens,INTEGER token 表示整数类型。

    y := 20 / 7 + 3.14;
    


完整的语法如下:

    program : PROGRAM variable SEMI block DOT

    block : declarations compound_statement

    declarations : VAR (variable_declaration SEMI)+
                 | empty

    variable_declaration : ID (COMMA ID)* COLON type_spec

    type_spec : INTEGER

    compound_statement : BEGIN statement_list END

    statement_list : statement
                   | statement SEMI statement_list

    statement : compound_statement
              | assignment_statement
              | empty

    assignment_statement : variable ASSIGN expr

    empty :

    expr : term ((PLUS | MINUS) term)*

    term : factor ((MUL | INTEGER_DIV | FLOAT_DIV) factor)*

    factor : PLUS factor
           | MINUS factor
           | INTEGER_CONST
           | REAL_CONST
           | LPAREN expr RPAREN
           | variable

    variable: ID

接下里我们要更新代码了。

译者按:为了更清楚的表达,很多名词我就不翻译了。


更新Lexer

更新项目包括:

  1. 新的tokens
  2. 新的保留关键字
  3. 新的skip_comments 方法来处理 Pascal comments
  4. 重新命名 integer 方法,改动一下实现
  5. 更新 get_next_token 方法来返回新的token

以上。

  1. 我们需要增加一些新的token来处理整数和浮点数的除法。INTEGER token 也得升级了,让它能表示整数这个类型,而不是具体的数字。更新后的 token 列表是:

    • PROGRAM (关键字)
    • VAR  (关键字)
    • COLON (:)
    • COMMA (,)
    • INTEGER (整数类型)
    • REAL (浮点数类型)
    • INTEGER_CONST (具体的整数数值,比如3、5)
    • REAL_CONST (具体的浮点数数值)
    • INTEGER_DIV 整数除法(DIV 关键字)
    • FLOAT_DIV 浮点数除法 ( / )
  2. 从关键字到token的映射关系是:

    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'),
    }
    
  3. skip_comment 方法实现很简单,一直忽略字符处理,直到它遇到右花括号:

    def skip_comment(self):
        while self.current_char != '}':
            self.advance()
        self.advance()  # the closing curly brace
    
  4. 我们重新命名 integer 和 number 方法。让它可以同时处理整数和浮点数。两者的区分在于有没有小数点,根据小数点进入不同的if分支。

    def number(self):
        """Return a (multidigit) integer or float consumed from the input."""
        result = ''
        while self.current_char is not None and self.current_char.isdigit():
            result += self.current_char
            self.advance()
    
        if self.current_char == '.':
            result += self.current_char
            self.advance()
    
            while (
                self.current_char is not None and
                self.current_char.isdigit()
            ):
                result += self.current_char
                self.advance()
    
            token = Token('REAL_CONST', float(result))
        else:
            token = Token('INTEGER_CONST', int(result))
    
        return token
    
  5. 还升级了 get_next_token 方法,返回新的token:

    def get_next_token(self):
        while self.current_char is not None:
            ...
            if self.current_char == '{':
                self.advance()
                self.skip_comment()
                continue
            ...
            if self.current_char.isdigit():
                return self.number()
    
            if self.current_char == ':':
                self.advance()
                return Token(COLON, ':')
    
            if self.current_char == ',':
                self.advance()
                return Token(COMMA, ',')
            ...
            if self.current_char == '/':
                self.advance()
                return Token(FLOAT_DIV, '/')
            ...
    


更新Parser

  1. 新的AST 节点: ProgramBlockVarDeclType
  2. 处理新的语法规则的新方法: blockdeclarationsvariable_declaration, 和 type_spec.
  3. 新的Parser方法: programterm 和 factor

我们一个个说:

  1. 先说AST节点吧。我们有四个新的节点:

    • Program AST 节点表示一个程序,是AST的根节点:

      class Program(AST):
          def __init__(self, name, block):
              self.name = name
              self.block = block

       

    • Block AST节点存放复合语句的声明和定义:

      class Block(AST):
          def __init__(self, declarations, compound_statement):
              self.declarations = declarations
              self.compound_statement = compound_statement

       

    • VarDecl AST 节点存放变量的声明。包含了变量的节点和类型的节点:

      class VarDecl(AST):
          def __init__(self, var_node, type_node):
              self.var_node = var_node
              self.type_node = type_node

       

    • Type AST节点表示一个变量的类型,是整形还是浮点型。

      class Type(AST):
          def __init__(self, token):
              self.token = token
              self.value = token.value

       

  2. 你一定记得,在我们的parser中,语法中的每条规则都对应着一个方法。我们今天要增加四个新的方法 blockdeclarationsvariable_declaration, 和 type_spec.这些方法负责解析新的语言结构体并构建出新的AST节点。

    def block(self):
        """block : declarations compound_statement"""
        declaration_nodes = self.declarations()
        compound_statement_node = self.compound_statement()
        node = Block(declaration_nodes, compound_statement_node)
        return node
    
    def declarations(self):
        """declarations : VAR (variable_declaration 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)
    
        return declarations
    
    def variable_declaration(self):
        """variable_declaration : ID (COMMA ID)* COLON type_spec"""
        var_nodes = [Var(self.current_token)]  # first ID
        self.eat(ID)
    
        while self.current_token.type == COMMA:
            self.eat(COMMA)
            var_nodes.append(Var(self.current_token))
            self.eat(ID)
    
        self.eat(COLON)
    
        type_node = self.type_spec()
        var_declarations = [
            VarDecl(var_node, type_node)
            for var_node in var_nodes
        ]
        return var_declarations
    
    def type_spec(self):
        """type_spec : INTEGER
                     | REAL
        """
        token = self.current_token
        if self.current_token.type == INTEGER:
            self.eat(INTEGER)
        else:
            self.eat(REAL)
        node = Type(token)
        return node

    看见一大堆的eat函数没有?

  3. 我们还需要更新 programterm, 和, factor 方法以顺应语法的升级:

    def program(self):
        """program : PROGRAM variable SEMI block DOT"""
        self.eat(PROGRAM)
        var_node = self.variable()
        prog_name = var_node.value
        self.eat(SEMI)
        block_node = self.block()
        program_node = Program(prog_name, block_node)
        self.eat(DOT)
        return program_node
    
    def term(self):
        """term : factor ((MUL | INTEGER_DIV | FLOAT_DIV) factor)*"""
        node = self.factor()
    
        while self.current_token.type in (MUL, INTEGER_DIV, FLOAT_DIV):
            token = self.current_token
            if token.type == MUL:
                self.eat(MUL)
            elif token.type == INTEGER_DIV:
                self.eat(INTEGER_DIV)
            elif token.type == FLOAT_DIV:
                self.eat(FLOAT_DIV)
    
            node = BinOp(left=node, op=token, right=self.factor())
    
        return node
    
    def factor(self):
        """factor : PLUS factor
                  | MINUS factor
                  | INTEGER_CONST
                  | REAL_CONST
                  | LPAREN expr RPAREN
                  | variable
        """
        token = self.current_token
        if token.type == PLUS:
            self.eat(PLUS)
            node = UnaryOp(token, self.factor())
            return node
        elif token.type == MINUS:
            self.eat(MINUS)
            node = UnaryOp(token, self.factor())
            return node
        elif token.type == INTEGER_CONST:
            self.eat(INTEGER_CONST)
            return Num(token)
        elif token.type == REAL_CONST:
            self.eat(REAL_CONST)
            return Num(token)
        elif token.type == LPAREN:
            self.eat(LPAREN)
            node = self.expr()
            self.eat(RPAREN)
            return node
        else:
            node = self.variable()
            return node

     

现在我们看看AST这棵树现在是什么样子。针对下面这个Pascal程序:

PROGRAM Part10AST;
VAR
   a, b : INTEGER;
   y    : REAL;

BEGIN {Part10AST}
   a := 2;
   b := 10 * a + 10 * a DIV 4;
   y := 20 / 7 + 3.14;
END.  {Part10AST}

首先我们用 genastdot.py 工具来生成AST:

$ python genastdot.py part10ast.pas > ast.dot && dot -Tpng -o ast.png ast.dot

你能看到新的AST节点吧?


更新interpreter

增加了四个新的visit_方法,其实就是对应了四个新加入的AST节点。

  • visit_Program
  • visit_Block
  • visit_VarDecl
  • visit_Type

这很简单。 Interpreter 对 VarDecl 和 Type 节点并不做什么工作。

def visit_Program(self, node):
    self.visit(node.block)

def visit_Block(self, node):
    for declaration in node.declarations:
        self.visit(declaration)
    self.visit(node.compound_statement)

def visit_VarDecl(self, node):
    # Do nothing
    pass

def visit_Type(self, node):
    # Do nothing
    pass

这还不够,我们要升级visit_BinOp 方法来准确的区分和解释整数和浮点数的除法:

def visit_BinOp(self, node):
    if node.op.type == PLUS:
        return self.visit(node.left) + self.visit(node.right)
    elif node.op.type == MINUS:
        return self.visit(node.left) - self.visit(node.right)
    elif node.op.type == MUL:
        return self.visit(node.left) * self.visit(node.right)
    elif node.op.type == INTEGER_DIV:
        return self.visit(node.left) // self.visit(node.right)
    elif node.op.type == FLOAT_DIV:
        return float(self.visit(node.left)) / float(self.visit(node.right))


总结一下这一章我们都干了些什么:

  • 给我们的语法增加了几条新的规则
  • 增加了新的token和对应的方法
  • 针对新的语言结构增加了新的AST节点
  • 针对新的语法规则增加了新的方法
  • 增加了新的visit方法

有了本章的这些改动,第九章提到的那些限制就没有了:

  • 解释器可以处理PROGRAM 头了
  • 变量可以用VAR关键字声明了
  • DIV和/分开处理了

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值