CS61A-Spring2022

Lab 11: Interpreters

起始文件

下载 lab11.zip 。在该压缩包中,你将找到本实验中问题的起始文件,以及 Ok 自动评分器的副本。

说明

Scheme 项目 中,你将为 Scheme 实现一个 Python 解释器。

解释 Scheme 表达式的过程的一部分是能够将一串 Scheme 代码作为我们的输入解析到我们的解释器对 Scheme 表达式的内部 Python 表示中。由于所有的 Scheme 表达式都是 Scheme 列表(因此也是链表),我们使用 Pair 类来表示所有的 Scheme 表达式,它的行为就像一个链表。 这个类在 pair.py 中定义。

当给定一个诸如 (+ 1 2) 的输入时,有两个主要步骤是我们要做的。

解释表达式的第一部分是采取输入并将其分解为每个组成部分。在我们的例子中,我们想把 (+12) 中的每一个都当作一个单独的标记,然后我们可以找出如何表示。这被称为 词法分析 ,在 scheme_tokens.pytokenize_lines 函数中已经为你实现。

现在我们已经把输入的内容分解成了它的组成部分,我们想把这些 Scheme tokens 变成我们的解释器对它们的内部表示。这就是所谓的 语法分析 ,在 scheme_reader.pyscheme_readread_tail 函数中进行。

主要的想法是,在我们进行任何计算或在操作数上调用运算符之前,我们首先要认识到输入代表什么,等等。

这个实验的目标是研究解析的各个部分;虽然在这个实验和这个项目中,我们关注的是 Scheme 语言,但我们如何设置 Scheme 解释器的一般想法也可以适用于其他语言 —— 比如 Python 本身!

必要的问题

请看 介绍 ,了解本实验的背景。

Part 1

Context

我们在 Buffer 实例中存储准备被解析的标记。例如,一个包含输入 (+ (2 3)) 的缓冲区会有 '(''+''('23')'')' 的标记。

在这一部分,我们将实现 Buffer 类。

Buffer 提供了一种访问跨行标记序列的方法。

它的构造函数需要一个迭代器,称为“source”,每次查询都会返回下一行标记的列表,直到它的行数用尽。

例如, source 可以被定义如下:

line1 = ['(', '+', 6, 1, ')']      # (+ 6 1)
line2 = ['(', 'quote', 'A', ')']  # (quote A)
line3 = [2, 1, 0]                 # 2 1 0
input_lines = [line1, line2, line3]
source = iter(input_lines)

实际上, Buffer 结合了从其源头返回的序列,然后通过其 pop_first 方法一次提供其中的项目,只有在需要时才调用 source 的更多项目序列。

此外, Buffer 提供了一个 current 实例属性,可以查看下一个要供应的项目,而不需要移过它。

Problem 1

重要提示: 这部分的代码应该放在 buffer.py 中。

你在这部分的工作是实现 Buffer 类的 create_generator__init__pop_first 方法。

注意: 对于这个问题,你可能想使用内置的函数 next 及其默认参数。这里有一个例子:

>>> iterator = iter([1, 2])
>>> next(iterator) # Here, there is no default arg given.
1
>>> next(iterator, 5) # Here, there is a default arg given, but not used.
2
>>> next(iterator, 5) # The iterator is exhausted, so next returns default.
5

关于 next 的更多信息,请随时阅读 next Python 文档

create_generator

实现 create_generator ,这是一个生成器函数,它接收 source ,是一个行的迭代器,每个行是一个包含标记的列表。

这个函数应该每次从 source 的某一行产生一个标记。如果某行没有更多的标记,那么它应该产生 EOL_TOKEN (一个代表行末标记的对象)。

如果整个 source 中没有更多的标记,它就不应该有更多的 yields 。在这种情况下,如果你在这个函数的生成器上调用 next ,就会产生一个 StopIteration ,因为不再有适用的 yield 了。

你可以在你实现的 __init__pop_first 中引用这个函数。

记住,生成器函数可以如下使用:

>>> gen = some_generator_function()
>>> next(gen)
# Returns the first yield from some_generator_function
>>> next(gen)
# Returns the next yield from some_generator_function

__init__

__init__ 接收输入源 source 。你应该定义以下实例属性:

如果你愿意,你可以定义更多你认为合适的实例属性。

pop_first

实现 pop_first ,其作用如下:

测试你的代码

使用 Ok 来测试你的代码:

python3 ok -q buffer

Part 2

Internal Representations

阅读器将把 Scheme 代码解析成 Python 值,其表示方法如下:

输入示例 Scheme 表达式类型 我们的内部表述
`scm> 1` Numbers Python 的内置 `int` 和 `float` 值
`scm> x` Symbols Python 的内置 `string` 值
`scm> #t` Booleans (`#t`, `#f`) Python 内置的 `True`、 `False` 值
`scm> (+ 2 3)` Combinations `Pair` 类的实例,定义在 `scheme_reader.py` 中。这个例子被表示为: `Pair('+', Pair(2, Pair(3, nil)))` 。
`scm> nil` `nil` `nil` 对象,定义在 `scheme_reader.py` 中。

当我们在这里提到组合时,我们指的是调用表达和特殊形式。

Problem 2

重要提示: 你这部分的代码应该放在 scheme_reader.py 中。

重要提示: 在解锁这个问题时,如果从 Buffer 实例中产生的 token 是 EOL_TOKEN ,它将根据 EOL_TOKEN 类的 __repr__ 函数来显示。具体来说,你会得到:

>>> EOL_TOKEN
This is a token representing the end of a line.

你在这一部分的工作是编写解析功能,它由两个相互递归的函数组成: scheme_readread_tail 。每个函数都接收一个 src 参数,它是一个 Buffer 实例。

简而言之, scheme_read 返回缓冲区中下一个完整的表达式, read_tail 返回缓冲区中剩余的列表或 Pair 。这两个函数都会改变缓冲区,删除已经处理过的标记。

两个函数的行为都取决于当前在 src 中的第一个标记。它们应该按如下方式实现:

scheme_read:

read_tail:

使用 Ok 来解锁和测试你的代码:

python3 ok -q scheme_read -u
python3 ok -q scheme_read

Problem 3

重要提示: 这一部分的代码应该放在 scheme_reader.py 中。

在这个问题上,你的任务是完成 scheme_read 的实现,让这个函数现在能够处理带引号的表达式。

在 Scheme 中,像 '<expr> 这样的带引号的表达式等同于 (quote <expr>) 。这意味着我们需要将 ' 后面的表达式(你可以通过递归调用 scheme_read 得到)包装成 quote 的特殊形式,它是一个 Scheme 列表(和所有的特殊形式一样)。

在我们的表述中,一个 Pair 代表一个 Scheme 列表。因此,你应该把 ' 后面的表达式包在 Pair 中。

例如, 'bagel ,或者说 ["'", "bagel"] 在被标记后,应该表示为 Pair('quote', Pair('bagel', nil))'(1 2)["'", "(", 1, 2, ")"] 应该表示为 Pair('quote', Pair(Pair(1, Pair(2, nil)), nil))

使用 Ok 来解锁和测试你的代码:

python3 ok -q quote -u
python3 ok -q quote

运行你的解析器

现在你的解析器已经完成了,你可以通过运行 读取-计算-打印 循环来测试:

python3 scheme_reader.py --repl

每次你在提示符中输入一个值,解析后的表达式的 strrepr 值都会被打印出来。你可以尝试以下输入方式:

read> 42
str : 42
repr: 42
read> nil
str : ()
repr: nil
read> (1 (2 3) (4 (5)))
str : (1 (2 3) (4 (5)))
repr: Pair(1, Pair(Pair(2, Pair(3, nil)), Pair(Pair(4, Pair(Pair(5, nil), nil)), nil)))

要退出解释器,你可以输入 exit

提交

请确保提交这项作业:

python3 ok --submit