标准ML的词法分析器生成器。
版本1.6.0,1994年10月

安德鲁·W·阿佩尔
詹姆斯·马特森
大卫·R·塔迪蒂

计算机系科学类,普林斯顿大学

(c) 1989-94年安德鲁·W·阿佩尔(Andrew W.Appel)、詹姆斯·S·马特森(James S.Mattson)、大卫·R·塔迪蒂(David R.Tarditi)

此软件绝对没有保修。它只受ML-Yacc通知、许可和免责声明的条款(在文件COPYRIGHT随此软件分发)。

此版本中的新增功能:




目录




1.概述

计算机程序通常需要将其输入分成单词和区分不同种类的单词。编译器,用于例如,需要区分整数、保留字和标识符。应用程序通常需要能够识别用户输入命令的组件。

将输入分割为单词和识别类别的问题单词被称为词汇分析。这个问题的小案例,例如读取由空格分隔的文本字符串,可以通过使用手写程序。此问题的较大案例,例如为编译器标记输入流,也可以使用手写程序。

然而,一个用于大型词汇分析问题的手写程序,面临两大问题。首先,该计划要求公平要创建的程序员时间量。第二,描述程序中没有明确的单词类别。必须进行推断从程序代码。这使得很难验证程序识别每个类的正确单词。它还使未来维护程序困难。

Lex是Unix系统的编程工具,是一个成功的解决方案词汇分析的一般问题。它使用常规描述单词类别的表达式。程序片段是与每一类单词相关。此信息提供给Lex作为规范(Lex程序)。Lex为可用于执行词汇分析的函数。

该功能的操作如下。它会从最长的单词开始查找从输入流中的当前位置单词类。它执行与类,并将输入流中的当前位置设置为单词后面的字符。程序片段包含实际文本可以是任何代码段。对许多人来说应用程序返回某种值。

Lex允许程序员明确语言描述,专注于如何处理已识别的单词,而不是如何识别单词。它节省了程序员的时间并增加了程序可维护性。

不幸的是,莱克斯只针对C。它还放置了人工对可识别字符串大小的限制。

ML-Lex是用于ML编程语言的Lex的变体。ML-Lex(ML-Lex)具有类似于Lex的语法,并生成ML程序,而不是C程序。ML-Lex生成了一个运行非常高效的程序。通常,程序将与在标准ML中实现的手工编码lexer。

该程序通常只使用少量空间。因此,ML Lex为ML程序员提供了与Lex为C语言提供的好处相同的好处程序员。它也没有人为地限制识别的字符串。



2.ML-Lex规格

ML-Lex规范具有通用格式:

用户声明%%ML-Lex定义%%规则

每个部分与其他部分用%%分隔符。

这些规则用于定义词汇分析功能。每个规则由两部分组成——正则表达式和操作。普通人表达式定义规则匹配的单词类。行动是当规则与输入匹配时要执行的程序片段。这个操作用于计算值,并且必须返回同一类型。

用户可以定义用户中所有规则操作可用的值声明部分。用户必须在此中定义两个值section——lexresult类型和eof函数。Lexresult定义了规则操作返回的值的类型。功能“eof”是当到达输入流的末尾时,lexer调用。通常会返回一个发送eof信号的值或引发异常。它使用与lex相同的参数调用(请参见%参数,如下),并且必须返回lexresult类型的值。

在定义部分,用户可以定义命名规则表达式,一组启动状态,并指定各种需要ML-Lex的钟声和哨声。

启动状态允许用户控制特定规则何时匹配。只有当lexer位于特定的启动状态。用户可以更改lexer的启动状态在规则操作中。这允许用户指定特殊处理词汇对象。

此功能通常用于处理带转义符的引用字符串表示特殊字符。识别内部的规则字符串的内容只为一个开始状态定义。这个当识别出字符串的开头时进入开始状态,并在识别出字符串末尾时退出。



3.正则表达式

正则表达式是表示类的简单语言串。正则表达式是在带有一组基本操作的字母表。ML-Lex的字母表为Ascii字符集(字符代码0-127;或如果%完全使用,0-255)。

正则表达式的语法和语义将在降低优先级的顺序(从最紧密绑定的运算符开始至最弱的结合):

单个字符代表自身,除了保留字符? * + | ( ) ^ $ / ; . = < > [ { " \


反斜杠后跟一个保留字符对于那个角色。

用方括号[]括起来的一组字符对于其中任何一个字符。仅在支架内部符号\ - ^已被保留。初始向上箭头^站立用于所列字符的补码,例如。[^abc]表示除a、b或c之外的任何字符。连字符-表示一系列字符,例如。【a-z】代表任何小写字母字符,以及[0-9a-fA-F]代表任何十六进制数字。包括^按字面意思,把它放在括号里的任何地方但首先;包括-按字面意思,在一组中,把它放在第一或最后。

.
圆点.字符代表除换行符外的任何字符,即与[^\n]

内部有以下特殊的转义序列或方形支架外:

\b条
退格
\n个
换行符
\t吨
选项卡
\小时
代表代码>127的所有字符,使用7位字符时。
\ddd(ddd)
其中ddd是3位数十进制转义。

"
一系列字符将代表自身(保留字符将按字面意思表示)双引号" ".

{}
命名正则表达式(在“定义”中定义第节)可通过将其名称括在支撑{ }.

()
任何正则表达式都可以用括号括起来( )用于句法(但通常不是语义)效果。

*
后缀运算符*代表Kleene闭合:前面的表达式重复零次或多次。

+
后缀运算符+代表一次或多次重复前面表达式的。

后缀运算符代表零次或一次出现前面的表达式。

后缀重复范围{a、 b条}其中b条都很小整数表示在b条前面表达式的。符号{}代表确切地重复。

表达式的串联表示字符串的串联。表达式ef(参考)表示由匹配的一个字符串的串联e(电子)和另一个匹配的字符串如果.

|
中缀运算符|代表交替。表达式 e(电子)|如果代表任何任何一个e(电子)如果代表。

/
中缀运算符/表示向前看。Lookahead不是已实现且无法使用,因为存在错误在使用lookahead生成lexer的算法中。如果它可以被使用,表达式e(电子)/如果将匹配任何字符串那个e>代表,但仅当该字符串后跟匹配的字符串如果.

当向上箭头^出现在表达式的开头,该表达式将只匹配出现在行的开头(在换行符之后)。

$
C Lex$的美元符号没有实现,因为它是一个缩写对于涉及换行符的lookahead(即是的缩写/\n个).

下面是正则表达式的一些示例,以及它们表示的字符串集:

0 | 1 | 2 | 3
0到3之间的一位数
[0123]
0到3之间的一位数
0123
字符串“123”
0*
0或更多0的所有字符串
00*
1个或多个0的所有字符串
0个+
1个或多个0的所有字符串
[0-9]{3}
任何三位数的十进制数。
\\【ntb】
换行符、制表符或退格符。
(00)*
偶数为0的任何字符串



4.ML-Lex语法摘要




4.1用户声明

第一次之前的任何事情%%位于用户声明部分。这个用户应该注意,没有符号标识符包含%%可以是在本节中使用。




4.2 ML-Lex定义

可以使用定义起始状态

%秒标识符列表

标识符列表由一个或多个标识符组成。
标识符由一个或多个字母、数字、下划线、,或素数,必须以字母开头。
命名表达式可以用定义

标识符=正则表达式;

正则表达式定义如下。
以下%命令也可用:

%拒绝
创建REJECT()函数
%计数
使用yylineno计算新行数
%完全
为完整的8位字符集创建lexer,允许使用0-255范围内的字符作为输入。
%结构{标识符}
命名输出程序中的结构标识符而不是Mlex
%收割台
使用后面的代码为lexer创建头结构
%参数
额外的(curried)形式参数参数传递给lex函数,并将被传递用eof函数代替()

这些功能将在第节中讨论5.




4.3规则

每个规则都有以下格式:

<启动状态列表> 正则表达式 => ( 代码 );

中的所有括号代码必须平衡,包括用于字符串和注释。

这个启动状态列表是可选的。它由以下列表组成标识符由逗号分隔,并由三角形分隔括号<>。每个标识符必须是在%秒第节。

只有当lexer位于启动状态列表中的启动状态。如果没有启动状态列表给定,表达式在所有启动状态下都可以识别。

lexer以预定义的启动状态开始,称为姓名首字母.

lexer通过使用选择规则来解决规则之间的冲突最长匹配,在这种情况下,两个规则匹配同一字符串,选择规范中首先列出的规则。

规则应与所有可能的输入匹配。如果有输入发生不匹配任何规则,ML-Lex创建的lexer将引发异常LexError。请注意,这与打印的C Lex不同标准输出中任何不匹配的输入。




5.与规则关联的代码中可用的值。

 

ML-Lex放置正则表达式匹配的字符串的值在里面yytext(yytext),字符串变量。

用户可以递归地用调用词法分析函数lex().(如果%参数使用时可以使用相同的参数重新调用词法分析函数继续()。)这样可以方便地忽略空白或无提示注释:

[\\t\n]+=>(lex());

要切换启动状态,用户可以调用YYBEGIN公司名称为启动状态。

只有在相应的%命令位于ML-Lex定义部分:

价值 %命令 描述
拒绝 %拒绝 拒绝()导致当前规则为“拒绝。“lexer的行为就像当前规则没有匹配;另一个与此字符串匹配的规则,或与而是使用此字符串的可能最长前缀。
yypos公司 文件开头的当前字符位置。
yylineto公司 %计数 当前行号

只有在必要时才能使用这些值。正在添加拒绝lexer会将速度降低20%;添加yyline编号会慢下来另外20%或更多。(更有效地认出
n个
有一个增加line-number变量的操作。)使用前瞻操作员/也会降低整个lexer的速度。角色位置,yypos公司然而,维护成本并不高。




6.运行ML-Lex

从Unix shell运行sml-lex我的文件.lex输出文件为myfile.lex.sml。扩展名为.法律不是必需,但建议使用。

在交互式系统中[不是首选方法]:使用lexgen.sml语言; 这将创建一个结构LexGen。功能LexGen.LexGen从输入为lexer创建程序规范。它接受一个字符串参数-文件名包含输入规范。输出文件名为通过附加确定``.sml格式将“”添加到输入文件名。




7.使用ML-Lex生成的程序

加载输出文件时,它将创建一个结构Mlex包含函数品牌Lexer从中获取函数int->字符串并返回词法分析函数。

例如,

val lexer=Mlex.makeLexer(输入c(open_in“f”))

创建一个lexer,对名为f的文件进行操作。

功能应读取字符串从输入流。它应该返回一个空字符串以指示已经到达了溪流的尽头。整数是lexer希望读取的字符数;该函数可以返回任意非零字符数。例如,

val lexer=设val input_line=fn f=>让有趣的循环结果=设val c=输入(f,1)val结果=c::result如果String.size c=0 orelse c=“\n”那么String.implode(版本结果)else循环结果结束回路中零结束在Mlex.makeLexer中(fn n=>input_line std_in)结束

适用于出现提示等的交互式流;莱克瑟不会在意的input_line(输入行)可能会返回更多字符串小于或等于n个字符。

lexer尝试从输入中读取大量字符函数,并且希望输入函数返回尽可能多。一次阅读多个字符使lexer效率更高。输入调用和缓冲操作更少并且输入在大数据块读取中效率更高。对于交互式流这不太令人担忧,因为这是一个限制因素是用户可以键入的速度。

要获取值,请通过传递一个单元来调用lexer:

val nextToken=lexer()

如果要重新启动lexer,只需放弃列克瑟并在同一个流上创建一个新的lexer,再调用品牌Lexer。这是丢弃任何缓冲字符的最佳方法内部由lexer执行。

用户声明部分中的所有代码都放在结构UserDeclarations。要访问此结构,请使用路径名姆莱克斯。用户声明.

如果任何输入都不匹配,程序将引发异常姆莱克斯。Lex错误。内部错误(即错误)将导致例外内部。Lexer错误有待提高。

如果%结构使用时,请记住结构名称不会更长的是Mlex,但在命令中指定的那个。



8.样品

下面是计算器程序的示例lexer:

数据类型lexresult=DIV | EOF | EOS |字符串ID | LPAREN|整数的数量|PLUS|PRINT|RPAREN|SUB|TIMESval linenum=参考1val错误=fn x=>输出(标准输出,x^“\n”)值eof=fn()=>eof%%%结构CalcLexα=[A-Za-z];数字=[0-9];ws=[\\t];%%\n=>(inc linenum;lex());{ws}+=>(lex());“/”=>(DIV);“;”=>(EOS);“(”=>(LPAREN);{数字}+=>(NUM(revfold(fn(a,r)=>ord(a)-ord(“0”)+10*r)(分解yytext)0));“)”=>(RPAREN);“+”=>(加号);{alpha}+=>(如果yytext=“print”,则打印其他ID yytext);“-”=>(SUB);“*”=>(时间);.=>(错误(“calc:忽略错误字符”^yytext);lex());

以下是计算器的解析器:

(*演示lexer使用的交互式计算器示例最初的语法是stmt_list->stmt_list stmt报表->打印导出;|经验;exp->exp+t | exp-t | tt->t*f|t/f|ff->(exp)|id|num函数parse获取一个流并将其解析为计算器程序。如果发生语法错误,parse将打印错误消息并调用它自己就在小溪上。在这个具有忽视效果的系统上所有输入到一行末尾。*)结构计算=结构打开CalcLex打开用户声明异常错误fun解析strm=val say=fn s=>输出(标准输出,s)值input_line=fn f=>让有趣的循环结果=设val c=输入(f,1)val结果=c::result如果String.size c=0 orelse c=“\n”那么String.implode(版本结果)else循环结果结束回路中零结束val lexer=makeLexer(fn n=>input_line strm)val nexttok=ref(lexer())val前进=fn()=>(nexttok:=lexer()!nexttok)val error=fn()=>(例如(“calc:syntax error on line”)^(标记字符串(!linenum))^“\n”);引发错误)val查找=fn i=>如果i=“一”,则为1否则,如果i=“TWO”,则为2else(例如(“calc:unknown identifier”“^i^”“\n”);引发错误)fun STMT_LIST()=箱子!nexttok,共个EOF=>()|_=>(STMT());STMT_LIST())和STMT()=(案例!nexttokEOS的=>()|打印=>(高级();say((makestring(E():int))^“\n”);())|_=>(E();());箱子!下一个托克EOS的=>(高级())|_=>错误())且E()=E'(T())和E'(i:int)=箱子!的下一个加=>(提前();E'(i+T()))|SUB=>(提前();E’(i-T())|RPAREN=>i|EOF=>i|EOS=>i|_=>错误()且T()=T'(F())和T’i=箱子!nexttok,共个加=>i|SUB=>i|时间=>(前进();T’(i*F())|DIV=>(前进();T’(i div F())|EOF=>i|EOS=>i|RPAREN=>i|_=>错误()和F()=箱子!nexttok,共个ID i=>(高级();查找i)|LPAREN=>设val v=(advance();E())如果!nexttok=RPAREN然后(前进();v) else错误()结束|NUM i=>(高级();i)|_=>错误()在STMT_LIST()handle中错误=>解析strm结束结束


弗兰克·P·弗兰纳里
美国东部时间1996年7月22日星期一14:15:03