This commit is contained in:
Wankupi
2023-10-30 19:34:40 +08:00
commit 2e29af68b3
107 changed files with 7880 additions and 0 deletions

86
docs/antlr_guide.md Normal file
View File

@ -0,0 +1,86 @@
# ANTLR in Python Interpreter
## 配置教程
### 配置 Antlr C++ 运行环境
Python 解释器采用 Antlr 作为前端语法分析器,其中核心代码编译时间较长,因此我们提前编译好了 Antlr 的运行环境,这样你的程序在编译时就不需要再编译 Antlr 的运行环境了。
为了在你自己的电脑上也使用 Antlr 运行环境,你需要将 Antlr 编译好的运行环境安装到你的电脑上。
`antlr-runtime_4.13.1_amd64.deb` 文件下载到 WSL 中,打开文件所在目录,执行以下命令安装:
```shell
sudo apt install ./antlr-runtime_4.13.1_amd64.deb
```
在这个包中,含有 Antlr 4.13.1 的动态链接库、静态链接库以及头文件,如果不装这个包,
将导致你的程序在编译时找不到 Antlr 的头文件和动态链接库,从而编译失败。
使用 Archlinux 的同学可以直接使用以下命令安装运行环境:
```shell
pacman -S antlr4-runtime
```
如有在其他环境下编程的同学(比如 Windows、Mac 和除 Debian,Arch 之外的 Linux 系统),请联系助教。
### 生成语法树
#### 使用 VScode 插件(推荐)
首先在 Windows 环境下安装插件(注意,不要在 WSL 环境下安装):
![vscode-plugin](https://github.com/ACMClassCourse-2023/Python-Interpreter-2023/blob/main/docs/vscode-plugin.png)
安装后,点击卸载旁的箭头,安装 2.3.1 版本。
![vscode-install](https://github.com/ACMClassCourse-2023/Python-Interpreter-2023/blob/main/docs/vscode-install.png)
安装完后重新加载。打开 `Python3.g4` 文件,右边会出现对应插件的图标,点击,等待其中的 PARSER RULES 等部分加载完毕。
![vscode-antlr](https://github.com/ACMClassCourse-2023/Python-Interpreter-2023/blob/main/docs/vscode-antlr.png)
接下来配置运行文件。点击左侧的运行和调试,创建 `launch.json` 文件,并写入
```javascript
{
"version": "2.3.1",
"configurations": [
{
"name": "Debug ANTLR4 grammar",
"type": "antlr-debug",
"request": "launch",
"input": "./a.txt", // 输入文件(即你要运行的 Python 代码文件)
"grammar": "./resources/Python3Parser.g4", // 语法文件
"startRule": "file_input", // 语法入口规则,我们的公式语法入口规则是 file_input
"printParseTree": true, // 是否 打印/可视化 parse tree
"visualParseTree": true
}
]
}
```
最后打开要运行的文件,在左侧的运行和调试中,点击运行即可生成,如下图所示。
![vscode-antlr-result](https://github.com/ACMClassCourse-2023/Python-Interpreter-2023/blob/main/docs/vscode-antlr-result.png)
#### 使用 Clion 插件
由于本次 `.g4` 文件的特性,目前 ANTLR 插件只能支持不带 `INDENT``DEDENT` 规则的解释。
首先在插件市场中找到插件:
![plugin-market](https://github.com/ACMClassCourse-2023/Python-Interpreter-2023/blob/main/docs/plugin-market.png)
安装后,右键 `.g4` 中的 `return_stmt` 或任何不包含 `INDENT``DEDENT` 的规则,点击 `test rule`
![right-click](https://github.com/ACMClassCourse-2023/Python-Interpreter-2023/blob/main/docs/right-click.png)
之后在屏幕下方的 `antlr-preview` 中,左侧是待测试的代码,右侧是依据代码生成的语法树结构图。
## ANTLR 是什么
ANTLR全名ANother Tool for Language Recognition是基于 LL(\*)算法实现的语法解析器生成器parser generator用 Java 语言编写使用自上而下top-down的递归下降 LL 剖析器方法。
ANTLR 可以将输入的代码转化成与之对应的**树形结构**,即语法树,以便后续程序操作。按照上面的配置操作,即可得到一份 `Python` 代码对应的语法树。

239
docs/grammar.md Normal file
View File

@ -0,0 +1,239 @@
## Specific Python
### I. 程序结构
按照控制流逐行执行。
### II. 语法规则
#### 1. 字符集合
ASCII 编码,区分大小写,中文字符是**未定义**的,合法字符集如下:
- **标识符**包括变量标识符、函数标识符26 个小写英语字母26 个大写英语字母,数码 `0``9`,下划线 `_`
- **标准运算符**:加号 `+`,减号 `-`,乘号 `*`,浮点除 `/`,整除`//`,取模 `%`
- **关系运算符**:大于 `>`,小于 `<`,大于等于 `>=`,小于等于 `<=`,不等于 `!=`,等于 `==`
- **增量运算符**:加法 `+=`,减法 `-=`,乘法 `*=`,浮点除 `/=`,整除 `//=`,取模`%=`
- **赋值运算符**:赋值 `=`
- **下标运算符**:取下标对象 `[]`
- **优先级运算符**:括号 `()`
- **分隔符**:逗号 `,`
- **特殊符号**:空格 ` `,换行符 `\n`,制表符 `\t`,注释标识符 `#`,字符串标识符 `"``'`
#### 2. 关键字
```python
None
True
False
def
return
break
continue
if
elif
else
while
or
and
not
```
关键字不可作为变量名或函数名。
#### 3. 空白字符
**空格**、**制表符**在源文件中可以区分词素 (Token),同时在一行的开头可以表示缩进。
**换行符**表示新的语句的开始。
缩进在 Python 用于识别代码块,相关问题将在 Parser 中使用 Antlr4 解决,无需考虑相关的实现。
#### 4. 注释
`#` 开始到本行结束的内容都会被作为注释。
#### 5. 标识符
标识符的第一个字符必须是英文字母,第二个字符开始可以是英文字母、数字或者下划线。
标识符区分大小写。长度超过 $64$ 个字符的标识符是**未定义**的。
#### 6. 常量
- 逻辑常量:`True` 为真,`False` 为假
- 整数常量:
- 整数常量以十进制表示,非十进制的表示为**未定义**行为
- 整数常量不设负数,负数可以由正数取负号得到
- Python 中,整数的范围是没有限制的,此处同理,这意味着你必须实现高精度整数
- 首位为 $0$ 的整数常量是**未定义**的
- 浮点数常量:
- 浮点数常量以十进制表示
- 浮点数常量不设负数,负数可以由正数取负号得到
- 拥有前导 $0$ 的浮点数为**未定义**的,如`0001.1`
- 含有 e 的浮点数为**未定义**的,如 `1e5`
- 以 . 开头的浮点数为**未定义**的,如 `.123`
- 字符串常量
- 字符串常量是由双引号 `"` 或单引号 `'` 括起来的字符串
- 可以由两个字符串拼接而形成新的字符串常量。如 `"123""456"` 相当于 `"123456"`
- 字符串中的所有字符必须是可示字符 (printable character) 或空格中的一种
- 空值常量:`None` 用来表示变量没有指向任何值
### 7. 数据类型
- `bool`:只有 `True``False`
- `int`:高精度整数
- `float`: 与 C++ 中的 `double` 一致
- `str`:字符串,**不可修改!**
- 元组: 具体表现请参考 python (待完善)
### 8. 表达式
#### 8.1. 基础表达式
基础表达式包括单独出现的常量、变量、函数调用和数组访问。
#### 8.2. 算术表达式
合法的算术运算符包括:`+`, `-`, `*`, `/`, `//`, `%`,具体行为可以参照 C++,特殊行为有:
- 字符串运算符
- `str + str` 表示字符串的拼接
- `str * int` 表示字符串的重复
- `str <= str` 表示字符串的比较,比较规则同 C++ `std::striing``>=`, `==`等同理
- 除法
- `/` 表示浮点除,即计算结果为浮点数
- `//` 表示整除,即计算结果为整数。注意无论正负皆**向下取整**,例如 `-5 // 3 = -2`
- `%` 表示模运算,无论模数的正负,皆定义为:`a % b = a - (a // b) * b`
- 连续比较
- 存在 `1<2>3` 这样连续的关系运算符,处理方法是将其拆为相邻的比较并用 `and` 连接但**每个值最多只计算一次**
-`a()<b()<c()`,等价于先翻译成 `a()<b() and b()<c()` 再进行计算, `a(),b(),c()` 最多只调用一次(因为短路运算符)
- 逻辑运算符
- `and` 等价于 C++ 中的 `&&``or` 等价于 C++ 中的 `||` `not` 等价于 C++ 中的 `!`
- `and``or` 为**短路**运算符
- **隐式转化**
- 运算时如果两个运算数类型不一致,需转化成相同类型之后再进行运算
- 具体转化规则参考 C++
不包含在特殊行为和 C++ 标准中的行为均为**未定义**行为。
#### 8.3. 赋值表达式
- 语法与具体行为均可以参照 C++,并无特殊差异
- 给一个变量赋值的意义是将这个变量指向右值,右值不管类型
- **与标准 Python 不同,全局变量的生效范围是全部范围**(不用 global 关键字即可访问)
- 局部变量的生效范围是在当前语句块(被缩进和取消缩进包起来的部分),具体局部变量和全局变量划分规则和 C++ 一样
- **只有函数的定义会产生新的作用域**,作用域的产生请参考 Python
-`a=1,a=”123”,a=1.1` 这三条语句依次执行,再输出 `a`,结果是 `1.1`,允许变量遮蔽
- 可以给多变量赋值,如 `a,b=1,2` 意思是 `a=1,b=2` 依次执行,此处或与标准 Python **不同**
- 增量赋值即为普通算术运算的简化表达,具体行为参考 C++
#### 8.4. 运算符优先级
从低到高依次为(其中处在相同行的运算符优先级相同)
```python
=
or
and
not
< > <= >= == !=
+ -
* / // %
()
```
### 9. 语句
#### 9.1. 变量定义/赋值语句
- 单变量赋值:`var = value`
- 连续赋值:`var_1 = var_2 = ... = value`
参考[赋值表达式](####8.3. 赋值表达式)。
#### 9.2. 表达式语句
参考[表达式](###8. 表达式)
#### 9.3. 条件语句
```python
if expression_1:
# code block
elif expression_2:
# code block
else expression_3:
# code block
```
`elif` 相当于 `else if``else` 可以没有。
#### 9.4. 循环语句
```python
while expression:
# code block
```
#### 9.5. 跳转语句
- 包括 `return``break`,和 `continue`
- 具体行为参考 C++
### 10. 函数
#### 10.1. 函数定义
```python
def func(parameters):
# code block
```
- 参数列表如 `a, b, c`,变量名之间用逗号分隔,可以为空
- 有些变量可以有默认值,但是都必须出现在无默认值的变量后面
- 函数可以有返回值,也可以没有返回值,无需显示声明。返回值可以为多变量,如 `return a, b`
#### 10.2. 函数调用
```python
func(parameters)
```
- 函数调用必须出现在该函数定义后。
- 参数有两种形式keyword 和 positional
- Keyword argument 比如 `foo(a=1,b=2)` 表示传入参数 `a` 的值为 `1``b` 的值 `2`
- Positional argument 是指 `foo(1,2)` 这样按照出现顺序指代参数
- 若一个参数列表中同时有两种参数形式,那么 positional argument 必须出现在 keyword argument 之前。如 `foo(1,b=2)`
- 数据保证递归层数不超过 $2000$ 层
### 11. 内建函数
- `print`:输出,可以有任意个参数,逐个输出,中间用空格分隔。输出后换行。输出 `float` 保留 $6$ 位小数。输出字符串不要输出前后面的引号。如 `print("123",1.0)` 请输出 `123 1.000000`
- `int`:将 `float``bool``str` 转成 `int`
- `float`: 将 `int``bool``str` 转成 `float`
- `str``int``float``bool` 转成 `str`
- `bool``int``float``str` 转成 `bool`。对于 `str`,如果是 `""` 则为 `False`,否则为 `True`
- 转型类函数都只有一个参数。

View File

@ -0,0 +1,2 @@
# 实现细节

BIN
docs/plugin-market.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 679 KiB

BIN
docs/right-click.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

14
docs/suggestions.md Normal file
View File

@ -0,0 +1,14 @@
### 一些小建议
0. 有搞不明白的问题找助教啦~
1. git仓库中放置测试点可能使你的仓库克隆时间增长导致评测变慢。
推荐将本仓库下载到本地后,只在你的仓库中放置必要的文件。
2. 建议使用多文件编程。将整个解释器分成多个模块,分别写在不同的文件中。
3. 清楚调用的函数的复杂度。
4. 你可以先 `using int2048 = long long;` 来方便前期编写。
TO_BE_CONTINUED

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

BIN
docs/vscode-antlr.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
docs/vscode-install.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
docs/vscode-plugin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

182
docs/workflow_details.md Normal file
View File

@ -0,0 +1,182 @@
# 解释器作业的完成流程
## Step 1. 配置环境
见文档 [antlr_guide.md](antlr_guide.md)。
## Step 2. 阅读 "./resources/Python3Parser.g4"
**阅读 `.g4` 文件需要一定的正则表达式基础。**
如果你不会正则表达式,可以参考 [正则表达式 - 维基百科](https://zh.wikipedia.org/wiki/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F)。
我们假设有这样的语法规则(并不是我们这次作业的一部分):
```
plus: NUMBER '+' NUMBER;
NUMBER:[0-9]+;
ADD:'+';
```
## Step 3. 阅读 "./generated/Python3Parser.h"
你可以看到以下代码(对应着上面的语法规则):
```c++
//...............
class PlusContext : public antlr4::ParserRuleContext {
public:
PlusContext(antlr4::ParserRuleContext *parent, size_t invokingState);
virtual size_t getRuleIndex() const override;
std::vector<antlr4::tree::TerminalNode *> NUMBER();
antlr4::tree::TerminalNode* NUMBER(size_t i);
antlr4::tree::TerminalNode* ADD();
virtual void enterRule(antlr4::tree::ParseTreeListener *listener) override;
virtual void exitRule(antlr4::tree::ParseTreeListener *listener) override;
virtual std::any accept(antlr4::tree::ParseTreeVisitor *visitor) override;
}
//...............
```
因为在上述的 `plus` 语法中,`NUMBER` 一定要出现至少一次,所以 `PlusContext` 有以下两个函数:
```c++
std::vector<antlr4::tree::TerminalNode *> NUMBER();
antlr4::tree::TerminalNode* NUMBER(size_t i);
```
第一个函数返回一个 `vector`,包含了指向所有 `NUMBER` 的指针,第二个函数返回指向第 i 个 `NUMBER` 的指针,从 0 开始。
因为 `ADD` 只在 `plus` 中出现了一次,所以它只有以下函数,返回指向唯一的 `ADD` 的指针:
```c++
antlr4::tree::TerminalNode* ADD()
```
对于一个 `temrinal node`,有以下方法:
```c++
//...............
std::string toString()
Token* TerminalNodeImpl::getSymbol()
/*
* for example, consider:
* antlr4::tree::TerminalNode *it;
* it->toString() returns the string, for example, "123456" or "a"
* (so you need to converse (std::string)"123456" to (int)123456)
* it->getSymbol()->getTokenIndex() returns where this word is in the whole input.
*/
//...............
```
## Step 4. 完成 "./src/Evalvisitor.h"
在这一步中,所要做的就是补全相关代码:
```c++
//...............
std::any visitPlus(Python3Parser::PlusContext *ctx)
{
/*
* TODO
* the pseudo-code is:
* return visit(ctx->NUMBER(0))+visit(ctx->NUMBER(1));
*/
}
//...............
```
当写:
```c++
visit(ctx->NUMBER(0))
```
等价于写:
```c++
visitAtom(ctx->NUMBER(0))
```
所以我们只需要用 `visit` 函数来访问各种结点,而不是用 `visitBalabala`,想想为什么?
## Step 5. 编译程序
输入以下代码即可:
```sh
cmake -B build
cmake --build build
```
如果你不会使用 `cmake`,你可以借助于 `Clion` 来实现,如果你不知道这步如何操作,请询问助教。
## 神奇的 std::any
关于这个类,你只需要会一个方法:`std::any_cast`。
譬如说,如果有以下的语法规则:
```
plus: atom '+' atom;
atom: NUMBER | STRING+;
NUMBER:[0-9]+;
STRING:[A-Z]+;
ADD:'+';
```
在 Parser.h 中的 `Context` 长这样:
```c++
//...............
class PlusContext : public antlr4::ParserRuleContext {
public:
PlusContext(antlr4::ParserRuleContext *parent, size_t invokingState);
virtual size_t getRuleIndex() const override;
std::vector<AtomContext*> atom();
AtomContext* atom(size_t i);
antlr4::tree::TerminalNode* ADD()
virtual void enterRule(antlr4::tree::ParseTreeListener *listener) override;
virtual void exitRule(antlr4::tree::ParseTreeListener *listener) override;
virtual std::any accept(antlr4::tree::ParseTreeVisitor *visitor) override;
}
//notice that the atom is AtomContext* type rather than a TerminalNode* type.
//an easy way to tell the difference is: capital letter-TerminalNode; xxxContext otherwise
//...............
```
那么相应的代码就长这样:
```c++
//...............
std::any visitPlus(Python3Parser::PlusContext *ctx)
{
auto ret1 = visit(ctx->NUMBER());
auto ret2 = visit(ctx->NUMBER());
if (auto v1int = std::any_cast<int*>(ret1), v2int = std::any_cast<int*>(ret2);
v1int && v2int)
return *v1int + *v2int;
else if (auto v1str = std::any_cast<string*>(ret1), v2str = std::any_cast<string*>(ret2);
v1str && v2str)
return *v1str + *v2str;
else
throw(std::string("unsupported operand type(s) for +: ") + ret1.type().name() + " + " + ret2.type().name());//no need
}
//...............
```
我们保证测试文件的语法都是正确的,所以后两行实则是不需要的。
`any_cast` 模板函数可以直接将 `any` 类型转换为你想要的类型,但是如果转换失败,它会直接抛出异常。
而在上文的代码中,我们向模板填入的类型是目标类型的引用,所以如果类型不匹配、转换失败,它会返回 `nullptr`。
在 OOP 课程中,你们将会学习构造函数和析构函数。所以你们最好是在理解 `std::any` 是如何构造与析构的基础上,进行编程。通过阅读[cppreference](https://zh.cppreference.com/w/cpp/utility/any)来理解。如果这对于你们来说太过困难,请求助助教。
https://www.cnblogs.com/mangoyuan/p/6446046.html
https://www.cnblogs.com/xiaoshiwang/p/9590029.html
搜索 "C++11 traits" 来获得更多信息。