查看源码 leex (parsetools v2.6)
Erlang 的词法分析器生成器
一个基于正则表达式的 Erlang 词法分析器生成器,类似于 lex
或 flex
。
注意
当引入
leex
模块时,它被认为是实验性的。
默认 Leex 选项
可以使用(主机操作系统)环境变量 ERL_COMPILER_OPTIONS
来提供默认的 Leex 选项。它的值必须是有效的 Erlang 项。如果该值是一个列表,则按原样使用。如果它不是列表,则将其放入一个列表中。
该列表将附加到传递给 file/2
的任何选项中。
可以使用 compile:env_compiler_options/0
来检索该列表。
输入文件格式
扫描器文件中允许使用以 %
开头的 Erlang 风格的注释。定义文件具有以下格式
<Header>
Definitions.
<Macro Definitions>
Rules.
<Token Rules>
Erlang code.
<Erlang code>
Definitions.
、Rules.
和 Erlang code
标题是强制性的,并且必须从源代码行的开头开始。<Header>
、<Macro Definitions>
和 <Erlang code>
部分允许为空,但必须至少有一条规则。
宏定义具有以下格式
NAME = VALUE
并且 =
周围必须有空格。通过写入 {NAME}
,可以在规则的正则表达式中使用宏。
注意
当在表达式中展开宏时,宏调用将替换为宏值,而不会进行任何形式的引用或括在括号中。
规则具有以下格式
<Regexp> : <Erlang code>.
<Regexp>
必须出现在行的开头,并且不包含任何空格;使用 \t
和 \s
在正则表达式中包含 TAB 和 SPACE 字符。如果 <Regexp>
匹配,则会评估相应的 <Erlang code>
来生成标记。在 Erlang 代码中,可以使用以下预定义的变量
TokenChars
- 匹配的标记中的字符列表。TokenLen
- 匹配的标记中的字符数。TokenLine
- 标记出现的行号。TokenCol
- 标记出现的列号(包含在标记中的第一个字符的列)。TokenLoc
- 标记位置。扩展为{TokenLine,TokenCol}
(即使error_location
设置为line
)。
代码必须返回
{token,Token}
- 将Token
返回给调用者。{end_token,Token}
- 返回Token
,并且是标记调用中的最后一个标记。skip_token
- 完全跳过此标记。{error,ErrString}
- 标记中的错误,ErrString
是描述该错误的字符串。
也可以使用以下返回值将字符推回输入字符中
{token,Token,PushBackList}
{end_token,Token,PushBackList}
{skip_token,PushBackList}
这些与正常返回具有相同的含义,但是 PushBackList
中的字符将添加到输入字符的前面,并扫描下一个标记。请注意,推回换行符将意味着行号不再正确。
注意
推回字符会给你带来意想不到的可能性,导致扫描器循环!
以下示例将匹配一个简单的 Erlang 整数或浮点数,并返回一个可以发送到 Erlang 解析器的标记
D = [0-9]
{D}+ :
{token,{integer,TokenLine,list_to_integer(TokenChars)}}.
{D}+\.{D}+((E|e)(\+|\-)?{D}+)? :
{token,{float,TokenLine,list_to_float(TokenChars)}}.
Erlang code.
部分中的 Erlang 代码直接写入到输出文件中,模块声明和预定义的导出声明之后,使其可以添加额外的导出,定义导入以及在整个文件中可见的其他属性。
正则表达式
此处允许的正则表达式是 egrep
和 AWK 编程语言(在 A. V. Aho、B. W. Kernighan 和 P. J. Weinberger 的书 *The AWK Programming Language* 中定义)中的子集。它们由以下字符组成
c
- 匹配非元字符 c。\c
- 匹配转义序列或文字字符 c。.
- 匹配任何字符。^
- 匹配字符串的开头。$
- 匹配字符串的结尾。[abc...]
- 字符类,匹配任何字符abc...
。字符范围由一对以-
分隔的字符指定。[^abc...]
- 否定字符类,匹配除abc...
之外的任何字符。r1 | r2
- 选择。它匹配r1
或r2
。r1r2
- 连接。它匹配r1
,然后匹配r2
。r+
- 匹配一个或多个r
。r*
- 匹配零个或多个r
。r?
- 匹配零个或一个r
。(r)
- 分组。它匹配r
。
允许的转义序列与 Erlang 字符串的转义序列相同
\b
- 退格。\f
- 换页。\n
- 换行符(行馈送)。\r
- 回车符。\t
- 制表符。\e
- 转义符。\v
- 垂直制表符。\s
- 空格。\d
- 删除。\ddd
- 八进制值ddd
。\xhh
- 十六进制值hh
。\x{h...}
- 十六进制值h...
。\c
- 任何其他字符,例如,\\
表示反斜杠,\"
表示"
。
以下示例定义了一些 Erlang 数据类型的简化版本
Atoms [a-z][0-9a-zA-Z_]*
Variables [A-Z_][0-9a-zA-Z_]*
Floats (\+|-)?[0-9]+\.[0-9]+((E|e)(\+|-)?[0-9]+)?
注意
在当前版本的
leex
中,不支持使用^
和$
锚定正则表达式,并且会生成解析错误。
摘要
类型
从所有 I/O 模块返回的标准 error_info/0
结构。ErrorDescriptor
可由 format_error/1
格式化。
类型
-type error_info() :: {erl_anno:line() | none, module(), ErrorDescriptor :: term()}.
从所有 I/O 模块返回的标准 error_info/0
结构。ErrorDescriptor
可由 format_error/1
格式化。
-type errors() :: [{file:filename(), [error_info()]}].
-type ok_ret() :: {ok, Scannerfile :: file:filename()} | {ok, Scannerfile :: file:filename(), warnings()}.
-type warnings() :: [{file:filename(), [error_info()]}].
生成的扫描器导出
-spec string(String) -> StringRet when String :: string(), StringRet :: {ok, Tokens, EndLoc} | ErrorInfo, Tokens :: [Token], Token :: term(), ErrorInfo :: {error, error_info(), erl_anno:location()}, EndLoc :: erl_anno:location().
等效于 string(String, 1)
。
-spec string(String, StartLoc) -> StringRet when String :: string(), StringRet :: {ok, Tokens, EndLoc} | ErrorInfo, Tokens :: [Token], Token :: term(), ErrorInfo :: {error, error_info(), erl_anno:location()}, StartLoc :: erl_anno:location(), EndLoc :: erl_anno:location().
扫描 String
并返回其中的所有标记或 error
元组。
StartLoc
和 EndLoc
是 erl_anno:line()
或 erl_anno:location()
,具体取决于 error_location
选项。
注意
如果没有消耗
String
中的所有字符,则会出错。
-spec token(Cont, Chars) -> {more, Cont1} | {done, TokenRet, RestChars} when Cont :: [] | Cont1, Cont1 :: tuple(), Chars :: string() | eof, RestChars :: string() | eof, TokenRet :: {ok, Token, EndLoc} | {eof, EndLoc} | ErrorInfo, ErrorInfo :: {error, error_info(), erl_anno:location()}, Token :: term(), EndLoc :: erl_anno:location().
-spec token(Cont, Chars, StartLoc) -> {more, Cont1} | {done, TokenRet, RestChars} when Cont :: [] | Cont1, Cont1 :: tuple(), Chars :: string() | eof, RestChars :: string() | eof, TokenRet :: {ok, Token, EndLoc} | {eof, EndLoc} | ErrorInfo, ErrorInfo :: {error, error_info(), erl_anno:location()}, Token :: term(), StartLoc :: erl_anno:location(), EndLoc :: erl_anno:location().
这是一个可重入调用,用于尝试从 Chars
扫描单个标记。
如果 Chars
中有足够的字符来扫描一个 token 或检测到一个错误,则将返回 {done,...}
。否则,将返回 {cont,Cont}
,其中 Cont
用于下次调用 token()
时,以便使用更多字符来尝试扫描 token。此过程将持续到扫描到 token 为止。Cont
的初始值为 []
。
它不是设计为由应用程序直接调用的,而是通过 I/O 系统使用的,通常可以在应用程序中通过以下方式调用:
io:request(InFile, {get_until,unicode,Prompt,Module,token,[Loc]})
-> TokenRet
-spec tokens(Cont, Chars) -> {more, Cont1} | {done, TokensRet, RestChars} when Cont :: [] | Cont1, Cont1 :: tuple(), Chars :: string() | eof, RestChars :: string() | eof, TokensRet :: {ok, Tokens, EndLoc} | {eof, EndLoc} | ErrorInfo, Tokens :: [Token], Token :: term(), ErrorInfo :: {error, error_info(), erl_anno:location()}, EndLoc :: erl_anno:location().
-spec tokens(Cont, Chars, StartLoc) -> {more, Cont1} | {done, TokensRet, RestChars} when Cont :: [] | Cont1, Cont1 :: tuple(), Chars :: string() | eof, RestChars :: string() | eof, TokensRet :: {ok, Tokens, EndLoc} | {eof, EndLoc} | ErrorInfo, Tokens :: [Token], Token :: term(), ErrorInfo :: {error, error_info(), erl_anno:location()}, StartLoc :: erl_anno:location(), EndLoc :: erl_anno:location().
这是一个可重入调用,用于尝试从 Chars
扫描标记。
如果 Chars
中有足够的字符来扫描 token 或检测到错误,则将返回 {done,...}
。否则,将返回 {cont,Cont}
,其中 Cont
用于下次调用 tokens()
时,以便使用更多字符来尝试扫描 token。此过程将持续到所有 token 都被扫描完毕为止。Cont
的初始值为 []
。
此函数与 token
的不同之处在于,它将继续扫描 token,直到扫描到 {end_token,Token}
为止(请参阅下一节)。然后,它将返回所有 token。这通常用于扫描像 Erlang 这样的语法,其中存在显式的结束 token,'.'
。如果未找到结束 token,则将扫描并返回整个文件。如果发生错误,则将跳过所有 token,直到下一个结束 token(包括下一个结束 token)。
它不是设计为由应用程序直接调用的,而是通过 I/O 系统使用的,通常可以在应用程序中通过以下方式调用:
io:request(InFile, {get_until,unicode,Prompt,Module,tokens,[Loc]})
-> TokensRet
函数
-spec file(FileName) -> leex_ret() when FileName :: file:filename().
等效于 file(File, [])
。
-spec file(FileName, Options) -> leex_ret() when FileName :: file:filename(), Options :: Option | [Option], Option :: {dfa_graph, boolean()} | {includefile, Includefile :: file:filename()} | {report_errors, boolean()} | {report_warnings, boolean()} | {report, boolean()} | {return_errors, boolean()} | {return_warnings, boolean()} | {return, boolean()} | {scannerfile, Scannerfile :: file:filename()} | {verbose, boolean()} | {warnings_as_errors, boolean()} | {deterministic, boolean()} | {error_location, line | column} | {tab_size, pos_integer()} | dfa_graph | report_errors | report_warnings | report | return_errors | return_warnings | return | verbose | warnings_as_errors.
从输入文件中的定义生成词法分析器。
输入文件具有扩展名 .xrl
。如果未给出,则将其添加到文件名中。生成的模块是 Xrl 文件名,不带 .xrl
扩展名。
当前的选项是:
dfa_graph
- 生成一个.dot
文件,其中包含 DFA 的描述,其格式可以使用 Graphviz (www.graphviz.com
) 查看。{includefile,Includefile}
- 使用特定的或自定义的序言文件,而不是默认的lib/parsetools/include/leexinc.hrl
,否则会包含此文件。{report_errors, boolean()}
- 使错误在发生时打印。默认值为true
。{report_warnings, boolean()}
- 使警告在发生时打印。默认值为true
。{report, boolean()}
- 这是report_errors
和report_warnings
的简短形式。{return_errors, boolean()}
- 如果设置此标志,则当存在错误时,将返回{error, Errors, Warnings}
。默认值为false
。{return_warnings, boolean()}
- 如果设置此标志,则在成功时返回的元组中添加一个包含Warnings
的额外字段。默认值为false
。{return, boolean()}
- 这是return_errors
和return_warnings
的简短形式。{scannerfile, Scannerfile}
-Scannerfile
是将包含生成的 Erlang 扫描器代码的文件的名称。默认值 (""
) 是将扩展名.erl
添加到去掉.xrl
扩展名的FileName
中。{verbose, boolean()}
- 输出解析输入文件和生成内部表的信息。{warnings_as_errors, boolean()}
- 使警告被视为错误。{deterministic, boolean()}
- 使生成的-file()
属性仅包含文件路径的基本名称。{error_location, line | column}
- 如果设置为column
,则错误位置将是{Line,Column}
元组,而不仅仅是Line
。此外,string/2
、token/3
和tokens/3
函数中的StartLoc
和EndLoc
将是{Line,Column}
元组,而不仅仅是Line
。默认值为line
。请注意,即使error_location
设置为line
,您也可以独立地使用TokenLoc
来获取 token 位置。Unicode 字符的列数与它们用于表示的字节数相同。
{tab_size, pos_integer()}
- 设置\t
字符的宽度(仅当error_location
设置为column
时才相关)。默认值为8
。
任何布尔选项都可以通过声明选项的名称设置为 true
。例如,verbose
等效于 {verbose, true}
。
Leex 将扩展名 .hrl
添加到 Includefile
名称,并将扩展名 .erl
添加到 Scannerfile
名称,除非扩展名已存在。
-spec format_error(ErrorDescriptor) -> io_lib:chars() when ErrorDescriptor :: term().
返回由 leex:file/1,2
返回的错误原因 ErrorDescriptor
的英文描述性字符串,当正则表达式中存在错误时。