实验介绍¶
Task 1 · 词法分析 / Lexical Analysis
"The limits of my language mean the limits of my world."
(我语言的局限,即是我世界的局限。)
先让输出接近 clang,再追求实现优雅
Task1 最有效的做法是不断对照标准输出,把 token、位置信息和额外标志逐项补齐。 先建立正确性,再考虑代码结构是否足够整洁。
任务描述¶
本次实验中需要实现一个简单的词法分析器,它接收经过预处理的源代码,输出词法分析的结果。
/YatCC/test/cases 路径下存放了本实验所有的测试用源代码文件。以
/YatCC/test/cases/functional-0/000_main.sysu.c 为例,其内容如下所示:
int main(){
return 3;
}
其经过 clang 预处理后,我们可以在
/YatCC/build/test/task0/functional-0/000_main.sysu.c 得到以下内容:
# 1 "./functional-0/000_main.sysu.c"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 384 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "./functional-0/000_main.sysu.c" 2
int main(){
return 3;
}
相比源代码,多了一些以 # 开头的行。这些行是 clang
在预处理过程中插入的,称之为行标记(Linemarkers)。
行标记不是源码内容
Task1 最容易忽略的就是这些以 #
开头的预处理信息。它们虽然不是待识别的普通 token,但却决定了后续文件名、行号和列号能否对齐。如果这里处理思路错了,后面的 Loc 基本都会跟着错。
行标记的基本格式是 # linenum filename flags,它的含义是下一行来自于
filename 文件的第 linenum 行。例如 # 1 "./functional-0/000_main.sysu.c 2"
表示下一行 int main(){ 来自于 ./functional-0/000_main.sysu.c 文件的第 1 行。
至于 flags 的含义以及更多行标记的细节可以参考
这里,此处不再赘述。
用 clang 进一步对预处理后的源代码进行词法分析,其结果如下,该信息可以在
/YatCC/build/test/task1/functional-0/000_main.sysu.c/answer.txt 找到:
int 'int' [StartOfLine] Loc=<./functional-0/000_main.sysu.c:1:1>
identifier 'main' [LeadingSpace] Loc=<./functional-0/000_main.sysu.c:1:5>
l_paren '(' Loc=<./functional-0/000_main.sysu.c:1:9>
r_paren ')' Loc=<./functional-0/000_main.sysu.c:1:10>
l_brace '{' Loc=<./functional-0/000_main.sysu.c:1:11>
return 'return' [StartOfLine] [LeadingSpace] Loc=<./functional-0/000_main.sysu.c:2:5>
numeric_constant '3' [LeadingSpace] Loc=<./functional-0/000_main.sysu.c:2:12>
semi ';' Loc=<./functional-0/000_main.sysu.c:2:13>
r_brace '}' [StartOfLine] Loc=<./functional-0/000_main.sysu.c:3:1>
eof '' Loc=<./functional-0/000_main.sysu.c:3:2>
其中每行开头的单词是后面单引号包裹的词法单元的别名, [StartOfLine]
表示该词法单元位于所在行的行首, [LeadingSpace] 表示该词法单元前面存在空格。
Loc 中的内容则是代表词法单元在源代码文件中所处的位置。例如
Loc=<./functional-0/000_main.sysu.c:1:1> 就表示这个词法单元位于
./functional-0/000_main.sysu.c 文件的第 1 行第 1 列(都是从 1 开始计数)。
本次实验的目标,就是自己实现一个词法分析器,输入经过预处理的源代码,能够得到和上面一样的结果!同学们需要完成的工作总结如下:
- 补充更多的
token词法规则定义 - 提取
Loc信息,包括简单的token所在的源文件路径,行号以及列号 - 判断
[StartOfLine]和[LeadingSpace]是否需要输出
同学们可能会想,实现这样的一个词法分析器的工程量应该很大吧?设计实验以及编写文档的助教和大家的想法是一样的!所以肯定不会让大家从零开始实现一个词法分析器。在
task1 中我们提供了 flex 和 antlr 两种框架来实现我们的词法分析器,其中
antlr 在 task2
中还会继续用到。同学们可以自由选择自己喜欢的框架进行实现,或者让我们提供的大模型服务提供选择建议。
实验步骤¶
- 预处理源代码
点击左侧 Cmake 扩展,在 PROJECT OUTLINE/YatCC/test/task0 里选择构建
task0-answer。该操作将自动将所有 YatCC/test/cases/*/*.sysu.c
源代码进行预处理。预处理结果将保存在 YatCC/build/test/task0
文件夹中,作为词法分析器的输入。

- 实现词法分析器
本次实验可以选择使用 flex 或 antlr 实现词法分析器,需要在
/YatCC/config.cmake 中设置 TASK1_WITH 为 "flex" 或
"antlr"。设置完成后,同学们可以自由编写 YatCC/task/1/flex 或
YatCC/task/1/antlr 中的源代码。
完成源代码编写后,可以通过 PROJECT OUTLINE/YatCC/test/task1 中选择构建
task1-score 进行实验一的评分。完成所有样例测评后,每个样例的标准输出
answer.txt、实际输出 output.txt 和评分结果 score.txt 将保存在
YatCC/build/test/task1
文件夹中,同学们可以根据评分结果对比标准输出和实际输出,并进行代码修改。

实验要求能够正确输出除 eof
和不可见字符外的所有词法单元的别名、所属的源文件的路径以及在源文件中的位置。为了简化,对于文件结束符
eof,我们仅判断其是否被正确识别并输出别名,而不进行词法单元位置和源文件路径的判断。
调试应循序渐进
调试顺序建议是:先确保 token 别名正确,再完成 Loc 的识别,最后确定是否输出
[StartOfLine] 和 [LeadingSpace]。这样可以确保功能逐步完善,不至于逻辑混乱。
- 手动编写测例
同学们在通过 task1-score 的所有测例之后,如果想检验词法分析器的功能是否完备、健壮,可以在 test/diy-cases 这个目录下自己编写测例。
注意,测例文件必须以 .sysu.c 为后缀,否则无法被识别。

添加好测例之后,构建 task1-diy-score 便可自动为 diy 测例评分

- 打包提交
完成实验后,将实验报告上传到 /YatCC/task/task1,然后构建
PROJECT OUTLINE/YatCC/task/task1-score
进行实验一源代码打包,打包结果将保存于 /YatCC/build/task
中。请将打包好的源代码提交给助教,我们会在后台进行隐藏测例的评测并汇总分数。

评分标准¶
- 本地评测标准
- 是否提取出正确的 Token (60 分)
- 是否提取出正确的 token location (30 分)
- 是否识别出其他无关的字符 (10 分)
注: eof 这个 token 的位置和源文件路径不计入评分。 eof
需要被识别,但它的位置和源文件路径不计分。
- 隐藏测例
今年新增隐藏测例
为了防止面向测例编程的情况发生,今年我们在新增了隐藏测例。同学们将代码打包提交给助教之后,我们会在后台进行隐藏测例的评测。这些隐藏测例不会超出实验要求范围。
最后 task1 的评测成绩:本地评测分数 _ 0.95 + 隐藏测例分数 _ 0.05
脚本说明¶
与实验一相关的脚本说明如下:
/YatCC/config.cmake:根据个人需要,设置实验一的实现方式TASK1_WITH为"flex"或"antlr"。/YatCC/task/1/CMakeLists.txt:根据TASK1_WITH选择编译工具为"flex"或"antlr",并使用相应工具生成词法分析器task1。/YatCC/test/task1/CMakeLists.txt:主要包含两个构造目标:task1-answer:调用同文件夹下的answer.sh,使用clang -cc1 -dump-tokens *.sysu.c指令生成所有测例的标准词法分析结果。task1-score:调用同文件夹下的score.py,将task1生成的输出与clang生成的标准答案进行比较,最终统计各测例得分。评分时会根据测例权重文件对各测例得分进行加权计算总得分。同时,本文件还包含为每个测例创建测试的代码,方便同学们使用断点调试功能(相关用法已在 如何调试代码 介绍,此处不再赘述)。
YatCC Agent