今天的目标是:搭建一个支持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
我们今天要完成的目标是:
- 解析Pascal PROGRAM 头。
- 解析Pascal 变量声明。
- 让解释器支持DIV 整数除法的关键字, / 作为浮点数的除法。
- 支持Pascal的comments。
-
程序 在Pascal语言中的例子是:
PROGRAM Part10; BEGIN END.
-
块规则整合了声明规则和复合语句规则。这条规则在后面讲到“过程声明”的时候也会被用到。 块的例子:
VAR number : INTEGER; BEGIN END 另一个例子:
BEGIN END
-
Pascal 的声明分好几块,每一块都是可选的。 我们在本文中只谈谈declaration 这部分。 declarations 规则要么就是变量声明,要么就空白的。
-
Pascal 是静态语言,所有变量必须在VAR这个部分事先声明,然后才能使用:
VAR number : INTEGER; a, b, c, x : INTEGER; y : REAL;
-
type_spec 规则是为了处理INTEGER 和 REAL 两种类型,而且是用在变量声明里面:
VAR a : INTEGER; b : REAL;
这部分是可选的。
-
term 规则也升级了。现在它支持 DIV 关键字了。
斜杠只支持浮点数的除法,DIV只支持整数除法。
20 / 7 = 2.85714285714 20 DIV 7 = 2
-
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
更新项目包括:
- 新的tokens
- 新的保留关键字
- 新的skip_comments 方法来处理 Pascal comments
- 重新命名 integer 方法,改动一下实现
- 更新 get_next_token 方法来返回新的token
以上。
-
我们需要增加一些新的token来处理整数和浮点数的除法。INTEGER token 也得升级了,让它能表示整数这个类型,而不是具体的数字。更新后的 token 列表是:
- PROGRAM (关键字)
- VAR (关键字)
- COLON (:)
- COMMA (,)
- INTEGER (整数类型)
- REAL (浮点数类型)
- INTEGER_CONST (具体的整数数值,比如3、5)
- REAL_CONST (具体的浮点数数值)
- INTEGER_DIV 整数除法(DIV 关键字)
- FLOAT_DIV 浮点数除法 ( / )
-
从关键字到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'), }
-
skip_comment 方法实现很简单,一直忽略字符处理,直到它遇到右花括号:
def skip_comment(self): while self.current_char != '}': self.advance() self.advance() # the closing curly brace
-
我们重新命名 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
-
还升级了 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
- 新的AST 节点: Program, Block, VarDecl, Type
- 处理新的语法规则的新方法: block, declarations, variable_declaration, 和 type_spec.
- 新的Parser方法: program, term 和 factor
我们一个个说:
-
先说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
-
-
你一定记得,在我们的parser中,语法中的每条规则都对应着一个方法。我们今天要增加四个新的方法 block, declarations, variable_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函数没有?
-
我们还需要更新 program, term, 和, 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和/分开处理了