.l文件 & .y文件

375 阅读5分钟

.l


%option noyywrap//不需要在文件末尾添加 yywrap 函数
%option nounput//禁用 unput 函数
%option noinput//禁用 input 函数

%{
//直接复制到生成的.c文件的开头
#include <cstdlib>//使用stdlib.h 头文件中的 strtol 函数,
                    //用于将字符串表示的数字转换为整型变量
#include <string>//存储标识符的字符串值

// 因为 Flex 会用到 Bison 中关于 token 的定义
// 所以需要 include Bison 生成的头文件
#include "sysy.tab.hpp"

using namespace std;

%}


//Flex/Bison 相关的定义

//正则表达式的书写
/* 空白符和注释 */
WhiteSpace    [ \t\n\r]*
    [] 表示一个字符集合,可以匹配其中任意一个字符。
    \t 表示制表符(tab)。
    \n 表示换行符(newline)。
    \r 表示回车符(carriage return)。
    * 表示匹配前一个字符零次或多次,即可以出现也可以不出现
        ?非贪婪匹配 代替 * 
        +? 代替 +

LineComment   "//".*
    . 表示匹配任意一个字符(除了换行符之外)

/* 标识符 */
Identifier    [a-zA-Z_][a-zA-Z0-9_]*
    以字母或下划线开头 后跟零个或多个字母、数字或下划线的标识符
    连接

/* 整数字面量 */
Decimal       [1-9][0-9]*
        表示一个十进制整数,首位为 19,后跟零个或多个数字
        连接
Octal         0[0-7]*
        表示一个八进制整数,以 0 开头,后跟零个或多个 07 的数字。
Hexadecimal   0[xX][0-9a-fA-F]+
        表示一个十六进制整数,以 0x 或 0X 开头,后跟至少一个 09a 到 f(大小写均可)的数字。
        `+` 表示匹配前一个字符一次或多次,即至少出现一次。

%%

//匹配模式(正则表达式) + 动作(C代码)
//Flex/Bison 的规则描述

{WhiteSpace}    { /* 忽略, 不做任何操作 */ }
{LineComment}   { /* 忽略, 不做任何操作 */ }

"int"           { return INT; }
"return"        { return RETURN; }
        在Flex的正则表达式中,可以直接使用字符串来匹配输入流中的相应内容

/*在解析器中的终结符token中设置yylval以存储标识符的值。这个值可以被Bison解析器读取和处理,用于分析标识符的上下文含义*/

{Identifier}    { yylval.str_val = new string(yytext); return IDENT; }
        将Flex识别出来的标识符的字符串值(即yytext)
        存储在yylval变量的str_val成员中
        return的IDENT是在Bison的.y文件中定义的一个token类型
        (表示已经识别出一个标识符)
{Decimal}       { yylval.int_val = strtol(yytext, nullptr, 0); return INT_CONST; }
        strtol将字符串转换为长整型数值
        需要转换的字符串,函数将返回长整型的数值,根据字符串的内容自动选择转换的进制
{Octal}         { yylval.int_val = strtol(yytext, nullptr, 0); return INT_CONST; }
{Hexadecimal}   { yylval.int_val = strtol(yytext, nullptr, 0); return INT_CONST; }

.               { return yytext[0]; }
        表示返回字符流的第一个字符作为token
        如果Flex扫描器无法识别输入中的任何一个模式,
        则会将下一个字符作为一个通用token返回给Bison解析器。
        在Bison解析器中,这个通用token通常被视为错误,
        表示在源代码中存在语法错误

%%

.y

/*

%code requires`
    把大括号里的内容塞到 Bison 生成的 头文件里
%{ ... %}
    把 `...` 对应的内容塞到 Bison 生成的 源文件里

生成的头文件 主要是 
    parser 函数的定义 
        (给用户用的
        在编译器里调用 Bison 生成的 
        parser 帮我们解析 SysY 文件,
        就需要引用这个头文件)
    `yylval` 的定义
        (在 lexer 和 parser 之间传递信息, 
        我们已经在 Flex 文件中引用了这个头文件)

*/
%code requires {//声明所需的头文件
  #include <memory>
  #include <string>
}

%{

#include <iostream>
#include <memory>
#include <string>

// 声明 lexer 函数和错误处理函数
//使得parser 会找到 Flex 中定义的 lexer, 正常报错
int yylex();//返回值是被解析的下一个词法单元的标记
void yyerror(std::unique_ptr<std::string> &ast, const char *s);

using namespace std;

%}

/*定义了 parser 函数和错误处理函数的附加参数,
即抽象语法树指针的引用。在解析完成后,
我们可以手动修改这个参数,把它设置成解析得到的字符串*/
/*Bison 生成的 parser 函数返回类型一定是 `int`,
所以我们没办法通过返回值返回 AST, 只能通过参数来返回 AST */
%parse-param { std::unique_ptr<std::string> &ast }

/*定义yacc语法分析器中的 union 数据类型,
定义的变量名为 yylval
用于保存词法分析器 lexer 返回的 token 值的一个联合体

使用 union 是为了使 yylval 能够保存不同的类型,
以适应不同的 token 值,

使用 std::string *str_val 而不是 std::string 的原因
是为了避免在 union 中定义带有析构函数的对象时出现问题。 

在 yacc 文件中使用 yylval 
可以在分析过程中保存分析的结果,
最终将结果传递给调用分析器的函数。

yylval 不是 yacc 的内置变量,
它是通过 %union 定义的用户自定义变量*/
%union {
  std::string *str_val;
  int int_val;
}

/*定义了所有可能的 token 种类,
其中 `<str_val>` 和 `<int_val>` 
是为了告诉 parser,IDENT 和 INT_CONST 返回的是
字符串指针和整型值,而不是默认的整型 token*/
%token INT RETURN
%token <str_val> IDENT
%token <int_val> INT_CONST

/*定义了非终结符的类型,
这些类型是由语法规则产生的。
在这个语法中,我们定义了许多非终结符的类型,
包括 FuncDef、FuncType、Block、Stmt、Number*/
%type <str_val> FuncDef FuncType Block Stmt Number

%%

/*定义了语法规则
    通过定义不同的规则来解析不同的语法结构,
    这些规则返回的结果会被组合成更大的 AST,
    最终生成完整的 AST 表示整个程序的语法结构*/
    
/*用于解析语法并生成对应的 AST(抽象语法树)。
这段代码中的 CompUnit 规则表示
整个编译单元,由一个 FuncDef 构成。
在解析完 FuncDef 后,将 FuncDef 返回的 AST 存储到
unique_ptr<string> ast 指向的变量中。 
*/
CompUnit
  : FuncDef {
    ast = unique_ptr<string>($1);
  }
  ;

/*FuncDef 规则解析一个函数定义,
包括函数类型、函数名、空的参数列表和函数体。
在解析完成后,我们把它们拼接成一个字符串,并返回它*/
FuncDef
  : FuncType IDENT '(' ')' Block {
    auto type = unique_ptr<string>($1);
    auto ident = unique_ptr<string>($2);
    auto block = unique_ptr<string>($5);
    $$ = new string(*type + " " + *ident + "() " + *block);
  }
  ;

/*FuncType 规则定义了函数类型,这里只有一个 int 类型*/
FuncType
  : INT {
    $$ = new string("int");
  }
  ;

/*Block 规则解析一个语句块,包括花括号内的一些语句。
解析完成后,我们把它们拼接成一个字符串,并返回它*/
Block
  : '{' Stmt '}' {
    auto stmt = unique_ptr<string>($2);
    $$ = new string("{ " + *stmt + " }");
  }
  ;

/*Stmt 规则解析一个 return 语句,返回一个数字。
解析完成后,我们把它们拼接成一个字符串,并返回它*/
Stmt
  : RETURN Number ';' {
    auto number = unique_ptr<string>($2);
    $$ = new string("return " + *number + ";");
  }
  ;

/*Number 规则解析一个整数,返回整数值的字符串*/
Number
  : INT_CONST {
    $$ = new string(to_string($1));
  }
  ;

%%

/*定义了一个错误处理函数 `yyerror`,
它在 parser 出现错误时被调用。
该函数输出错误信息到标准错误输出*/
void yyerror(unique_ptr<string> &ast, const char *s) {
  cerr << "error: " << s << endl;
}