使用Lex工具进行tiny+语言的词法分析
詞法分析程序?qū)嶒瀳蟾?/h1>
實驗環(huán)境
- 架構(gòu):Intel x86_64 (虛擬機)
- 操作系統(tǒng):Ubuntu 20.04
- 匯編器:gas (GNU Assembler) in AT&T mode
- 編譯器:gcc
實驗?zāi)康?/h3>
通過擴充已有的樣例語言TINY語言的詞法分析程序,為擴展TINY語言TINY+構(gòu)造詞法分析程序,從而掌握詞法分析程序的構(gòu)造方法。
實驗內(nèi)容
了解樣例語言TINY及TINY編譯器的實現(xiàn),了解擴展TINY語言TINY+,用C語言在已有的TINY詞法分析器基礎(chǔ)上擴展,構(gòu)造TINY+的詞法分析程序。
實驗要求
將TINY+源程序翻譯成對應(yīng)的TOKEN序列,并能檢查一定的詞法錯誤。
項目介紹
源程序:lex.yy.c
可執(zhí)行程序:a.out
文件夾結(jié)構(gòu)
tiny+ |-- a.out |-- lex.yy.c |-- Makefile |-- scanner |-- scanner.l |-- test|-- ilegal_input.tny|-- test1.tny|-- test.tny |-- token |-- token.h運行方式
進入tiny+文件夾目錄,在終端輸入:
./a.out < test/test.tny // TINY+樣例詞法分析序列打印到終端 ./a.out < test/test.tny > token // TINY+樣例詞法分析序列生成到token文件 ./a.out < test/test1.tny // TINY+合法輸入測試 ./a.out < ilegal_input.tny // TINY+詞法錯誤測試TINY語言
詞法定義
- 關(guān)鍵字:WRITE READ IF THEN ELSE RETURN BEGIN END MAIN STRING INT REAL REPEAT UNTIL
- 單字符分隔符:; , ( )
- 單字符運算符:+ - * /
- 多字符運算符::= == != =
- 標(biāo)識符:標(biāo)識符由一個字母后跟任意數(shù)量的字母或數(shù)字組成。以下是標(biāo)識符的示例:x、x2、xx2、x2x、End、END2。注意End是標(biāo)識符,而END是關(guān)鍵字。以下不是標(biāo)識符:
- IF, WRITE, READ, ....(關(guān)鍵字不計為標(biāo)識符)
- 2x(標(biāo)識符不能以數(shù)字開頭)
- 注釋中的字符串不是標(biāo)識符。
- Number 是一個數(shù)字序列,或者是一個數(shù)字序列,后跟一個點,然后是數(shù)字。Number -> Digits | Digits '.' Digits Digits -> Digit | Digit Digits Digit -> '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
- 注釋:/** 和 **/ 之間的字符串。注釋可以超過一行。
EBNF 語法
高級程序結(jié)構(gòu):
Program -> MethodDecl MethodDecl* Type -> INT | REAL |STRING MethodDecl -> Type [MAIN] Id '(' FormalParams ')' Block FormalParams -> [FormalParam ( ',' FormalParam )* ] FormalParam -> Type Id聲明:
Block -> BEGIN Statement+ ENDStatement -> Block| LocalVarDecl | AssignStmt | ReturnStmt| IfStmt| WriteStmt| ReadStmtLocalVarDecl -> Type Id ';' | Type AssignStmt AssignStmt -> Id := Expression ';'| Id := QString ';' ReturnStmt -> RETURN Expression ';' IfStmt -> IF '(' BoolExpression ')' Statement| IF '(' BoolExpression ')' Statement ELSE Statement WriteStmt -> WRITE '(' Expression ',' QString ')' ';' ReadStmt -> READ '(' Id ',' QString ')' ';' QString 是除雙引號本身之外的任何字符序列,用雙引號括起來。表達(dá)式:
Expression -> MultiplicativeExpr (( '+' | '-' ) MultiplicativeExpr)* MultiplicativeExpr -> PrimaryExpr (( '*' | '/' ) PrimaryExpr)* PrimaryExpr -> Num // Integer or Real numbers| Id | '(' Expression ')'| Id '(' ActualParams ')' BoolExpression -> Expression '==' Expression |Expression '!=' Expression ActualParams -> [Expression ( ',' Expression)*]樣例:
/** this is a comment line in the sample program **/ INT f2(INT x, INT y ) BEGIN INT z;z := x*x - y*y;RETURN z; END INT MAIN f1() BEGININT x;READ(x, "A41.input");INT y;READ(y, "A42.input");INT z;z := f2(x,y) + f2(y,x);WRITE (z, "A4.output"); ENDTINY+
TINY+是對TINY語言的一個擴充,比TINY多了程序的聲明部分,while語句,字符串類型定義等等。
詞法定義
- 關(guān)鍵字:在TINY的關(guān)鍵字write read if then else return begin end main string int real repeat until的基礎(chǔ)上,擴充了or and bool char while do這幾個關(guān)鍵字,小寫字母表示,自定義標(biāo)識符不能和關(guān)鍵字重復(fù)。
- 特殊符號:在TINY的特殊符號; , ( ) + - * / := == != =的基礎(chǔ)上,擴充了> < <= >= '這幾個特殊符號。
- 其他種類的單詞包括標(biāo)識符ID,數(shù)字NUM以及字符串STRING,他們的正規(guī)表達(dá)式的定義如下:
- 標(biāo)識符是以字母開頭,由字母和數(shù)字混合構(gòu)成的符號串:ID=letter (letter | digit)*
- TINY+中對數(shù)字的定義和TINY相同:NUM=digit digit*
- 一個字符串類型的單詞是用單引號括起來的字符申’…’,引號內(nèi)可出現(xiàn)除了’以外的任何符號。一個字符串不能跨行定義:STRING=any character except''
- 小寫和大寫字母是不同的:letter=a|...|z|A|...|Z digit=0|...|9
- 空白包括空格、回車以及Tab。所有的空白在詞法分析時,被當(dāng)作單詞ID, NUM以及保留字的分隔符,在詞法分析之后,他們不被當(dāng)作單詞保留。
- 注釋是用花括號括起來的符號串{…},注釋不能嵌套定義,但注釋的定義可以跨行。
EBNF 語法
和TINY相比,擴展的部分有:
Statement -> Block| LocalVarDecl | AssignStmt | ReturnStmt| IfStmt| WriteStmt| ReadStmt| WhileStmt| DoWhileStmt| ForStmtWhileStmt -> WHILE '(' BoolExpression ')' Statement DoWhileStmt -> DO Statement WHILE '(' BoolExpression ')' ForStmt -> For AssignStmt UPTO Expression DO Statement| For AssignStmt DOWNTO Expression DO StatementMultiplicativeExpr -> PrimaryExpr (( '*' | '/' | '%' ) PrimaryExpr)* BoolExpression -> Expression '==' Expression| Expression '!=' Expression | Expression '>' Expression| Expression '<' Expression樣例
在之前TINY語言的樣例基礎(chǔ)上,使用TINY+將樣例改動為:
{ this is a comment line in the sample program } int f2(int x, int y ) begin int z;z := x*x - y*y;return z; end int main f1() beginint x;read(x, 'A41.input');int y;read(y, 'A42.input');int z;z := f2(x,y) + f2(y,x);write (z, 'A4.output'); end實現(xiàn)過程
根據(jù)課程網(wǎng)站中有關(guān)lex工具的資料,可以通過lex生成C語言版的TINY+的詞法分析程序。
首先安裝lex工具:
在Linux上終端輸入下列命令:
sudo apt-get install flex然后在終端中輸入:
lex --version如果顯示lex的版本,那么安裝成功。
然后創(chuàng)建flex模式文件:
創(chuàng)建一個tiny+目錄,進入這個目錄中,創(chuàng)建一個scanner.l文件,輸入下列內(nèi)容:
%{ #include "token.h" int cur_line_num = 1; void lex_error(char* msg, int line); %}/* Definitions */ KEY ("write"|"read"|"if"|"then"|"else"|"return"|"begin"|"end"|"main"|"string"|"int"|"real"|"or"|"and"|"int"|"bool"|"char"|"while"|"do"|"repeat"|"until") SYM (";"|","|"("|")"|"+"|"-"|"*"|"/"|":="|"=="|"!="|">"|"<"|"<="|">="|"="|"'") ID ([a-zA-z][a-zA-Z0-9]*) NUM ([0-9][0-9]*) STR (\'[^\'\n]*\') UNTERM_STR1 ((\'[^\'\n]*)) UNTERM_STR2 (\'\n) COMMENT (\{[^\{\}]*\}) UNTERM_COMMENT (\{[^\{\}]*[\{]*) ILLEGAL_SYMBOL ([^[a-zA-Z0-9]|";"|","|"("|")"|"+"|"-"|"*"|"/"|":="|"=="|"!="|">"|"<"|"<="|">="|"="|"'"])%%[\n] { cur_line_num++; } [ \t\r\a]+ { /* ignore all spaces */ }{KEY} { return T_KEY; } {SYM} { return T_SYM; } {ID} { return T_ID; } {NUM} { return T_NUM; } {STR} { return T_STR; } {COMMENT} { /* skip for comment */ } {ILLEGAL_SYMBOL} { lex_error("An illegal symbol was found", cur_line_num); } {UNTERM_STR1} { lex_error("The string is missing a closing quote", cur_line_num); } {UNTERM_STR2} { lex_error("The string is missing a closing quote", cur_line_num); cur_line_num++; } {UNTERM_COMMENT} { lex_error("The comment is missing a closing bracket", cur_line_num); }<<EOF>> { return 0; }%%int main(int argc, char* argv[]) {int token;while (token = yylex()) {print_token(token);printf("%s", yytext);printf(")\n");}return 0; }void lex_error(char* msg, int line) {printf("\nError at line %-3d: %s\n\n", line, msg); }int yywrap(void) {return 1; }flex 模式文件中,%{ 和 %} 之間的為 聲明(Declarations) ,都是 C 代碼,這些代碼會被原樣的復(fù)制到 lex.yy.c 文件中,一般在這里聲明一些全局變量和函數(shù),這樣在后面可以使用這些變量和函數(shù):
%{ #include "token.h" int cur_line_num = 1; void lex_error(char* msg, int line); %}cur_line_num用來記錄當(dāng)前掃描到的行號。
%} 和 %% 之間的為 定義(Definitions),在這里可以定義正則表達(dá)式中的一些名字,可以在 規(guī)則(Rules) 段被使用,如本文件中定義了 NUM 為 ([0-9][0-9]*) , 這樣在后面可以用 {NUM} 代替這個正則表達(dá)式,這里根據(jù)TINY+語言的要求,定義了五個單詞種類,并定義了出現(xiàn)詞法錯誤時進行匹配的串:
/* Definitions */ KEY ("write"|"read"|"if"|"then"|"else"|"return"|"begin"|"end"|"main"|"string"|"int"|"real"|"or"|"and"|"int"|"bool"|"char"|"while"|"do"|"repeat"|"until") SYM (";"|","|"("|")"|"+"|"-"|"*"|"/"|":="|"=="|"!="|">"|"<"|"<="|">="|"="|"'") ID ([a-zA-z][a-zA-Z0-9]*) NUM ([0-9][0-9]*) STR (\'[^\'\n]*\') UNTERM_STR1 ((\'[^\'\n]*)) UNTERM_STR2 (\'\n) COMMENT (\{[^\{\}]*\}) UNTERM_COMMENT (\{[^\{\}]*[\{]*) ILLEGAL_SYMBOL ([^[a-zA-Z0-9]|";"|","|"("|")"|"+"|"-"|"*"|"/"|":="|"=="|"!="|">"|"<"|"<="|">="|"="|"'"])- KEY表示關(guān)鍵字;
- SYM表示特殊符號;
- ID表示標(biāo)識符;
- NUM表示數(shù)字常量
- STR表示字符串常量
- COMMENT表示注釋
- ILLEGAL_SYMBOL表示非法符號,詞法分析器可能識別到一個TINY+程序的字母表中不允許的符號,比如識別到$,那么應(yīng)報告一個詞法錯誤,發(fā)現(xiàn)了一個非法符號;
- UNTERM_STR1和UNTERM_STR2表示單引號不匹配,例如出現(xiàn)’ scanner,應(yīng)報告錯誤“字符串缺少右引號”;
- UNTERM_COMMENT表示注釋的括號不匹配,例如出現(xiàn){this is an example,應(yīng)報告錯誤“注釋缺少右括號”。
%% 和 %% 之間的內(nèi)容被稱為 規(guī)則(rules),本文件中每一行都是一條規(guī)則,每條規(guī)則由 匹配模式(pattern) 和 事件(action) 組成, 模式在前面,用正則表達(dá)式表示,事件在后面,即 C 代碼。每當(dāng)一個模式被匹配到時,后面的 C 代碼被執(zhí)行。
簡單來說,flex 會將本段內(nèi)容翻譯成一個名為 yylex 的函數(shù),該函數(shù)的作用就是掃描輸入文件(默認(rèn)情況下為標(biāo)準(zhǔn)輸入),當(dāng)掃描到一個完整的、最長的、可以和某條規(guī)則的正則表達(dá)式所匹配的字符串時,該函數(shù)會執(zhí)行此規(guī)則后面的 C 代碼。如果這些 C 代碼中沒有 return 語句,則執(zhí)行完這些 C 代碼后, yylex 函數(shù)會繼續(xù)運行,開始下一輪的掃描和匹配。
當(dāng)有多條規(guī)則的模式被匹配到時, yylex 會選擇匹配長度最長的那條規(guī)則,如果有匹配長度相等的規(guī)則,則選擇排在最前面的規(guī)則。
%%[\n] { cur_line_num++; } [ \t\r\a]+ { /* ignore all spaces */ }{KEY} { return T_KEY; } {SYM} { return T_SYM; } {ID} { return T_ID; } {NUM} { return T_NUM; } {STR} { return T_STR; } {COMMENT} { /* skip for comment */ } {ILLEGAL_SYMBOL} { lex_error("An illegal symbol was found", cur_line_num); } {UNTERM_STR1} { lex_error("The string is missing a closing quote", cur_line_num); } {UNTERM_STR2} { lex_error("The string is missing a closing quote", cur_line_num); cur_line_num++; } {UNTERM_COMMENT} { lex_error("The comment is missing a closing bracket", cur_line_num); }<<EOF>> { return 0; }%%- 匹配到了普通單詞種類KEY、SYM、ID、NUM、STR會返回一個值,在token.h文件中進行相應(yīng)的組合token和打印處理;
- 匹配到了注釋COMMENT直接跳過,不做任何處理;
- 匹配到了非法符號ILLEGAL_SYMBOL直接跳過該行后面所有內(nèi)容,并報告錯誤An illegal symbol was found和行號。
- 匹配到了單引號不匹配UNTERM_STR1和UNTERM_STR2直接跳過該行后面所有內(nèi)容,并報告錯誤The string is missing a closing quote和行號。
- 匹配到了注釋的括號不匹配UNTERM_COMMENT直接跳過后面所有內(nèi)容,并報告錯誤The comment is missing a closing bracket和行號。
最后的 main 函數(shù)是程序的入口, flex 會將這些代碼原樣的復(fù)制到 lex.yy.c 文件的最后面:
int main(int argc, char* argv[]) {int token;while (token = yylex()) {print_token(token);if (token == (int)TokenType.T_STR) {int i = 1;while (yytext[i] != '\'') {printf("%c", yytext[i]);i++;}}else {printf("%s", yytext);}printf(")\n");}return 0; }void lex_error(char* msg, int line) {printf("\nError at line %-3d: %s\n\n", line, msg); }int yywrap(void) {return 1; }接著創(chuàng)建token.h文件:
在同一個文件目錄下,創(chuàng)建token.h文件,并輸入內(nèi)容:
#ifndef TOKEN_H #define TOKEN_Htypedef enum {T_KEY = 256, T_SYM, T_ID, T_NUM, T_STR } TokenType;static void print_token(int token) {static char* token_strs[] = {"KEY", "SYM", "ID", "NUM", "STR"};if (token < 256) {printf("%-20c", token);} else z{printf("(%s, ", token_strs[token-256]);} }#endif最后創(chuàng)建Makefile文件:
在同一個文件目錄下,創(chuàng)建token.h文件,并輸入內(nèi)容:
out: scannerscanner: lex.yy.c token.hgcc -o $@ $<lex.yy.c: scanner.lflex $<生成源程序和可運行程序:
進入文件目錄下,在終端輸入make,生成源程序lex.yy.c和可運行程序scanner。
在終端再輸入:
gcc lex.yy.c
也可生成可運行程序a.out
測試報告
樣例TINY+測試
在tiny+文件夾目錄,創(chuàng)建一個test文件夾,在這個文件夾中,創(chuàng)建一個test.tny文件,并把TINY+樣例放進這個文件里面:
{ this is a comment line in the sample program } int f2(int x, int y ) begin int z;z := x*x - y*y;return z; end int main f1() beginint x;read(x, 'A41.input');int y;read(y, 'A42.input');int z;z := f2(x,y) + f2(y,x);write (z, 'A4.output'); end回到tiny+文件夾目錄,在終端輸入:
./scanner < test/test.tny或
./a.out < test/test.tny可以看到:
詞法分析成功。
生成token序列,在終端輸入:
./a.out < test/test.tny > token可以看到:
詞法分析的token序列已經(jīng)被生成到了token文件中。
合法輸入測試
在test文件夾,創(chuàng)建一個test1.tny文件,輸入下列內(nèi)容:
or and int bool char while do if then else end repeat until read write , ; := + - * / ( ) < = > <= >= a2c 123 'EFG' { this is an example } { this is an example }對所有的關(guān)鍵字KEY、特殊符號SYM、符合格式要求的標(biāo)識符ID、符號格式要求的數(shù)組常量NUM、符號格式要求的字符串常量STR、單行和多行注釋COMMENT進行測試,回到tiny+文件夾目錄,在終端輸入:
./a.out < test/test1.tny可以看到:
所有的合法輸入KEY、SYM、ID、NUM、STR都被識別出來并組成token序列,單行和多行注釋也被去掉。
詞法錯誤測試
在test文件夾,創(chuàng)建一個ilegal_input.tny文件,輸入下列內(nèi)容:
$ # ' scanner scanner ' { this is a comment line in the sample program對非法符號錯誤、單引號不匹配錯誤、注釋的括號不匹配錯誤進行測試,回到tiny+文件夾目錄,在終端輸入:
./a.out < ilegal_input.tny可以看到:
第1行和第2行的非法符號$和#被識別出來并報告錯誤An illegal symbol was found發(fā)現(xiàn)非法符號,第3行和第4行后面的單引號不匹配被識別出來并報告錯誤The string is missing a closing quote沒有右引號,第5行的注釋的括號不匹配被識別出來并報告錯誤The comment is missing a closing bracket沒有右括號。
總結(jié)
以上是生活随笔為你收集整理的使用Lex工具进行tiny+语言的词法分析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 软件测试作业8:分析自动售货机软件例子生
- 下一篇: 使用pthread和线程池实现B+树的并