07 优化与重构 | 《Let’s Build A Simple Interpreter》
本文最后更新于:2021年9月5日
为了提供更好的错误消息,指出代码中出现问题的位置,例如:SyntaxError: Unexpected token -> Token(TokenType.SEMI, ‘;’, position=23:13),需要向解释器添加一些功能,具体包括:
- 添加错误代码和自定义异常:LexerError、ParserError 和 SemanticError
- 向 Lexer 类添加新成员以帮助跟踪标记的位置:lineno 和 column
- 修改 Advance 方法来更新词法分析器的 lineno 和 column 变量
- 更新错误方法以引发 LexerError 异常,其中包含有关当前行和列的信息
- 在 TokenType 枚举类中定义 token 类型
- 添加代码以从 TokenType 枚举成员中自动创建保留关键字
- 在 Token 类中添加新成员:lineno 和 column 以跟踪标记的行号和列号,相应在文本中重构 get_next_token 方法代码以使其更短,并拥有处理单字符标记的通用代码
"""SPI - Simple Pascal Interpreter. Part 15."""
import argparse
import sys
from enum import Enum
_SHOULD_LOG_SCOPE = False # see '--scope' command line option
class ErrorCode(Enum):
UNEXPECTED_TOKEN = 'Unexpected token'
ID_NOT_FOUND = 'Identifier not found'
DUPLICATE_ID = 'Duplicate id found'
class Error(Exception):
def __init__(self, error_code=None, token=None, message=None):
self.error_code = error_code
self.token = token
# add exception class name before the message
self.message = f'{self.__class__.__name__}: {message}'
class LexerError(Error):
pass
class ParserError(Error):
pass
class SemanticError(Error):
pass
###############################################################################
# #
# LEXER #
# #
###############################################################################
class TokenType(Enum):
# single-character token types
PLUS = '+'
MINUS = '-'
MUL = '*'
FLOAT_DIV = '/'
LPAREN = '('
RPAREN = ')'
SEMI = ';'
DOT = '.'
COLON = ':'
COMMA = ','
# block of reserved words
PROGRAM = 'PROGRAM' # marks the beginning of the block
INTEGER = 'INTEGER'
REAL = 'REAL'
INTEGER_DIV = 'DIV'
VAR = 'VAR'
PROCEDURE = 'PROCEDURE'
BEGIN = 'BEGIN'
END = 'END' # marks the end of the block
# misc
ID = 'ID'
INTEGER_CONST = 'INTEGER_CONST'
REAL_CONST = 'REAL_CONST'
ASSIGN = ':='
EOF = 'EOF'
class Token(object):
def __init__(self, type, value, lineno=None, column=None):
self.type = type
self.value = value
self.lineno = lineno
self.column = column
def __str__(self):
"""String representation of the class instance.
Example:
>>> Token(TokenType.INTEGER, 7, lineno=5, column=10)
Token(TokenType.INTEGER, 7, position=5:10)
"""
return 'Token({type}, {value}, position={lineno}:{column})'.format(
type=self.type,
value=repr(self.value),
lineno=self.lineno,
column=self.column,
)
def __repr__(self):
return self.__str__()
def _build_reserved_keywords():
"""Build a dictionary of reserved keywords.
The function relies on the fact that in the TokenType
enumeration the beginning of the block of reserved keywords is
marked with PROGRAM and the end of the block is marked with
the END keyword.
Result:
{'PROGRAM': <TokenType.PROGRAM: 'PROGRAM'>,
'INTEGER': <TokenType.INTEGER: 'INTEGER'>,
'REAL': <TokenType.REAL: 'REAL'>,
'DIV': <TokenType.INTEGER_DIV: 'DIV'>,
'VAR': <TokenType.VAR: 'VAR'>,
'PROCEDURE': <TokenType.PROCEDURE: 'PROCEDURE'>,
'BEGIN': <TokenType.BEGIN: 'BEGIN'>,
'END': <TokenType.END: 'END'>}
"""
# enumerations support iteration, in definition order
tt_list = list(TokenType)
start_index = tt_list.index(TokenType.PROGRAM)
end_index = tt_list.index(TokenType.END)
reserved_keywords = {
token_type.value: token_type
for token_type in tt_list[start_index:end_index + 1]
}
return reserved_keywords
RESERVED_KEYWORDS = _build_reserved_keywords()
class Lexer(object):
def __init__(self, text):
# client string input, e.g. "4 + 2 * 3 - 6 / 2"
self.text = text
# self.pos is an index into self.text
self.pos = 0
self.current_char = self.text[self.pos]
# token line number and column number
self.lineno = 1
self.column = 1
def error(self):
s = "Lexer error on '{lexeme}' line: {lineno} column: {column}".format(
lexeme=self.current_char,
lineno=self.lineno,
column=self.column,
)
raise LexerError(message=s)
def advance(self):
"""Advance the `pos` pointer and set the `current_char` variable."""
if self.current_char == '\n':
self.lineno += 1
self.column = 0
self.pos += 1
if self.pos > len(self.text) - 1:
self.current_char = None # Indicates end of input
else:
self.current_char = self.text[self.pos]
self.column += 1
def peek(self):
peek_pos = self.pos + 1
if peek_pos > len(self.text) - 1:
return None
else:
return self.text[peek_pos]
def skip_whitespace(self):
while self.current_char is not None and self.current_char.isspace():
self.advance()
def skip_comment(self):
while self.current_char != '}':
self.advance()
self.advance() # the closing curly brace
def number(self):
"""Return a (multidigit) integer or float consumed from the input."""
# Create a new token with current line and column number
token = Token(type=None, value=None, lineno=self.lineno, column=self.column)
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.type = TokenType.REAL_CONST
token.value = float(result)
else:
token.type = TokenType.INTEGER_CONST
token.value = int(result)
return token
def _id(self):
"""Handle identifiers and reserved keywords"""
# Create a new token with current line and column number
token = Token(type=None, value=None, lineno=self.lineno, column=self.column)
value = ''
while self.current_char is not None and self.current_char.isalnum():
value += self.current_char
self.advance()
token_type = RESERVED_KEYWORDS.get(value.upper())
if token_type is None:
token.type = TokenType.ID
token.value = value
else:
# reserved keyword
token.type = token_type
token.value = value.upper()
return token
def get_next_token(self):
"""Lexical analyzer (also known as scanner or tokenizer)
This method is responsible for breaking a sentence
apart into tokens. One token at a time.
"""
while self.current_char is not None:
if self.current_char.isspace():
self.skip_whitespace()
continue
if self.current_char == '{':
self.advance()
self.skip_comment()
continue
if self.current_char.isalpha():
return self._id()
if self.current_char.isdigit():
return self.number()
if self.current_char == ':' and self.peek() == '=':
token = Token(
type=TokenType.ASSIGN,
value=TokenType.ASSIGN.value, # ':='
lineno=self.lineno,
column=self.column,
)
self.advance()
self.advance()
return token
# single-character token
try:
# get enum member by value, e.g.
# TokenType(';') --> TokenType.SEMI
token_type = TokenType(self.current_char)
except ValueError:
# no enum member with value equal to self.current_char
self.error()
else:
# create a token with a single-character lexeme as its value
token = Token(
type=token_type,
value=token_type.value, # e.g. ';', '.', etc
lineno=self.lineno,
column=self.column,
)
self.advance()
return token
# EOF (end-of-file) token indicates that there is no more
# input left for lexical analysis
return Token(type=TokenType.EOF, value=None)
###############################################################################
# #
# PARSER #
# #
###############################################################################
class AST(object):
pass
class BinOp(AST):
def __init__(self, left, op, right):
self.left = left
self.token = self.op = op
self.right = right
class Num(AST):
def __init__(self, token):
self.token = token
self.value = token.value
class UnaryOp(AST):
def __init__(self, op, expr):
self.token = self.op = op
self.expr = expr
class Compound(AST):
"""Represents a 'BEGIN ... END' block"""
def __init__(self):
self.children = []
class Assign(AST):
def __init__(self, left, op, right):
self.left = left
self.token = self.op = op
self.right = right
class Var(AST):
"""The Var node is constructed out of ID token."""
def __init__(self, token):
self.token = token
self.value = token.value
class NoOp(AST):
pass
class Program(AST):
def __init__(self, name, block):
self.name = name
self.block = block
class Block(AST):
def __init__(self, declarations, compound_statement):
self.declarations = declarations
self.compound_statement = compound_statement
class VarDecl(AST):
def __init__(self, var_node, type_node):
self.var_node = var_node
self.type_node = type_node
class Type(AST):
def __init__(self, token):
self.token = token
self.value = token.value
class Param(AST):
def __init__(self, var_node, type_node):
self.var_node = var_node
self.type_node = type_node
class ProcedureDecl(AST):
def __init__(self, proc_name, params, block_node):
self.proc_name = proc_name
self.params = params # a list of Param nodes
self.block_node = block_node
class Parser(object):
def __init__(self, lexer):
self.lexer = lexer
# set current token to the first token taken from the input
self.current_token = self.get_next_token()
def get_next_token(self):
return self.lexer.get_next_token()
def error(self, error_code, token):
raise ParserError(
error_code=error_code,
token=token,
message=f'{error_code.value} -> {token}',
)
def eat(self, token_type):
# compare the current token type with the passed token
# type and if they match then "eat" the current token
# and assign the next token to the self.current_token,
# otherwise raise an exception.
if self.current_token.type == token_type:
self.current_token = self.get_next_token()
else:
self.error(
error_code=ErrorCode.UNEXPECTED_TOKEN,
token=self.current_token,
)
def program(self):
"""program : PROGRAM variable SEMI block DOT"""
self.eat(TokenType.PROGRAM)
var_node = self.variable()
prog_name = var_node.value
self.eat(TokenType.SEMI)
block_node = self.block()
program_node = Program(prog_name, block_node)
self.eat(TokenType.DOT)
return program_node
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)+)? procedure_declaration*
"""
declarations = []
if self.current_token.type == TokenType.VAR:
self.eat(TokenType.VAR)
while self.current_token.type == TokenType.ID:
var_decl = self.variable_declaration()
declarations.extend(var_decl)
self.eat(TokenType.SEMI)
while self.current_token.type == TokenType.PROCEDURE:
proc_decl = self.procedure_declaration()
declarations.append(proc_decl)
return declarations
def formal_parameters(self):
""" formal_parameters : ID (COMMA ID)* COLON type_spec """
param_nodes = []
param_tokens = [self.current_token]
self.eat(TokenType.ID)
while self.current_token.type == TokenType.COMMA:
self.eat(TokenType.COMMA)
param_tokens.append(self.current_token)
self.eat(TokenType.ID)
self.eat(TokenType.COLON)
type_node = self.type_spec()
for param_token in param_tokens:
param_node = Param(Var(param_token), type_node)
param_nodes.append(param_node)
return param_nodes
def formal_parameter_list(self):
""" formal_parameter_list : formal_parameters
| formal_parameters SEMI formal_parameter_list
"""
# procedure Foo();
if not self.current_token.type == TokenType.ID:
return []
param_nodes = self.formal_parameters()
while self.current_token.type == TokenType.SEMI:
self.eat(TokenType.SEMI)
param_nodes.extend(self.formal_parameters())
return param_nodes
def variable_declaration(self):
"""variable_declaration : ID (COMMA ID)* COLON type_spec"""
var_nodes = [Var(self.current_token)] # first ID
self.eat(TokenType.ID)
while self.current_token.type == TokenType.COMMA:
self.eat(TokenType.COMMA)
var_nodes.append(Var(self.current_token))
self.eat(TokenType.ID)
self.eat(TokenType.COLON)
type_node = self.type_spec()
var_declarations = [
VarDecl(var_node, type_node)
for var_node in var_nodes
]
return var_declarations
def procedure_declaration(self):
"""procedure_declaration :
PROCEDURE ID (LPAREN formal_parameter_list RPAREN)? SEMI block SEMI
"""
self.eat(TokenType.PROCEDURE)
proc_name = self.current_token.value
self.eat(TokenType.ID)
params = []
if self.current_token.type == TokenType.LPAREN:
self.eat(TokenType.LPAREN)
params = self.formal_parameter_list()
self.eat(TokenType.RPAREN)
self.eat(TokenType.SEMI)
block_node = self.block()
proc_decl = ProcedureDecl(proc_name, params, block_node)
self.eat(TokenType.SEMI)
return proc_decl
def type_spec(self):
"""type_spec : INTEGER
| REAL
"""
token = self.current_token
if self.current_token.type == TokenType.INTEGER:
self.eat(TokenType.INTEGER)
else:
self.eat(TokenType.REAL)
node = Type(token)
return node
def compound_statement(self):
"""
compound_statement: BEGIN statement_list END
"""
self.eat(TokenType.BEGIN)
nodes = self.statement_list()
self.eat(TokenType.END)
root = Compound()
for node in nodes:
root.children.append(node)
return root
def statement_list(self):
"""
statement_list : statement
| statement SEMI statement_list
"""
node = self.statement()
results = [node]
while self.current_token.type == TokenType.SEMI:
self.eat(TokenType.SEMI)
results.append(self.statement())
return results
def statement(self):
"""
statement : compound_statement
| assignment_statement
| empty
"""
if self.current_token.type == TokenType.BEGIN:
node = self.compound_statement()
elif self.current_token.type == TokenType.ID:
node = self.assignment_statement()
else:
node = self.empty()
return node
def assignment_statement(self):
"""
assignment_statement : variable ASSIGN expr
"""
left = self.variable()
token = self.current_token
self.eat(TokenType.ASSIGN)
right = self.expr()
node = Assign(left, token, right)
return node
def variable(self):
"""
variable : ID
"""
node = Var(self.current_token)
self.eat(TokenType.ID)
return node
def empty(self):
"""An empty production"""
return NoOp()
def expr(self):
"""
expr : term ((PLUS | MINUS) term)*
"""
node = self.term()
while self.current_token.type in (TokenType.PLUS, TokenType.MINUS):
token = self.current_token
if token.type == TokenType.PLUS:
self.eat(TokenType.PLUS)
elif token.type == TokenType.MINUS:
self.eat(TokenType.MINUS)
node = BinOp(left=node, op=token, right=self.term())
return node
def term(self):
"""term : factor ((MUL | INTEGER_DIV | FLOAT_DIV) factor)*"""
node = self.factor()
while self.current_token.type in (
TokenType.MUL,
TokenType.INTEGER_DIV,
TokenType.FLOAT_DIV,
):
token = self.current_token
if token.type == TokenType.MUL:
self.eat(TokenType.MUL)
elif token.type == TokenType.INTEGER_DIV:
self.eat(TokenType.INTEGER_DIV)
elif token.type == TokenType.FLOAT_DIV:
self.eat(TokenType.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 == TokenType.PLUS:
self.eat(TokenType.PLUS)
node = UnaryOp(token, self.factor())
return node
elif token.type == TokenType.MINUS:
self.eat(TokenType.MINUS)
node = UnaryOp(token, self.factor())
return node
elif token.type == TokenType.INTEGER_CONST:
self.eat(TokenType.INTEGER_CONST)
return Num(token)
elif token.type == TokenType.REAL_CONST:
self.eat(TokenType.REAL_CONST)
return Num(token)
elif token.type == TokenType.LPAREN:
self.eat(TokenType.LPAREN)
node = self.expr()
self.eat(TokenType.RPAREN)
return node
else:
node = self.variable()
return node
def parse(self):
"""
program : PROGRAM variable SEMI block DOT
block : declarations compound_statement
declarations : (VAR (variable_declaration SEMI)+)? procedure_declaration*
variable_declaration : ID (COMMA ID)* COLON type_spec
procedure_declaration :
PROCEDURE ID (LPAREN formal_parameter_list RPAREN)? SEMI block SEMI
formal_params_list : formal_parameters
| formal_parameters SEMI formal_parameter_list
formal_parameters : ID (COMMA ID)* COLON type_spec
type_spec : INTEGER | REAL
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
"""
node = self.program()
if self.current_token.type != TokenType.EOF:
self.error(
error_code=ErrorCode.UNEXPECTED_TOKEN,
token=self.current_token,
)
return node
###############################################################################
# #
# AST visitors (walkers) #
# #
###############################################################################
class NodeVisitor(object):
def visit(self, node):
method_name = 'visit_' + type(node).__name__
visitor = getattr(self, method_name, self.generic_visit)
return visitor(node)
def generic_visit(self, node):
raise Exception('No visit_{} method'.format(type(node).__name__))
###############################################################################
# #
# SYMBOLS, TABLES, SEMANTIC ANALYSIS #
# #
###############################################################################
class Symbol(object):
def __init__(self, name, type=None):
self.name = name
self.type = type
class VarSymbol(Symbol):
def __init__(self, name, type):
super().__init__(name, type)
def __str__(self):
return "<{class_name}(name='{name}', type='{type}')>".format(
class_name=self.__class__.__name__,
name=self.name,
type=self.type,
)
__repr__ = __str__
class BuiltinTypeSymbol(Symbol):
def __init__(self, name):
super().__init__(name)
def __str__(self):
return self.name
def __repr__(self):
return "<{class_name}(name='{name}')>".format(
class_name=self.__class__.__name__,
name=self.name,
)
class ProcedureSymbol(Symbol):
def __init__(self, name, params=None):
super().__init__(name)
# a list of formal parameters
self.params = params if params is not None else []
def __str__(self):
return '<{class_name}(name={name}, parameters={params})>'.format(
class_name=self.__class__.__name__,
name=self.name,
params=self.params,
)
__repr__ = __str__
class ScopedSymbolTable(object):
def __init__(self, scope_name, scope_level, enclosing_scope=None):
self._symbols = {}
self.scope_name = scope_name
self.scope_level = scope_level
self.enclosing_scope = enclosing_scope
def _init_builtins(self):
self.insert(BuiltinTypeSymbol('INTEGER'))
self.insert(BuiltinTypeSymbol('REAL'))
def __str__(self):
h1 = 'SCOPE (SCOPED SYMBOL TABLE)'
lines = ['\n', h1, '=' * len(h1)]
for header_name, header_value in (
('Scope name', self.scope_name),
('Scope level', self.scope_level),
('Enclosing scope',
self.enclosing_scope.scope_name if self.enclosing_scope else None
)
):
lines.append('%-15s: %s' % (header_name, header_value))
h2 = 'Scope (Scoped symbol table) contents'
lines.extend([h2, '-' * len(h2)])
lines.extend(
('%7s: %r' % (key, value))
for key, value in self._symbols.items()
)
lines.append('\n')
s = '\n'.join(lines)
return s
__repr__ = __str__
def log(self, msg):
if _SHOULD_LOG_SCOPE:
print(msg)
def insert(self, symbol):
self.log(f'Insert: {symbol.name}')
self._symbols[symbol.name] = symbol
def lookup(self, name, current_scope_only=False):
self.log(f'Lookup: {name}. (Scope name: {self.scope_name})')
# 'symbol' is either an instance of the Symbol class or None
symbol = self._symbols.get(name)
if symbol is not None:
return symbol
if current_scope_only:
return None
# recursively go up the chain and lookup the name
if self.enclosing_scope is not None:
return self.enclosing_scope.lookup(name)
class SemanticAnalyzer(NodeVisitor):
def __init__(self):
self.current_scope = None
def log(self, msg):
if _SHOULD_LOG_SCOPE:
print(msg)
def error(self, error_code, token):
raise SemanticError(
error_code=error_code,
token=token,
message=f'{error_code.value} -> {token}',
)
def visit_Block(self, node):
for declaration in node.declarations:
self.visit(declaration)
self.visit(node.compound_statement)
def visit_Program(self, node):
self.log('ENTER scope: global')
global_scope = ScopedSymbolTable(
scope_name='global',
scope_level=1,
enclosing_scope=self.current_scope, # None
)
global_scope._init_builtins()
self.current_scope = global_scope
# visit subtree
self.visit(node.block)
self.log(global_scope)
self.current_scope = self.current_scope.enclosing_scope
self.log('LEAVE scope: global')
def visit_Compound(self, node):
for child in node.children:
self.visit(child)
def visit_NoOp(self, node):
pass
def visit_BinOp(self, node):
self.visit(node.left)
self.visit(node.right)
def visit_ProcedureDecl(self, node):
proc_name = node.proc_name
proc_symbol = ProcedureSymbol(proc_name)
self.current_scope.insert(proc_symbol)
self.log(f'ENTER scope: {proc_name}')
# Scope for parameters and local variables
procedure_scope = ScopedSymbolTable(
scope_name=proc_name,
scope_level=self.current_scope.scope_level + 1,
enclosing_scope=self.current_scope
)
self.current_scope = procedure_scope
# Insert parameters into the procedure scope
for param in node.params:
param_type = self.current_scope.lookup(param.type_node.value)
param_name = param.var_node.value
var_symbol = VarSymbol(param_name, param_type)
self.current_scope.insert(var_symbol)
proc_symbol.params.append(var_symbol)
self.visit(node.block_node)
self.log(procedure_scope)
self.current_scope = self.current_scope.enclosing_scope
self.log(f'LEAVE scope: {proc_name}')
def visit_VarDecl(self, node):
type_name = node.type_node.value
type_symbol = self.current_scope.lookup(type_name)
# We have all the information we need to create a variable symbol.
# Create the symbol and insert it into the symbol table.
var_name = node.var_node.value
var_symbol = VarSymbol(var_name, type_symbol)
# Signal an error if the table already has a symbol
# with the same name
if self.current_scope.lookup(var_name, current_scope_only=True):
self.error(
error_code=ErrorCode.DUPLICATE_ID,
token=node.var_node.token,
)
self.current_scope.insert(var_symbol)
def visit_Assign(self, node):
# right-hand side
self.visit(node.right)
# left-hand side
self.visit(node.left)
def visit_Var(self, node):
var_name = node.value
var_symbol = self.current_scope.lookup(var_name)
if var_symbol is None:
self.error(error_code=ErrorCode.ID_NOT_FOUND, token=node.token)
def visit_Num(self, node):
pass
def visit_UnaryOp(self, node):
pass
###############################################################################
# #
# INTERPRETER #
# #
###############################################################################
class Interpreter(NodeVisitor):
def __init__(self, tree):
self.tree = tree
self.GLOBAL_MEMORY = {}
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
def visit_BinOp(self, node):
if node.op.type == TokenType.PLUS:
return self.visit(node.left) + self.visit(node.right)
elif node.op.type == TokenType.MINUS:
return self.visit(node.left) - self.visit(node.right)
elif node.op.type == TokenType.MUL:
return self.visit(node.left) * self.visit(node.right)
elif node.op.type == TokenType.INTEGER_DIV:
return self.visit(node.left) // self.visit(node.right)
elif node.op.type == TokenType.FLOAT_DIV:
return float(self.visit(node.left)) / float(self.visit(node.right))
def visit_Num(self, node):
return node.value
def visit_UnaryOp(self, node):
op = node.op.type
if op == TokenType.PLUS:
return +self.visit(node.expr)
elif op == TokenType.MINUS:
return -self.visit(node.expr)
def visit_Compound(self, node):
for child in node.children:
self.visit(child)
def visit_Assign(self, node):
var_name = node.left.value
var_value = self.visit(node.right)
self.GLOBAL_MEMORY[var_name] = var_value
def visit_Var(self, node):
var_name = node.value
var_value = self.GLOBAL_MEMORY.get(var_name)
return var_value
def visit_NoOp(self, node):
pass
def visit_ProcedureDecl(self, node):
pass
def interpret(self):
tree = self.tree
if tree is None:
return ''
return self.visit(tree)
def main():
parser = argparse.ArgumentParser(
description='SPI - Simple Pascal Interpreter'
)
parser.add_argument('inputfile', help='Pascal source file')
parser.add_argument(
'--scope',
help='Print scope information',
action='store_true',
)
args = parser.parse_args()
global _SHOULD_LOG_SCOPE
_SHOULD_LOG_SCOPE = args.scope
text = open(args.inputfile, 'r').read()
lexer = Lexer(text)
try:
parser = Parser(lexer)
tree = parser.parse()
except (LexerError, ParserError) as e:
print(e.message)
sys.exit(1)
semantic_analyzer = SemanticAnalyzer()
try:
semantic_analyzer.visit(tree)
except SemanticError as e:
print(e.message)
sys.exit(1)
interpreter = Interpreter(tree)
interpreter.interpret()
if __name__ == '__main__':
main()
评论系统采用 utterances ,加载有延迟,请稍等片刻。