[2021 spring] CS61A Lab 11: Interpreters
Lab 11: https://inst.eecs.berkeley.edu/~cs61a/sp21/lab/lab11/
任务: 构建PyCombinator解释器,完成expr.py
目录
Q1:序言
先试着理解已经写好的部分代码:
-
repl.py
包含REPL循环的逻辑,重复读取(read)表达式作为用户输入,计算(evaluate)并打印(print out)它们的值。 -
reader.py
包含解释器的读取器。函数read
调用函数tokenize
和read_expr
,将表达式字符串转换为一个Expr
对象。 -
expr.py
包含解释器对表达式和值的表示。Expr
和Value
的子类封装了PyCombinator语言中的所有表达式和值的类型。global_env
字典包含了原始函数的信息。
REPL部分:
prologue_reader:
prologue_expr:
Q2: Evaluating Names
查询字典,字典中存在则返回对应的值,不存在则返回None。
# class Name
def eval(self, env):
"""
>>> env = {
... 'a': Number(1),
... 'b': LambdaFunction([], Literal(0), {})
... }
>>> Name('a').eval(env)
Number(1)
>>> Name('b').eval(env)
LambdaFunction([], Literal(0), {})
>>> print(Name('c').eval(env))
None
"""
"*** YOUR CODE HERE ***"
if self.var_name in env:
return env[self.var_name]
Q3: Evaluating Call Expressions
阅读Value类,Pycombinator值的类型包括numbers,lambda functions, primitive functions。
- self.operator是Name类型,调用Name.eval方法。
- self.operands列表包含numbers类型和函数(原始函数和lambda函数)类型。创建一个新列表存放参数,如果operand是数字类型,直接添加到列表中;否则,调用eval方法将返回值添加到列表中。
- 调用apply方法,参数为2中列表。
# class CallExpr
def eval(self, env):
"""
>>> from reader import read
>>> new_env = global_env.copy()
>>> new_env.update({'a': Number(1), 'b': Number(2)})
>>> add = CallExpr(Name('add'), [Literal(3), Name('a')])
>>> add.eval(new_env)
Number(4)
>>> new_env['a'] = Number(5)
>>> add.eval(new_env)
Number(8)
>>> read('max(b, a, 4, -1)').eval(new_env)
Number(5)
>>> read('add(mul(3, 4), b)').eval(new_env)
Number(14)
"""
"*** YOUR CODE HERE ***"
eval_operands = []
for operand in self.operands[:]:
if isinstance(operand, Number):
eval_operands.append(operand)
else:
eval_operands.append(operand.eval(env))
return self.operator.eval(env).apply(eval_operands)
Q4: Applying Lambda Functions
LambdaFunction类的三个实例属性:parameters、body、parent。
- 创建parent environment的copy。
- 将parameters和arguments更新到copy中,形参和实参对应。
- evaluate body。
# class LambdaFunction
def apply(self, arguments):
"""
>>> from reader import read
>>> add_lambda = read('lambda x, y: add(x, y)').eval(global_env)
>>> add_lambda.apply([Number(1), Number(2)])
Number(3)
>>> add_lambda.apply([Number(3), Number(4)])
Number(7)
>>> sub_lambda = read('lambda add: sub(10, add)').eval(global_env)
>>> sub_lambda.apply([Number(8)])
Number(2)
>>> add_lambda.apply([Number(8), Number(10)]) # Make sure you made a copy of env
Number(18)
>>> read('(lambda x: lambda y: add(x, y))(3)(4)').eval(global_env)
Number(7)
>>> read('(lambda x: x(x))(lambda y: 4)').eval(global_env)
Number(4)
"""
if len(self.parameters) != len(arguments):
raise TypeError("Oof! Cannot apply number {} to arguments {}".format(
comma_separated(self.parameters), comma_separated(arguments)))
"*** YOUR CODE HERE ***"
new_env = self.parent.copy()
for parameter, argument in zip(self.parameters, arguments):
new_env[parameter] = argument
return self.body.eval(new_env)
Q5: Handling Exceptions
repl.py,使用try...except...处理异常。
针对算术异常:
except (OverflowError, ZeroDivisionError) as e:
print('Arithmetic error!')
print(type(e).__name__ + ':', e)