标准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随此软件分发)。
此版本中的新增功能:
- 拒绝的成本比以前低得多。
- 具有超过255个状态的词汇分析器现在可以在您的寿命。
计算机程序通常需要将其输入分成单词和区分不同种类的单词。编译器,用于例如,需要区分整数、保留字和标识符。应用程序通常需要能够识别用户输入命令的组件。
将输入分割为单词和识别类别的问题单词被称为词汇分析。这个问题的小案例,例如读取由空格分隔的文本字符串,可以通过使用手写程序。此问题的较大案例,例如为编译器标记输入流,也可以使用手写程序。
然而,一个用于大型词汇分析问题的手写程序,面临两大问题。首先,该计划要求公平要创建的程序员时间量。第二,描述程序中没有明确的单词类别。必须进行推断从程序代码。这使得很难验证程序识别每个类的正确单词。它还使未来维护程序困难。
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语言提供的好处相同的好处程序员。它也没有人为地限制识别的字符串。
ML-Lex规范具有通用格式:
用户声明%%
ML-Lex定义%%
规则
每个部分与其他部分用%%
分隔符。
这些规则用于定义词汇分析功能。每个规则由两部分组成——正则表达式和操作。普通人表达式定义规则匹配的单词类。行动是当规则与输入匹配时要执行的程序片段。这个操作用于计算值,并且必须返回同一类型。
用户可以定义用户中所有规则操作可用的值声明部分。用户必须在此中定义两个值section——lexresult类型和eof函数。Lexresult定义了规则操作返回的值的类型。功能“eof”是当到达输入流的末尾时,lexer调用。它通常会返回一个发送eof信号的值或引发异常。它使用与lex相同的参数调用(请参见%参数
,如下),并且必须返回lexresult类型的值。
在定义部分,用户可以定义命名规则表达式,一组启动状态,并指定各种需要ML-Lex的钟声和哨声。
启动状态允许用户控制特定规则何时匹配。只有当lexer位于特定的启动状态。用户可以更改lexer的启动状态在规则操作中。这允许用户指定特殊处理词汇对象。
此功能通常用于处理带转义符的引用字符串表示特殊字符。识别内部的规则字符串的内容只为一个开始状态定义。这个当识别出字符串的开头时进入开始状态,并在识别出字符串末尾时退出。
正则表达式是表示类的简单语言串。正则表达式是在带有一组基本操作的字母表。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的任何字符串
第一次之前的任何事情%%
位于用户声明部分。这个用户应该注意,没有符号标识符包含%%
可以是在本节中使用。
可以使用定义起始状态
%秒
标识符列表;
标识符列表由一个或多个标识符组成。
标识符由一个或多个字母、数字、下划线、,或素数,必须以字母开头。
命名表达式可以用定义
标识符=正则表达式;
正则表达式定义如下。
以下%命令也可用:
- %拒绝
- 创建REJECT()函数
- %计数
- 使用yylineno计算新行数
- %完全
- 为完整的8位字符集创建lexer,允许使用0-255范围内的字符作为输入。
- %结构{标识符}
- 命名输出程序中的结构标识符而不是Mlex
- %收割台
- 使用后面的代码为lexer创建头结构
- %参数
- 额外的(curried)形式参数参数传递给lex函数,并将被传递用eof函数代替()
这些功能将在第节中讨论5.
每个规则都有以下格式:
<
启动状态列表>
正则表达式 => (
代码 );
中的所有括号代码必须平衡,包括用于字符串和注释。
这个启动状态列表是可选的。它由以下列表组成标识符由逗号分隔,并由三角形分隔括号<>
。每个标识符必须是在%秒
第节。
只有当lexer位于启动状态列表中的启动状态。如果没有启动状态列表给定,表达式在所有启动状态下都可以识别。
lexer以预定义的启动状态开始,称为姓名首字母
.
lexer通过使用选择规则来解决规则之间的冲突最长匹配,在这种情况下,两个规则匹配同一字符串,选择规范中首先列出的规则。
规则应与所有可能的输入匹配。如果有输入发生不匹配任何规则,ML-Lex创建的lexer将引发异常LexError。请注意,这与打印的C Lex不同标准输出中任何不匹配的输入。
ML-Lex放置正则表达式匹配的字符串的值在里面yytext(yytext)
,字符串变量。
用户可以递归地用调用词法分析函数lex()
.(如果%参数
使用时可以使用相同的参数重新调用词法分析函数继续()。)这样可以方便地忽略空白或无提示注释:
[\\t\n]+=>(lex());
要切换启动状态,用户可以调用YYBEGIN公司
名称为启动状态。
只有在相应的%
命令位于ML-Lex定义部分:
价值 |
% 命令 |
描述 |
拒绝 |
%拒绝
| 拒绝() 导致当前规则为“拒绝。“lexer的行为就像当前规则没有匹配;另一个与此字符串匹配的规则,或与而是使用此字符串的可能最长前缀。 |
yypos公司 |
|
文件开头的当前字符位置。 |
|
yylineto公司 |
%计数 |
当前行号 |
只有在必要时才能使用这些值。正在添加拒绝到lexer会将速度降低20%;添加yyline编号会慢下来另外20%或更多。(更有效地认出
n个和有一个增加line-number变量的操作。)使用前瞻操作员/也会降低整个lexer的速度。角色位置,yypos公司然而,维护成本并不高。
从Unix shell运行sml-lex我的文件.lex输出文件为myfile.lex.sml。扩展名为.法律不是必需,但建议使用。
在交互式系统中[不是首选方法]:使用lexgen.sml语言; 这将创建一个结构LexGen。功能LexGen.LexGen从输入为lexer创建程序规范。它接受一个字符串参数-文件名包含输入规范。输出文件名为通过附加确定``.sml格式将“”添加到输入文件名。
加载输出文件时,它将创建一个结构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,但在命令中指定的那个。
下面是计算器程序的示例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