Upload
ivria
View
76
Download
0
Embed Size (px)
DESCRIPTION
编译原理上机实习. [email protected]. 实习题目. 利用词法分析程序的自动构造工具 LEX 构造一个识别 C 语言的 单词的词法分析程序; 利用语法分析程序的自动构造工具 Yacc 构造一个识别 C 语言的 程序的语法分析程序. LEX 简介 YACC 简介 实习要求. LEX 简介. Lex 是一种生成扫描器的工具。扫描器是一种识别文本中的词汇模式的程序。这些词汇模式(或者常规表达式)在一种特殊的句子结构中定义. - PowerPoint PPT Presentation
Citation preview
编译原理上机实习编译原理上机实习
[email protected]@pku.edu.cn
实习题目实习题目
1.1. 利用词法分析程序的自动构造工具利用词法分析程序的自动构造工具 LEXLEX 构构造一个识别造一个识别 CC 语言的单词的词法分析程序;语言的单词的词法分析程序;
2.2. 利用语法分析程序的自动构造工具利用语法分析程序的自动构造工具 YaccYacc构造一个识别构造一个识别 CC 语言的程序的语法分析程语言的程序的语法分析程序序
• LEXLEX 简介简介• YACCYACC 简介简介•实习要求实习要求
LEXLEX 简介简介
• Lex Lex 是一种生成扫描器的工具。扫描器是是一种生成扫描器的工具。扫描器是一种识别文本中的词汇模式的程序。这些一种识别文本中的词汇模式的程序。这些词汇模式(或者常规表达式)在一种特殊词汇模式(或者常规表达式)在一种特殊的句子结构中定义 的句子结构中定义
• 一种匹配的常规表达式可能会包含相关的动作。一种匹配的常规表达式可能会包含相关的动作。这一动作可能还包括返回一个标记。当 这一动作可能还包括返回一个标记。当 Lex Lex 接收接收到文件或文本形式的输入时,它试图将文本与常到文件或文本形式的输入时,它试图将文本与常规表达式进行匹配。它一次读入一个输入字符,规表达式进行匹配。它一次读入一个输入字符,直到找到一个匹配的模式。如果能够找到一个匹直到找到一个匹配的模式。如果能够找到一个匹配的模式,配的模式, Lex Lex 就执行相关的动作(可能包括返就执行相关的动作(可能包括返回一个标记)。另一方面,如果没有可以匹配的回一个标记)。另一方面,如果没有可以匹配的常规表达式,将会停止进一步的处理,常规表达式,将会停止进一步的处理, Lex Lex 将显将显示一个错误消息。 示一个错误消息。
Lex Lex 的常规表达式 的常规表达式
•常规表达式是一种使用元语言的模式描述。常规表达式是一种使用元语言的模式描述。表达式由符号组成。符号一般是字符和数表达式由符号组成。符号一般是字符和数字,但是 字,但是 Lex Lex 中还有一些具有特殊含义的中还有一些具有特殊含义的其他标记。下面两个表格定义了 其他标记。下面两个表格定义了 Lex Lex 中使中使用的一些标记并给出了几个典型的例子。 用的一些标记并给出了几个典型的例子。
• A-Z, 0-9, a-z A-Z, 0-9, a-z 构成了部分模式的字符和数字。 构成了部分模式的字符和数字。 . . 匹配任意字符,除了 匹配任意字符,除了 \n\n 。 。 - - 用来指定范围。例如:用来指定范围。例如: A-Z A-Z 指从 指从 A A 到 到 Z Z 之间的所有字之间的所有字符。 符。 [ ] [ ] 一个字符集合。匹配括号内的任意 字符。如果第一个字一个字符集合。匹配括号内的任意 字符。如果第一个字符是 符是 ^ ^ 那么它表示否定模式。例如那么它表示否定模式。例如 : [abC] : [abC] 匹配 匹配 a, b, a, b, 和和 CC 中的任何一个。 中的任何一个。
• * * 匹配匹配 00 个或者多个上述的模式。 个或者多个上述的模式。 + + 匹配匹配 11 个或者多个上述模式。 个或者多个上述模式。 ? ? 匹配匹配 00 个或个或 11 个上述模式。 个上述模式。 $ $ 作为模式的最后一个字符匹配一行的结尾。 作为模式的最后一个字符匹配一行的结尾。 { } { } 指出一个模式可能出现的次数。 例如指出一个模式可能出现的次数。 例如 : A{1,3} : A{1,3} 表示 表示 A A 可可能出现能出现 11 次或次或 33 次。 次。 \ \ 用来转义元字符。同样用来覆盖字符在此表中定义的特用来转义元字符。同样用来覆盖字符在此表中定义的特殊意义,只取字符的本意。 殊意义,只取字符的本意。 ^ ^ 否定。 否定。
• | | 表达式间的逻辑或。 表达式间的逻辑或。 "<"< 一些符号一些符号 >;" >;" 字符的字面含义。元字符具有。 字符的字面含义。元字符具有。 / / 向前匹配。如果在匹配的模版中的“向前匹配。如果在匹配的模版中的“ /”/”后跟有后续表达式,只匹配模版中“后跟有后续表达式,只匹配模版中“ /”/” 前前面的部分。如:如果输入 面的部分。如:如果输入 A01A01 ,那么在模,那么在模版 版 A0/1 A0/1 中的 中的 A0 A0 是匹配的。 是匹配的。 ( ) ( ) 将一系列常规表达式分组。 将一系列常规表达式分组。
常规表达式举例 常规表达式举例
• joke[rs] joke[rs] 匹配 匹配 jokes jokes 或 或 jokerjoker 。 。 A{1,2}shis+ A{1,2}shis+ 匹配 匹配 AAshis, Ashis, AAshi, AshiAAshis, Ashis, AAshi, Ashi 。 。 (A[b-e])+ (A[b-e])+ 匹配在 匹配在 A A 出现位置后跟随的从 出现位置后跟随的从 b b 到 到 e e 的所有字符中的 的所有字符中的 0 0 个或 个或 11 个。 个。
• Lex Lex 中的标记声明类似 中的标记声明类似 C C 中的变量名。每中的变量名。每个标记都有一个相关的表达式。(下表中个标记都有一个相关的表达式。(下表中给出了标记和表达式的例子。)使用这个给出了标记和表达式的例子。)使用这个表中的例子,我们就可以编一个字数统计表中的例子,我们就可以编一个字数统计的程序了。我们的第一个任务就是说明如的程序了。我们的第一个任务就是说明如何声明标记。 何声明标记。
标记声明举例 标记声明举例
•标记 相关表达式 含义 标记 相关表达式 含义 数字数字 (number) ([0-9])+ 1(number) ([0-9])+ 1 个或多个数字 个或多个数字 字符字符 (chars) [A-Za-z] (chars) [A-Za-z] 任意字符 任意字符 空格空格 (blank) " " (blank) " " 一个空格 一个空格 字字 (word) (chars)+ 1(word) (chars)+ 1 个或多个 个或多个 chars chars 变量变量 (variable) ((variable) ( 字符字符 )+()+( 数字数字 )*()*( 字符字符 )*)*(( 数字数字 )* )*
Lex Lex 编程 编程
• Lex Lex 编程可以分为三步: 编程可以分为三步:
以 以 Lex Lex 可以理解的格式指定模式相关的动可以理解的格式指定模式相关的动作。 作。 在这一文件上运行 在这一文件上运行 LexLex ,生成扫描器的 ,生成扫描器的 C C 代码。 代码。 编译和链接 编译和链接 C C 代码,生成可执行的扫描器。代码,生成可执行的扫描器。
•一个 一个 Lex Lex 程序分为三个段:第一段是 程序分为三个段:第一段是 C C 和 和 Lex Lex 的全局声明,第二段包括模式(的全局声明,第二段包括模式( C C 代码),第三段是补充的 代码),第三段是补充的 C C 函数。 例如函数。 例如 , , 第三段中一般都有 第三段中一般都有 main() main() 函数。这些段函数。这些段以以 %%%% 来分界。 来分界。
例:字数统计的 例:字数统计的 Lex Lex 程序 程序
• C C 和 和 Lex Lex 的全局声明 的全局声明 这一段中我们可以增加 这一段中我们可以增加 C C 变量声明。这里变量声明。这里我们将为字数统计程序声明一个整型变量,我们将为字数统计程序声明一个整型变量,来保存程序统计出来的字数。我们还将进来保存程序统计出来的字数。我们还将进行 行 Lex Lex 的标记声明。 的标记声明。
字数统计程序的声明 字数统计程序的声明
• %{ %{ int wordCount = 0; int wordCount = 0; %} %} chars [A-za-z\ chars [A-za-z\ 抃抃 '\.\"] '\.\"] numbers ([0-9])+ numbers ([0-9])+ delim [" "\n\t] delim [" "\n\t] whitespace {delim}+ whitespace {delim}+ words {chars}+ words {chars}+ %% %% 两个百分号标记指出了 两个百分号标记指出了 Lex Lex 程序中这一段的结束和三段程序中这一段的结束和三段中第二段的开始。 中第二段的开始。
Lex Lex 的模式匹配规则 的模式匹配规则
• 字数统计程序中的 字数统计程序中的 Lex Lex 规则 规则 • {words} { wordCount++; /* {words} { wordCount++; /* increase the word count by one*/ } increase the word count by one*/ }
{whitespace} { /* do {whitespace} { /* do nothing*/ } nothing*/ }
{numbers} { /* one may {numbers} { /* one may want to add some processing here*/ } want to add some processing here*/ }
%% %%
C C 代码 代码
• Lex Lex 编程的第三段,也就是最后一段覆盖编程的第三段,也就是最后一段覆盖了 了 C C 的函数声明(有时是主函数)。注意的函数声明(有时是主函数)。注意这一段必须包括 这一段必须包括 yywrap() yywrap() 函数。 函数。 Lex Lex 有有一套可供使用的函数和变量。 其中之一就一套可供使用的函数和变量。 其中之一就是 是 yywrapyywrap 。一般来说,。一般来说, yywrap() yywrap() 的定义的定义如下例。 如下例。
字数统计程序的 字数统计程序的 C C 代码段 代码段 • void main() void main()
{ {
yylex(); /* start the yylex(); /* start the analysis*/ analysis*/
printf(" No of words: printf(" No of words: %d\n", wordCount); %d\n", wordCount);
} }
int yywrap() int yywrap()
{ {
return 1; return 1;
} }
高级 高级 Lex Lex
• Lex Lex 变量 变量
yyin FILE* yyin FILE* 类型。 它指向 类型。 它指向 lexer lexer 正在解析的当前文件。 正在解析的当前文件。 yyout FILE* yyout FILE* 类型。 它指向记录 类型。 它指向记录 lexer lexer 输出的位置。 缺输出的位置。 缺省情况下,省情况下, yyin yyin 和 和 yyout yyout 都指向标准输入和输出。 都指向标准输入和输出。 yytext yytext 匹配模式的文本存储在这一变量中(匹配模式的文本存储在这一变量中( char*char* )。 )。 yyleng yyleng 给出匹配模式的长度。 给出匹配模式的长度。 yylineno yylineno 提供当前的行数信息。(提供当前的行数信息。( lexerlexer 不一定支持。)不一定支持。)
高级 高级 Lex ---Lex Lex ---Lex 函数 函数
• yylex() yylex() 这一函数开始分析。 它由 这一函数开始分析。 它由 Lex Lex 自动生成。 自动生成。 yywrap() yywrap() 这一函数在文件(或输入)的末尾调用。如果这一函数在文件(或输入)的末尾调用。如果函数的返回值是函数的返回值是 11 ,就停止解析。 因此它可以用来解析多,就停止解析。 因此它可以用来解析多个文件。代码可以写在第三段,这就能够解析多个文件。 个文件。代码可以写在第三段,这就能够解析多个文件。 方法是使用 方法是使用 yyin yyin 文件指针(见上表)指向不同的文件,文件指针(见上表)指向不同的文件,直到所有的文件都被解析。最后,直到所有的文件都被解析。最后, yywrap() yywrap() 可以返回 可以返回 1 1 来表示解析的结束。 来表示解析的结束。 yyless(int n) yyless(int n) 这一函数可以用来送回除了前憂这一函数可以用来送回除了前憂 ? ? 个字符外个字符外的所有读出标记。 的所有读出标记。 yymore() yymore() 这一函数告诉 这一函数告诉 Lexer Lexer 将下一个标记附加到当前将下一个标记附加到当前标记后。 标记后。
YACCYACC 语言简介语言简介
•什么是语法 :什么是语法 :•我们看到 我们看到 Lex Lex 从输入序列中识别标记。如从输入序列中识别标记。如果你在查看标记序列,你可能想在这一序果你在查看标记序列,你可能想在这一序列出现时执行某一动作。这种情况下有效列出现时执行某一动作。这种情况下有效序列的规范称为语法。序列的规范称为语法。 Yacc Yacc 语法文件包括语法文件包括这一语法规范。它还包含了序列匹配时你这一语法规范。它还包含了序列匹配时你想要做的事。 想要做的事。
• 以英语为例, 这一套标记可能是:名词以英语为例, 这一套标记可能是:名词 , , 动词动词 , , 形容词等等。为了使用这些标记造一个语法正确形容词等等。为了使用这些标记造一个语法正确的句子,你的结构必须符合一定的规则。一个简的句子,你的结构必须符合一定的规则。一个简单的句子可能是名词单的句子可能是名词 ++ 动词或者名词动词或者名词 ++ 动词动词 ++名名词。词。
• 所以在我们这里,标记本身来自语言(所以在我们这里,标记本身来自语言( LexLex ),),并且标记序列允许用 并且标记序列允许用 Yacc Yacc 来指定这些标记来指定这些标记 (( 标标记序列也叫语法记序列也叫语法 )) 。 。
终端和非终端符号 终端和非终端符号
• 终端符号终端符号 : : 代表一类在语法结构上等效的标记。 代表一类在语法结构上等效的标记。 • 终端符号有三种类型: 终端符号有三种类型:
命名标记命名标记 : : 这些由 这些由 %token %token 标识符来定义。按照惯例,它们都是大标识符来定义。按照惯例,它们都是大写。 写。
字符标记字符标记 : : 字符常量的写法与 字符常量的写法与 C C 相同。例如相同。例如 , ?? , ?? 就是一个字符标记。就是一个字符标记。
字符串标记 字符串标记 : : 写法与 写法与 C C 的字符串常量相同。例如,的字符串常量相同。例如, "<<" "<<" 就是一个就是一个字符串标记。 字符串标记。
lexer lexer 返回命名标记。 返回命名标记。
•非终端符号非终端符号 : : 是一组非终端符号和终端符是一组非终端符号和终端符号组成的符号。按照惯例,它们都是小写。号组成的符号。按照惯例,它们都是小写。 在例子中, 在例子中, file file 是一个非终端标记而 是一个非终端标记而 NANAME ME 是一个终端标记。 是一个终端标记。
用 用 Yacc Yacc 来创建一个编译器包括四来创建一个编译器包括四个步骤: 个步骤: •编写一个 编写一个 .y .y 的语法文件(同时说明 的语法文件(同时说明 C C 在在这里要进行的动作)。 这里要进行的动作)。 编写一个词法分析器来处理输入并将标记传编写一个词法分析器来处理输入并将标记传递给解析器。 这可以使用 递给解析器。 这可以使用 Lex Lex 来完成。 来完成。 编写一个函数,通过调用 编写一个函数,通过调用 yyparse() yyparse() 来开始来开始解析。 解析。 编写错误处理例程(如 编写错误处理例程(如 yyerror()yyerror() )。 )。 编译 编译 Yacc Yacc 生成的代码以及其他相关的源文生成的代码以及其他相关的源文件。 件。
用 用 Yacc Yacc 编写语法 编写语法
•如同 如同 Lex Lex 一样一样 , , 一个 一个 Yacc Yacc 程序也用双百程序也用双百分号分为三段。它们是:声明、语法规则分号分为三段。它们是:声明、语法规则和 和 C C 代码。 我们将解析一个格式为 姓名 代码。 我们将解析一个格式为 姓名 = = 年龄的文件作为例子,来说明语法规则。年龄的文件作为例子,来说明语法规则。我们假设文件有多个姓名和年龄,它们以我们假设文件有多个姓名和年龄,它们以空格分隔。 在看 空格分隔。 在看 Yacc Yacc 程序的每一段时,程序的每一段时,我们将为我们的例子编写一个语法文件。 我们将为我们的例子编写一个语法文件。
C C 与 与 Yacc Yacc 的声明 的声明
• C C 声明可能会定义动作中使用的类型和变声明可能会定义动作中使用的类型和变量,以及宏。还可以包含头文件。每个 量,以及宏。还可以包含头文件。每个 YaYacc cc 声明段声明了终端符号和非终端符号声明段声明了终端符号和非终端符号(标记)的名称,还可能描述操作符优先(标记)的名称,还可能描述操作符优先级和针对不同符号的数据类型。 级和针对不同符号的数据类型。 lexer (Lelexer (Lex) x) 一般返回这些标记。所有这些标记都必一般返回这些标记。所有这些标记都必须在 须在 Yacc Yacc 声明中进行说明。 声明中进行说明。
• 在文件解析的例子中我们感兴趣的是这些标记:在文件解析的例子中我们感兴趣的是这些标记: name, equal sname, equal sign, ign, 和 和 ageage 。。 Name Name 是一个完全由字符组成的值。 是一个完全由字符组成的值。 Age Age 是数是数字。于是声明段就会像这样: 字。于是声明段就会像这样:
文件解析例子的声明 文件解析例子的声明
% %
#typedef char* string; /* #typedef char* string; /* to specify token types as char* */ to specify token types as char* */
#define YYSTYPE string /* #define YYSTYPE string /* a Yacc variable which has the value of returned token */ a Yacc variable which has the value of returned token */
%} %}
%token NAME EQ AGE %token NAME EQ AGE
%% %%
•类似 类似 Lex, Yacc Lex, Yacc 也有一套变量和函数可供也有一套变量和函数可供用户来进行功能扩展。 用户来进行功能扩展。 YYSTYPE YYSTYPE 定义了定义了用来将值从 用来将值从 lexer lexer 拷贝到解析器或者 拷贝到解析器或者 YaccYacc 的 的 yylval yylval (另一个 (另一个 Yacc Yacc 变量)的类型。变量)的类型。默认的类型是 默认的类型是 intint 。 由于字符串可以从 。 由于字符串可以从 lelexer xer 拷贝,类型被重定义为 拷贝,类型被重定义为 char*char* 。 。
Yacc Yacc 语法规则 语法规则
• Yacc Yacc 语法规则具有以下一般格式: 语法规则具有以下一般格式:
result: components { /* result: components { /* action to be taken in C */ } action to be taken in C */ }
; ; 在这个例子中,在这个例子中, result result 是规则描述的非终端符号。是规则描述的非终端符号。Components Components 是根据规则放在一起的不同的终端是根据规则放在一起的不同的终端和非终端符号。 如果匹配特定序列的话 和非终端符号。 如果匹配特定序列的话 ComponComponents ents 后面可以跟随要执行的动作。 后面可以跟随要执行的动作。
• param : NAME EQ NAME { param : NAME EQ NAME { printf("\tName:%s\tValue(name):%s\n", $ printf("\tName:%s\tValue(name):%s\n", $1,$3);} 1,$3);}
| NAME EQ VALUE{ | NAME EQ VALUE{ printf("\tName:%s\tValue(value):%s\n", printf("\tName:%s\tValue(value):%s\n",$1,$3);} $1,$3);}
; ;
•如果上例中序列 如果上例中序列 NAME EQ NAME NAME EQ NAME 被匹配,被匹配,将执行相应的 将执行相应的 { } { } 括号中的动作。 这里另括号中的动作。 这里另一个有用的就是 一个有用的就是 $1 $1 和 和 $3 $3 的使用的使用 , , 它们它们引用了标记 引用了标记 NAME NAME 和 和 NAMENAME (或者第二(或者第二行的 行的 VALUEVALUE )的值。 )的值。
• 标记 标记 NAME NAME 的 的 Lex Lex 代码是这样的 代码是这样的 • char [A-Za-z] char [A-Za-z]
name {char}+ name {char}+
%% %%
{name} { yylval = strdup(yytext); {name} { yylval = strdup(yytext); return NAME; } return NAME; }
• 文件解析例子的规则段是这样的: 文件解析例子的规则段是这样的: • 文件解析的语法 文件解析的语法
file : record file file : record file
| record | record
; ;
record: NAME EQ AGE { record: NAME EQ AGE { printf("%s is now %s years old!!!", $1, $3);} printf("%s is now %s years old!!!", $1, $3);}
; ;
%% %%
附加 附加 C C 代码 代码
•一个函数如 一个函数如 main() main() 调用 调用 yyparse() yyparse() 函数函数(( Yacc Yacc 中 中 Lex Lex 的 的 yylex() yylex() 等效函数)。 等效函数)。 一般来说,一般来说, Yacc Yacc 最好提供 最好提供 yyerror(char yyerror(char msg) msg) 函数的代码。 当解析器遇到错误时函数的代码。 当解析器遇到错误时调用 调用 yyerror(char msg)yyerror(char msg) 。错误消息作为。错误消息作为参数来传递。 参数来传递。
• 一个简单的 一个简单的 yyerror( char* ) yyerror( char* ) 可能是这样的: 可能是这样的:
int yyerror(char* msg) int yyerror(char* msg)
{ {
printf("Error: %s printf("Error: %s encountered at line number:%d\n", msg, yylinen encountered at line number:%d\n", msg, yylineno); o);
} }
• 这一段还包括文件解析例子的主函数: 这一段还包括文件解析例子的主函数:
附加 附加 C C 代码 代码
void main() void main()
{ {
yyparse(); yyparse();
} }
int yyerror(char* msg) int yyerror(char* msg)
{ {
printf("Error: %s printf("Error: %s encountered \n", msg); encountered \n", msg);
实习要求实习要求
•用用 LEXLEX 编写编写 CC 语言词法分析器语言词法分析器•输入:输入: CC 语言程序 输出:词法分析结果语言程序 输出:词法分析结果•用用 YACCYACC 编写编写 CC 语言语法分析器语言语法分析器•提交:提交: .c.c 文件和文件和 .exe.exe 文件文件•提交时间:最晚考试前提交提交时间:最晚考试前提交• ftpftp 上有上有 PascalPascal的例子的例子
需要的软件需要的软件
• Parser GeneratorParser Generator•在在 ftpftp 上包括这个软件的使用方法和安装文上包括这个软件的使用方法和安装文件件
• VC6VC6或或 77
CC 语言语言
auto break case char const continueauto break case char const continue ddefaulefault t dodo doubledouble elseelse enumenum externextern
floatfloat forfor gotogoto ifif intint longlong registerregister returnreturn shortshort signedsigned sizeofsizeof staticstatic structstruct switchswitch typedeftypedef unionunion unsignedunsigned voidvoid volatilevolatile whilewhile