作者:Riceball([email protected])
关键字: 编译器, 解释器, LEX, YACC, 编译原理, 正则表达式, Pascal
预备知识:编译原理,正则表达式,Pascal
本文并不希望深入透彻的讲解编译原理,而是讲解如何利用工具(生成编译器的编译器)去编写编译器。如果你完全不知道编译什么东西,那么请看懂了编译原理,在看此文,本文不是为初学者准备的。
一、什么是编译器(解释器)
编译器是将一种计算机语言翻译为另一种计算机语言的程序。编译器将源程序(source language)
编写的程序作为输入,翻译产生用目标语言(target language)编写的等价程序。源程序一般为高级语言(high-level language),如Pascal 或Delphi,而目标语言则是汇编语言或目标机器的目标代码(object code),有时也称作机器代码(machine code)
源程序→ 编译器→ 目标程序
解释器也是同编译器一样的一种语言翻译程序。它与编译器的不同之处在于:它立即执行源程序而不是生成目标代码。从原理上讲,任何程序设计语言都可以被解释或被编译。
(1) 扫描程序(scanner)
由扫描程序(Scanner)阅读源程序(通常以字符流的形式表示),进行词法分析(Lexical analysis),它将源程序翻译成单词ID(Token),放入单词ID(Token)表中。在此过程中,扫描程序会进行简单的拼写检查。
单词ID(Token):
一个Token可以有若干种类型,典型的有:关键字(keyword),例如 if 和 while;标识符(identifier)是由用户定义的变量名,过程名等,它们通常由字母和数字组成并由一个字母开头;特殊符号(special symbol)如算术符号+和*、一些多字符符号,如 >= 和<>。TokenType 为枚举数据类型。实际上就是整数值,每一个数值代表一种单词ID类型。
TTokenType = (ttNone, ttStrVal, ttIntVal, ttFloatVal,
//ttNAME 就是标识符 Identifier 或关键字
ttNAME,
ttSWITCH,
ttVAR, ttCONST, ttTYPE, ttRECORD, ttARRAY, ttDOT, ttDOTDOT, ttOF,
ttTRY, ttEXCEPT, ttRAISE, ttFINALLY, ttON, ttREAD, ttWRITE, ttPROPERTY,
ttPROCEDURE, ttFUNCTION, ttCONSTRUCTOR, ttDESTRUCTOR, ttCLASS, ttNIL, ttIS,
ttAS,
ttVIRTUAL, ttOVERRIDE, ttREINTRODUCE, ttINHERITED, ttABSTRACT,
ttEXTERNAL, ttFORWARD, ttIN,
ttBEGIN, ttEND, ttBREAK, ttCONTINUE, ttEXIT,
ttIF, ttTHEN, ttELSE, ttWHILE, ttREPEAT, ttUNTIL, ttFOR, ttTO, ttDOWNTO, ttDO,
ttCASE,
ttTRUE, ttFALSE, ttAND, ttOR, ttXOR, ttDIV, ttMOD, ttNOT, ttPLUS, ttMINUS,
ttTIMES, ttDIVIDE,
ttEQ, ttNOTEQ, ttGTR, ttGTREQ, ttLESS, ttLESSEQ, ttSEMI, ttCOMMA, ttCOLON,
ttASSIGN,
ttBLEFT, ttBRIGHT, ttALEFT, ttARIGHT, ttCRIGHT,
ttDEFAULT,
// Tokens for compatibility to Delphi
ttPRIVATE, ttPROTECTED, ttPUBLIC, ttPUBLISHED,
ttREGISTER, ttPASCAL, ttCDECL, ttSTDCALL, ttFASTCALL);
TTokenTypes = set of TTokenType;
为了表示 Token 的内容,一般我们这样定义Token:
TToken = Record
TokenType: TTokenType;
TokenValue: Variant;
end;
TP LEX
TP Lex 是词法分析(Lexical analysis)扫描器源程序的生成器,它用于创建Pascal(TurboPascal, Delohi)扫描器子过程。
TP Lex 分析 LEX 文件(默认扩展名为.L),产生词法分析(Lexical analysis)扫描器过程,输出pascal源程序文件。如果 LEX 文件在分析过程发现错误,错误信息将会被写入相应的列表文件(扩展名为.lst)。
创建的 pascal 源文件程序将包含词法分析(Lexical analysis)扫描器过程:yylex。
function yylex : Integer;
你应该在你的主程序中调用该过程进行词法分析。每调用一次,yylex 的返回值为当前分析的Token类型值。当文件结束时,yylex 的返回值为0。
yylex 过程的代码模板在 yylex.cod 文件中。TP Lex 需要该文件构建生成 pascal 源程序文件。该文件必须在当前目录或TP LEX所在目录下。另外生成好的源程序需要 LexLib.pas 文件进行编译。
用法:
lex [options] lex-file[.l] [output-file[.pas]]
Options(参数)
-------
-v "详尽(Verbose):" 在该参数下,Lex 在生成词法分析器的同时,将生成一个可读的说明文件,扩展名`.lst'.
-o "优化(Optimize):" Lex 将优化 DFA 表,产生一个最小的 DFA.
如何编写 LEX 文件(.L)
LEX 文件(.L)分为三个部分,每个部分之间用“%%”隔开:
定义部分(definitions)
%%
规则部分(rules)
%%
辅助过程部分(auxiliary procedures)
三个部分可以都是空的也没有关系,以行为单位作为语句的分隔符。
定义部分(definitions)
定义部分出现在第1个双百分号之前。定义部分(definitions)可以包含以下的元素:
- 正则表达式一般定义格式:
定义的表达式名称(name) 替换的结果(substitution)
正则表达式的名字也得在该部分定义。这个名字的定义写在另一行的第1列,且其后(后面有一个或多个空格)是它所表示的正则表达式。定义的名称(name)必须是一个合法的标识符(第一位必须是字母,第二位可以是字母或数字)
替换的结果(substitution)是一个LEX正则表达式,你也可以在正则表达式中引用前面定义好的表达式名称,只要将该名称用花括号("{}")扩起即可。例如,带符号数字的定义:
Number [0-9]+
signedNumber ("+"|"-")? {Number}
- 开始(start)状态按照如下的格式书写:
%start name ...
这用来指定规则的启动条件(详见规则部分)。%start 关键字可以被简写为 %s 或 %S.
- “%{”与“%}”对
插入在 “%{”与“%}”对中间的是函数外部的任意Pascal源代码(请注意这些字符的顺序)。
规则部分(rules)
它们由一连串带有Pascal代码的正则表达式组成;当匹配相对应的正则表达式时,后面的Pascal代码(动作)就会被执行。规则的格式如下:
正则表达式(expression) 语句(statement);
注意:语句(statement)必须是单独的一个Pascal语句,最后以分号结尾(如果有多个语句使用Begin...End)。语句(statement)可以分成多行书写,不过后续行必须首先至少留一个空格或tab,用来指示该行是属于上一行的。使用“|”表示该表达式执行的动作和下一个表达式执行的动作(语句)一样。例如,Pascal的注释:
"(*" |
"{" begin
repeat
c := get_char;
case c of
'}' : ;
'*' : begin
c := get_char;
if c=')' then exit else unget_char(c)
end;
#0 : begin
commenteof;
exit;
end;
end;
until false
end;
TP Lex 库单元提供了一系列有用的变量和过程,你可以在你编写的动作(语句)中使用。如:yytext 变量返回匹配的字符串。yyleng 变量返回匹配的字符串长度。
在规则部分中的“%{”与“%}”对,中间插入的Pascal源代码,被当作是动作的局部变量(过程)出现。
辅助过程部分(auxiliary procedures)
辅助过程部分可以包含Pascal源程序,如辅助过程或主程序,该部分会被简单的放在文件的末尾。
本文地址:http://com.8s8s.com/it/it5455.htm