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)