实验介绍¶
Task 2 · 语法分析 / Syntax Analysis
"Language is a process of free creation; its laws and principles are fixed, but the manner in which the principles of generation are used is free and infinitely varied."
(语言是一个自由创造的过程;它的法则和原则是固定的,但生成原则的使用方式却是自由且千变万化的。)
先读清输入输出,再决定走 Bison 还是 ANTLR
这两个路线在输入形式、调试习惯和代码组织上都不同。先把评测目标和中间表示看明白,再选实现框架,会少走很多弯路。
任务描述¶
实验一中我们实现了一个词法分析器,源代码文件输入词法分析器后将会生成 token 流。而在本次实验中,我们首先需要使用 Bison 或 ANTLR 完成一个语法分析器,token 流输入到语法分析器之后会生成语法分析树 AST。AST 主要反映了程序的结构,但不足以全面表示程序的语义信息。语法分析图 ASG 在 AST 的基础上增加了额外的语义信息,如作用域、类型信息、变量和函数之间的关系等,这有助于接下里的编译优化、类型检查和代码生成等步骤。但是在本次实验中不管是 AST 还是 ASG,都是位于比较复杂的数据结构,不便于同学们打印出来以协助调试,也不便于实验评分,所以最终我们还需要实现从 ASG 到 JSON 转换。虽然是从 ASG 转换到 JSON,但 JSON 是树型结构,代表的仍然是 AST。
下图展示了实验的整体结构:

完成实验二的方式同实验一一样,需要同学们不断对比标准答案和当前输出,找到需要改进的内容,进行完善。至于如何改进实验二的程序以输出正确答案,将在“公用代码介绍”以及具体的实验章节中介绍。同学们可以选择 Bison 或 ANTLR 来完成本次实验。
先把树结构看懂,再写代码
task2 的难点往往不在语法规则本身,而在你是否真正理解了目标输出的树形结构。 开始写解析逻辑前,建议先把几个 answer.json 手动展开看一遍。
输入输出简介¶
在本次实验中,使用 Bison 和 ANTLR 的输入并不相同。前者的输入为 task1 的输出(token 流,例如
/YatCC/build/test/task1/functional-0/000_main.sysu.c),而后者的输入为 task0 的输出(经过预处理的源代码,例如
/YatCC/build/test/task0/functional-0/000_main.sysu.c)。
而输出都是由 clang parse 生成,是代表 ASG 的 JSON 文件,例如
/YatCC/build/test/task2/functional-0/000_main.sysu.c/answer.json(以下只截取了部分内容):
不要只盯着 kind
虽然评分重点在 kind、name、value、type,但很多同学调试时只看 kind,最后会在细节键值上丢分。 对照输出时,建议把关键字段逐个核对,而不是只看节点名字是否大致对上。
{
"id": "0x55d4efa4f008",
"kind": "TranslationUnitDecl",
"loc": {},
"range": {
"begin": {},
"end": {}
},
"inner": [
{
"id": "0x55d4efa4f838",
"kind": "TypedefDecl",
"loc": {},
"range": {
"begin": {},
"end": {}
},
"isImplicit": true,
"name": "__int128_t",
"type": {
"qualType": "__int128"
},
// ...
以上面这个 JSON 文件为例,进行一些解释(实际上同学们只需要关心 kind, name,
value, type
这四个关键词,我们的评测系统也只关注输出中的这四个关键字的正确性):
id: 唯一标识符,用于区分 AST 中的每一个节点。kind: 节点语法类型,表示该节点代表的源代码语法结构的种类,如TypedefDecl(类型定义声明)、BuiltinType(内置类型)、FunctionDecl(函数声明)等。loc: 位置信息,通常包含文件名、行号和列号,用于指示源代码中该元素的位置。range: 范围信息,指出了源代码中该节点覆盖的起始和结束位置。它有 begin 和 end 两个属性,每个属性可能包含偏移量、行号、列号等信息,用于准确定位代码片段。inner: 内部节点,这个列表包含了当前节点下的子节点。例如,一个FunctionDecl节点会包含它的参数和函数体等子节点。isImplicit: 表示该声明是否是隐式的,即没有在源代码中直接写出来,而是由编译器自动生成的。name: 节点名称,比如类型名称、函数名称等。type: 节点类型,包含了源代码的类型信息,如__int128、unsigned __int128等。对于类型节点,qualType属性描述了类型的完整限定名。decl: 声明信息,某些节点(如RecordType)可能包含对声明本身的引用。size: 大小,主要用于数组类型,表示数组的元素数量。valueCategory: 值类别,如prvalue表示纯右值。value: 节点值,对于字面量如整数字面量,这个字段包含了具体的值。
此外,通过 VSCode 可以很方便地看到 JSON 文件的结构。鼠标点击下图中红框里的
...,就会显示这个文件的结构:

可以看见,其最外层的结构的 kind(种类)为
TranslationUnitDecl。点击左侧图标展开,可以看到属性 inner
还包含其余六个部分:前五个都是 TypedefDecl,最后一个是 FunctionDecl:

所以这个 JSON 文件表示的 ASG,结构如下:
└── TranslationUnitDecl
├── 多个 TypedefDecl
└── FunctionDecl
└── CompoundStmt
└── ReturnStmt
└── IntegerLiteral
构建目标介绍¶
当同学们通过 cmake
插件点击小小按钮来构建 task2 的可执行文件时,助教们写好的各种 CMakeLists
脚本会将 task2 所有相关代码文件编译成一个可执行文件。同学们在 cmake
插件中的下拉选项中看见,到底有哪些文件参与进来。

当可执行文件编译完成后,同学们可以在 build/task/2/antlr/ 或
build/task/2/bison/ 目录下找到这个 task2 这个可执行文件。

当我们点击“task2-score”时,助教们写好的评测脚本 score.py
将调用这个程序,以测试样例作为输入,并将程序的输出(代表 ASG 的 JSON 文件)保存下来,最后与正确答案比较,给出评分。
看 score 之前,先抽一两个 answer.json 对照阅读
如果你没有先建立对目标树形结构的直觉,只看评分日志通常很难马上定位问题。 先手动比对少量样例,再让 score 帮你放大差异,效率会更高。
评分标准¶
同学们查看 JSON 文件,会发现上述每个节点里面包含了非常多的属性,除去 TypedefDecl 不用管之外,我们的评分以属性打印为准,具体如下:
- 是否提取出正确的
kind、name、value键值(不含InitListExpr)(60 分) - 是否提取出正确的
type键值及是否构造正确的InitListExpr生成树(40 分)。
在根目录下的 config.cmake 中可以选择实验二的日志输出级别:
- 级别 1:对应 60 分标准的错误提示
- 级别 2:对应 100 分标准的错误提示
- 级别 3:对应完整 clang parse 输出的错误提示。
建议同学们在实验过程中从低到高选择不同的日志等级,比较当前输出与标准输出的区别,并逐渐完善代码。
同时,强烈建议(尤其是选用 Bison 完成 task2 的)同学们在 config.cmake
中启用复活功能,以直接使用标准的输入,避免 task1 中实现的不完善,影响 task2 的完成。是否启用复活功能,对本次实验的成绩没有任何影响。
复活机制是隔离问题,不是偷懒
当前一阶段还不稳定时,先用标准输入把 task2 自身打通,是很合理的调试策略。 先把问题边界切干净,再回头联调整个流水线。
文法参考¶
本实验设计时参考的文法是 SysY 语言文法。SysY 语言文法是一个非常完整的类 C 语言的文法,完成实验只需要用到其中的一部分。同学们还可以参考 SysY 语言定义,来获取更详细的解释和说明。
下面是 SysY 语言文法的所有产生式,同学们只需要选取其中的一部分,即可完成实验:
完整文法
start
: translation_unit
;
primary_expression
: IDENTIFIER
| CONSTANT
| STRING_LITERAL
| '(' expression ')'
;
postfix_expression
: primary_expression
| postfix_expression '[' expression ']'
| postfix_expression '(' ')'
| postfix_expression '(' argument_expression_list ')'
| postfix_expression '.' IDENTIFIER
| postfix_expression PTR_OP IDENTIFIER
| postfix_expression INC_OP
| postfix_expression DEC_OP
| '(' type_name ')' '{' initializer_list '}'
| '(' type_name ')' '{' initializer_list ',' '}'
;
argument_expression_list
: assignment_expression
| argument_expression_list ',' assignment_expression
;
unary_expression
: postfix_expression
| INC_OP unary_expression
| DEC_OP unary_expression
| unary_operator cast_expression
| SIZEOF unary_expression
| SIZEOF '(' type_name ')'
;
unary_operator
: '&'
| '*'
| '+'
| '-'
| '~'
| '!'
;
cast_expression
: unary_expression
| '(' type_name ')' cast_expression
;
multiplicative_expression
: cast_expression
| multiplicative_expression '*' cast_expression
| multiplicative_expression '/' cast_expression
| multiplicative_expression '%' cast_expression
;
additive_expression
: multiplicative_expression
| additive_expression '+' multiplicative_expression
| additive_expression '-' multiplicative_expression
;
shift_expression
: additive_expression
| shift_expression LEFT_OP additive_expression
| shift_expression RIGHT_OP additive_expression
;
relational_expression
: shift_expression
| relational_expression '<' shift_expression
| relational_expression '>' shift_expression
| relational_expression LE_OP shift_expression
| relational_expression GE_OP shift_expression
;
equality_expression
: relational_expression
| equality_expression EQ_OP relational_expression
| equality_expression NE_OP relational_expression
;
and_expression
: equality_expression
| and_expression '&' equality_expression
;
exclusive_or_expression
: and_expression
| exclusive_or_expression '^' and_expression
;
inclusive_or_expression
: exclusive_or_expression
| inclusive_or_expression '|' exclusive_or_expression
;
logical_and_expression
: inclusive_or_expression
| logical_and_expression AND_OP inclusive_or_expression
;
logical_or_expression
: logical_and_expression
| logical_or_expression OR_OP logical_and_expression
;
conditional_expression
: logical_or_expression
| logical_or_expression '?' expression ':' conditional_expression
;
assignment_expression
: conditional_expression
| unary_expression assignment_operator assignment_expression
;
assignment_operator
: '='
| MUL_ASSIGN
| DIV_ASSIGN
| MOD_ASSIGN
| ADD_ASSIGN
| SUB_ASSIGN
| LEFT_ASSIGN
| RIGHT_ASSIGN
| AND_ASSIGN
| XOR_ASSIGN
| OR_ASSIGN
;
expression
: assignment_expression
| expression ',' assignment_expression
;
constant_expression
: conditional_expression
;
declaration
: declaration_specifiers ';'
| declaration_specifiers init_declarator_list ';'
;
declaration_specifiers
: storage_class_specifier
| storage_class_specifier declaration_specifiers
| type_specifier
| type_specifier declaration_specifiers
| type_qualifier
| type_qualifier declaration_specifiers
| function_specifier
| function_specifier declaration_specifiers
;
init_declarator_list
: init_declarator
| init_declarator_list ',' init_declarator
;
init_declarator
: declarator
| declarator '=' initializer
;
storage_class_specifier
: TYPEDEF
| EXTERN
| STATIC
| AUTO
| REGISTER
;
type_specifier
: VOID
| CHAR
| SHORT
| INT
| LONG
| FLOAT
| DOUBLE
| SIGNED
| UNSIGNED
| BOOL
| COMPLEX
| IMAGINARY
| struct_or_union_specifier
| enum_specifier
| TYPE_NAME
;
struct_or_union_specifier
: struct_or_union IDENTIFIER '{' struct_declaration_list '}'
| struct_or_union '{' struct_declaration_list '}'
| struct_or_union IDENTIFIER
;
struct_or_union
: STRUCT
| UNION
;
struct_declaration_list
: struct_declaration
| struct_declaration_list struct_declaration
;
struct_declaration
: specifier_qualifier_list struct_declarator_list ';'
;
specifier_qualifier_list
: type_specifier specifier_qualifier_list
| type_specifier
| type_qualifier specifier_qualifier_list
| type_qualifier
;
struct_declarator_list
: struct_declarator
| struct_declarator_list ',' struct_declarator
;
struct_declarator
: declarator
| ':' constant_expression
| declarator ':' constant_expression
;
enum_specifier
: ENUM '{' enumerator_list '}'
| ENUM IDENTIFIER '{' enumerator_list '}'
| ENUM '{' enumerator_list ',' '}'
| ENUM IDENTIFIER '{' enumerator_list ',' '}'
| ENUM IDENTIFIER
;
enumerator_list
: enumerator
| enumerator_list ',' enumerator
;
enumerator
: IDENTIFIER
| IDENTIFIER '=' constant_expression
;
type_qualifier
: CONST
| RESTRICT
| VOLATILE
;
function_specifier
: INLINE
;
declarator
: pointer direct_declarator
| direct_declarator
;
direct_declarator
: IDENTIFIER
| '(' declarator ')'
| direct_declarator '[' type_qualifier_list assignment_expression ']'
| direct_declarator '[' type_qualifier_list ']'
| direct_declarator '[' assignment_expression ']'
| direct_declarator '[' STATIC type_qualifier_list assignment_expression ']'
| direct_declarator '[' type_qualifier_list STATIC assignment_expression ']'
| direct_declarator '[' type_qualifier_list '*' ']'
| direct_declarator '[' '*' ']'
| direct_declarator '[' ']'
| direct_declarator '(' parameter_type_list ')'
| direct_declarator '(' identifier_list ')'
| direct_declarator '(' ')'
;
pointer
: '*'
| '*' type_qualifier_list
| '*' pointer
| '*' type_qualifier_list pointer
;
type_qualifier_list
: type_qualifier
| type_qualifier_list type_qualifier
;
parameter_type_list
: parameter_list
| parameter_list ',' ELLIPSIS
;
parameter_list
: parameter_declaration
| parameter_list ',' parameter_declaration
;
parameter_declaration
: declaration_specifiers declarator
| declaration_specifiers abstract_declarator
| declaration_specifiers
;
identifier_list
: IDENTIFIER
| identifier_list ',' IDENTIFIER
;
type_name
: specifier_qualifier_list
| specifier_qualifier_list abstract_declarator
;
abstract_declarator
: pointer
| direct_abstract_declarator
| pointer direct_abstract_declarator
;
direct_abstract_declarator
: '(' abstract_declarator ')'
| '[' ']'
| '[' assignment_expression ']'
| direct_abstract_declarator '[' ']'
| direct_abstract_declarator '[' assignment_expression ']'
| '[' '*' ']'
| direct_abstract_declarator '[' '*' ']'
| '(' ')'
| '(' parameter_type_list ')'
| direct_abstract_declarator '(' ')'
| direct_abstract_declarator '(' parameter_type_list ')'
;
initializer
: assignment_expression
| '{' initializer_list '}'
| '{' initializer_list ',' '}'
;
initializer_list
: initializer
| designation initializer
| initializer_list ',' initializer
| initializer_list ',' designation initializer
;
designation
: designator_list '='
;
designator_list
: designator
| designator_list designator
;
designator
: '[' constant_expression ']'
| '.' IDENTIFIER
;
statement
: labeled_statement
| compound_statement
| expression_statement
| selection_statement
| iteration_statement
| jump_statement
;
labeled_statement
: IDENTIFIER ':' statement
| CASE constant_expression ':' statement
| DEFAULT ':' statement
;
compound_statement
: '{' '}'
| '{' block_item_list '}'
;
block_item_list
: block_item
| block_item_list block_item
;
block_item
: declaration
| statement
;
expression_statement
: ';'
| expression ';'
;
selection_statement
: IF '(' expression ')' statement
| IF '(' expression ')' statement ELSE statement
| SWITCH '(' expression ')' statement
;
iteration_statement
: WHILE '(' expression ')' statement
| DO statement WHILE '(' expression ')' ';'
| FOR '(' expression_statement expression_statement ')' statement
| FOR '(' expression_statement expression_statement expression ')' statement
| FOR '(' declaration expression_statement ')' statement
| FOR '(' declaration expression_statement expression ')' statement
;
jump_statement
: GOTO IDENTIFIER ';'
| CONTINUE ';'
| BREAK ';'
| RETURN ';'
| RETURN expression ';'
;
translation_unit
: external_declaration
| translation_unit external_declaration
;
external_declaration
: function_definition
| declaration
;
function_definition
: declaration_specifiers declarator declaration_list compound_statement
| declaration_specifiers declarator compound_statement
;
declaration_list
: declaration
| declaration_list declaration
;
YatCC Agent