这是可以使用我们的多个免费在线工作站之一在 OnWorks 免费托管服务提供商中运行的命令挂钩,例如 Ubuntu Online、Fedora Online、Windows 在线模拟器或 MAC OS 在线模拟器
程序:
您的姓名
peg, leg - 解析器生成器
概要
挂 [-HVV -o输出] [文件名 ...]
腿 [-HVV -o输出] [文件名 ...]
商品描述
挂 和 腿 是用于生成递归下降解析器的工具:执行
文本模式匹配。 他们处理解析表达式语法 (PEG) [Ford 2004] 以
生成一个程序来识别该语法的合法句子。 挂 处理 PEG
使用福特描述的原始语法编写; 腿 处理使用编写的 PEG
略有不同的语法和约定,旨在使其具有吸引力
替换构建的解析器 法(1)和 雅克(1). 不像 法 和 雅克, 挂 和 腿
支持无限回溯,提供有序选择作为消除歧义的手段,以及
可以将扫描(词法分析)和解析(句法分析)合并为一个
活性。
挂 读取指定的 文件名s,如果没有,则为标准输入 文件名s 是给定的,对于一个
描述要生成的解析器的语法。 挂 然后生成一个 C 源文件
定义一个函数 yyparse() 这个 C 源文件可以包含在,或者编译然后
与客户端程序相关联。 每次客户端程序调用 yy解析() 解析器
根据解析规则使用输入文本,从第一个规则开始
语法。 yy解析() 如果输入可以根据
语法; 如果无法解析输入,则返回零。
前缀“yy”或“YY”被添加到生成的所有外部可见符号之前
解析器。 这是为了降低客户端程序中命名空间污染的风险。
('yy' 的选择是历史性的;见 法(1)和 雅克(1),例如。)
配置
挂 和 腿 提供以下选项:
-h 打印可用选项的摘要,然后退出。
-o输出
将生成的解析器写入文件 产量 而不是标准输出。
-v 在工作时将详细信息写入标准错误。
-V 将版本信息写入标准错误然后退出。
A 简单 例
下列 挂 输入指定具有单个规则(称为“开始”)的语法,即
当输入包含字符串“用户名”时满意。
开始 <- “用户名”
(引号是 而不去 匹配文本的一部分; 它们用于指示文字
要匹配的字符串。)换句话说, yy解析() 在生成的 C 源代码中会返回
仅当从输入中读取的下八个字符拼写单词“username”时才非零。
如果输入包含其他任何内容, yy解析() 返回零并且没有输入
消耗。 (随后调用 yy解析() 也将返回零,因为解析器是
有效地阻止了查找字符串“用户名”。)为了确保进度,我们可以添加一个
'start' 规则的替代子句,如果“用户名”将匹配任何单个字符
没有找到。
开始 <- “用户名”
/。
yy解析() 现在总是返回非零值(除了在输入的最后)。 去做
一些有用的东西,我们可以向规则添加操作。 这些操作是在一个
找到完全匹配(从第一条规则开始)并根据
'path' 通过语法匹配输入。 (语言学家将这条路径称为
'短语标记'。)
开始 <- "用户名" { printf("%s\n", getlogin()); }
/ < . > { putchar(yytext[0]); }
第一行指示解析器在它看到时打印用户的登录名
输入中的“用户名”。 如果匹配失败,第二行告诉解析器回显
输入标准输出的下一个字符。 我们的解析器现在正在执行有用的
工作:它将输入复制到输出,将所有出现的“用户名”替换为
用户的帐户名。
请注意添加到第二个选项中的尖括号(“<”和“>”)。 这些
对规则的含义没有影响,但有助于划定可供使用的文本
变量中的以下操作 文本.
如果将上述语法放在文件中 用户名.peg, 运行命令
peg -o 用户名.c 用户名.peg
将相应的解析器保存在文件中 用户名.c. 创建一个完整的程序
这个解析器可以包含在 C 程序中,如下所示。
#包括/* printf(), putchar() */
#包括/* 获取登录() */
#include "username.c" /* yyparse() */
int main()
{
while (yyparse()) /* 重复直到 EOF */
;
0返回;
}
PEG 语法
文法由一组命名规则组成。
名称 <- 模式
- 模式 包含以下一个或多个元素。
姓名 该元素代表具有给定规则的整个模式 姓名.
"字符"
双引号中的字符或字符串按字面匹配。 ANSI C
转义序列在 字符.
'字符'
单引号中的字符或字符串按字面匹配,如上所述。
[字符]
用方括号括起来的一组字符匹配来自
集,转义字符识别如上。 如果集合以
uparrow (^) 然后集合被否定(元素匹配任何字符 而不去 ,在
放)。 任何以破折号 (-) 分隔的字符对表示范围
从第一个字符到第二个字符,包括在内。 单个字母字符
或下划线与以下集合匹配。
[a-zA-Z_]
类似地,以下匹配任何单个非数字字符。
[^ 0-9]
. 点匹配任何字符。 请注意,唯一失败的时间是在
文件,其中没有要匹配的字符。
( 模式 )
括号用于分组(修改运算符的优先级)
如下面所描述的)。
{ 行动 }
花括号包围动作。 动作是任意的C源代码
在匹配结束时执行。 动作中的任何大括号都必须正确
嵌套。 在操作之前匹配并以角度分隔的任何输入文本
括号(见下文)在动作中作为内容提供
字符数组 文本. 长度(字符数) 文本 is
在变量中可用 伊伦. (这些变量名称是历史性的;见
法(1)。)
< 左尖括号始终匹配(不消耗输入)并导致解析器
开始积累匹配的文本。 此文本将可用于以下操作
变量 文本.
> 右尖括号始终匹配(不消耗输入)并导致解析器
停止累积文本 文本.
以上 elements 可以通过以下后缀变为可选和/或可重复的:
element ?
该元素是可选的。 如果出现在输入中,它会被消耗并且匹配
成功。 如果输入中不存在,则不消耗任何文本并且匹配成功
反正。
element +
该元素是可重复的。 如果出现在输入中,则出现一次或多次
element 被消耗并且匹配成功。 如果没有出现 element 旨在
出现在输入中,匹配失败。
element *
该元素是可选的且可重复的。 如果出现在输入中,一个或多个
的发生 element 被消耗并且匹配成功。 如果没有出现
element 出现在输入中,无论如何匹配都会成功。
上述元素和后缀可以转换为谓词(匹配任意
输入文本然后成功或失败 也完全不需要 消耗该输入)与
以下前缀:
& element
谓词仅在以下情况下成功 element 可以匹配。 输入文本扫描时
匹配 element 不会从输入中消耗并保持可用于
后续匹配。
! element
谓词仅在以下情况下成功 element 无法匹配。 输入文本扫描时
匹配 element 不会从输入中消耗并保持可用于
后续匹配。 一个流行的成语是
!.
在输入的最后一个字符之后匹配文件结尾
被消耗了。
提供了一种特殊形式的“&”谓词:
&{ 表达 }
在这个谓词中,简单的 C 表达 (而不去 语句)被立即评估
当解析器到达谓词时。 如果 表达 产生非零(真)
“匹配”成功,解析器继续处理模式中的下一个元素。
如果 表达 产生零(假)“匹配”失败并且解析器备份到
寻找输入的替代解析。
几个元素(有或没有前缀和后缀)可以组合成一个 序列
通过一个接一个地写它们。 仅当每个个体都匹配时,整个序列才匹配
其中的元素从左到右匹配。
可以通过交替运算符“/”将序列分成不相交的备选方案。
序列 1 / 序列 2 / ... / 序列-N
依次尝试每个序列,直到其中一个匹配,此时匹配
因为整体模式成功。 如果没有任何序列匹配,则匹配
整体模式失败。
最后,井号 (#) 引入了一直持续到结尾的注释(已丢弃)
行的。
总结上述内容,解析器尝试将输入文本与模式匹配
包含文字、名称(代表其他规则)和各种运算符(写成
前缀、后缀、排序和中缀交替运算符的并置),
修改模式中元素的匹配方式。 比赛是从左到
对,在遇到命名的子规则时“降序”。 如果匹配过程
失败,解析器“回溯”(在过程中适当地“回绕”输入)到
通过语法找到最近的替代“路径”。 换句话说,解析器
对第一个成功匹配的路径执行深度优先、从左到右的搜索
通过规则。 如果找到,则执行成功路径上的操作(在
他们遇到的顺序)。
请注意,谓词被评估 立即 在寻找成功匹配的过程中,
因为它们有助于搜索的成功或失败。 然而,行动是
仅在找到成功匹配后才进行评估。
PEG 语法 用于 PEG 语法
语法为 挂 语法如下所示。 这将说明和形式化
以上说明。
语法 <- 间距定义+ EndOfFile
定义 <- 标识符 LEFTARROW 表达式
表达式 <- 序列(斜线序列)*
序列 <- 前缀*
前缀 <- AND 操作
/ ( 和 | 不是 )? 后缀
后缀 <- 主要 ( QUERY / STAR / PLUS )?
主要 <- 标识符 !LEFTARROW
/ OPEN 表达式 CLOSE
/ 文字
/课
/点
/ 行动
/ 开始
/ 结尾
标识符 <- < IdentStart IdentCont* > 间距
IdentStart <- [a-zA-Z_]
IdentCont <- IdentStart / [0-9]
文字 <- ['] < ( !['] Char )* > ['] 间距
/ ["] < ( !["] Char )* > ["] 间距
类 <- '[' < ( !']' 范围 )* > ']' 间距
范围 <- 字符 '-' 字符 / 字符
字符 <- '\\' [abefnrtv'"\[\]\\]
/ '\\' [0-3][0-7][0-7]
/ '\\' [0-7][0-7]?
/ '\\' '-'
/ !'\\' 。
左箭头 <- '<-' 间距
斜线 <- '/' 间距
AND <- '&' 间距
不是 <- '!' 间距
查询 <- '?' 间距
星号 <- '*' 间距
加号 <- '+' 间距
OPEN <- '(' 间距
CLOSE <- ')' 间距
点 <- '.' 间距
间距 <-(空格/注释)*
注释 <- '#' ( !EndOfLine . )* EndOfLine
空格 <- ' ' / '\t' / EndOfLine
EndOfLine <- '\r\n' / '\n' / '\r'
EndOfFile <- !.
操作 <- '{' < [^}]* > '}' 间距
BEGIN <- '<' 间距
END <- '>' 间距
专家组 语法
腿 是...的变体 挂 这增加了一些功能 法(1)和 雅克(1). 它不同于
挂 通过以下方式。
%{ 文本... %}
声明部分可以出现在需要规则定义的任何地方。 这
文本 分隔符 '%{' 和 '%}' 之间被逐字复制到生成的 C
解析器代码 before 实现解析器本身的代码。
姓名 = 模式
“赋值”运算符替换了左箭头运算符“<-”。
规则名称
连字符可以显示为规则名称中的字母。 每个连字符都转换为
生成的 C 源代码中的下划线。 单个连字符“-”是
法律规则名称。
- = [ \t\n\r]*
数字 = [0-9]+ -
名称 = [a-zA-Z_][a-zA_Z_0-9]* -
l-paren = '(' -
r-paren = ')' -
这个例子显示了在阅读语法时被忽略的空格是多么明显
并且当自由地放置在与
一个词汇元素。
序列-1 | 序列-2
交替运算符是竖线“|” 而不是正斜杠'/'。 这
挂 排除
名称 <- 序列-1
/ 序列-2
/ 序列-3
因此写成
名称 = 序列 1
| 序列 2
| 序列 3
;
in 腿 (最后一个分号是可选的,如下所述)。
EXP ~ { 行动 }
后缀运算符 ~{ 行动 } 可以放在任何表达式之后并且会表现
就像一个正常的动作(任意的 C 代码),除了它只在 EXP
失败。 除了交替和
排序,旨在使错误处理和恢复代码更容易
写。 注意 文本 和 伊伦 在这些操作中不可用,但是
指针变量 yy 可用于授予代码访问任何用户定义的权限
解析器状态的成员(请参阅下面的“自定义解析器”)。 还要注意的是
EXP 始终是单个表达式; 为任何失败调用错误操作
序列,必须使用括号将序列分组为单个
表达。
rule = e1 e2 e3 ~{ error("e[12] ok; e3 has failed"); }
| ...
rule = (e1 e2 e3) ~{ error("e[123] 之一失败"); }
| ...
模式 ;
分号标点符号可以选择性地终止 模式.
%% 文本...
双百分比 '%%' 终止规则(和声明)部分
语法。 全部 文本 后面的 '%%' 被逐字复制到生成的 C 解析器代码
after 解析器实现代码。
$$ = 折扣值
子规则可以返回语义 折扣值 通过将一个动作分配给
伪变量'$$'。 所有语义值必须具有相同的类型(默认为
'int')。 可以通过在声明部分定义 YYSTYPE 来更改此类型。
识别码:姓名
从子规则返回的语义值(通过分配给 '$$') 姓名 is
与 识别码 并可在后续操作中参考。
下面的桌面计算器示例说明了“$$”和“:”的使用。
专家组 例: A 台 计算器
中的扩展 腿 上面描述的允许有用的解析器和评估器(包括
声明、语法规则和支持 C 函数(例如“main”)要保留在
单个源文件。 为了说明这一点,我们展示了一个简单的桌面计算器,支持
四种常见的算术运算符和命名变量。 中间结果
算术计算将通过将它们返回为隐式堆栈来累积
来自子规则的语义值。
%{
#包括/* 打印 () */
#包括/* atoi() */
整数变量[26];
%}
stmt = - e:Expr EOL { printf("%d\n", e); }
| ( !EOL . )* EOL { printf("error\n"); }
Expr = i:ID 分配 s:Sum { $$ = vars[i] = s; }
| s:总和 { $$ = s; }
总和 = l:乘积
( 加上 r:Product { l += r; }
| 减去 r:产品 { l -= r; }
)* { $$ = l; }
产品 = l:值
( 时间 r:Value { l *= r; }
| 除法 r: 值 { l /= r; }
)* { $$ = l; }
值 = i:NUMBER { $$ = atoi(yytext); }
| i:ID !ASSIGN { $$ = vars[i]; }
| 打开 i:Expr 关闭 { $$ = i; }
NUMBER = < [0-9]+ > - { $$ = atoi(yytext); }
ID = < [az] > - { $$ = yytext[0] - 'a'; }
赋值 = '=' -
加号 = '+' -
减号 = '-' -
时间 = '*' -
除法 = '/' -
打开 = '(' -
关闭 = ')' -
- = [ \t]*
EOL = '\n' | '\r\n' | '\r' | ';'
%%
int main()
{
而 (yyparse())
;
0返回;
}
专家组 语法 用于 专家组 语法
语法为 腿 语法如下所示。 这将说明和形式化
以上说明。
语法 = -
(声明|定义)+
预告片? 文件尾
声明 = '%{' < ( !'%}' . )* > RPERCENT
拖车 = '%%' < .* >
定义 = 标识符 EQUAL 表达式 SEMICOLON?
表达式 = 序列(BAR 序列)*
序列=错误+
错误 = 前缀(波浪号操作)?
前缀 = AND 动作
| (和 | 不是)? 后缀
后缀 = 主要 ( QUERY | STAR | PLUS )?
主要 = 标识符 COLON 标识符 !EQUAL
| 标识符 !EQUAL
| OPEN 表达式 CLOSE
| 文字
| 班级
| 点
| 行动
| 开始
| 结尾
标识符 = < [-a-zA-Z_][-a-zA-Z_0-9]* > -
文字 = ['] < ( !['] char )* > ['] -
| ["] < ( !["] 字符 )* > ["] -
class = '[' < ( !']' 范围 )* > ']' -
范围 = 字符 '-' 字符 | 字符
char = '\\' [abefnrtv'"\[\]\\]
| '\\' [0-3][0-7][0-7]
| '\\' [0-7][0-7]?
| !'\\' 。
动作 = '{' < 大括号 * > '}' -
大括号 = '{' 大括号 * '}'
| !'}' .
等于 = '=' -
冒号 = ':' -
分号 = ';' ——
酒吧 = '|' ——
AND = '&' -
不是 = '!' ——
查询 = '?' ——
星 = '*' -
加号 = '+' -
打开 = '(' -
关闭 = ')' -
点 = '.' ——
开始 = '<' -
END = '>' -
波浪号 = '~' -
RPERCENT = '%}' -
- = ( 空格 | 评论 )*
空格 = ' ' | '\t' | 行结束
注释 = '#' ( !end-of-line . )* 行尾
行尾 = '\r\n' | '\n' | '\r'
文件结尾 = !.
定制 “ 解析器
可以在声明部分重新定义以下符号以修改生成的
解析器代码。
YYS型
语义值类型。 伪变量“$$”和标识符“绑定”到
带有冒号运算符 ':' 的规则结果都应视为已声明
要有这种类型。 默认值为“int”。
YY解析
解析器的主入口点的名称。 默认值为“yyparse”。
yyparsfrom
解析器的替代入口点的名称。 这个函数需要一个
参数:与从中搜索匹配项的规则相对应的函数
应该开始。 默认值为“yyparsefrom”。 请注意, yyparse() 定义为
int yyparse() { 返回 yyparsefrom(yy_foo); }
其中“foo”是语法中第一条规则的名称。
YY_输入(BUF, 导致, 最大尺寸)
该宏由解析器调用以获取更多输入文本。 BUF 指向
最多可容纳的内存区域 最大尺寸 人物。 宏应该复制
输入文本到 BUF 然后分配整数变量 导致 表示
复制的字符数。 如果没有更多的输入可用,宏应该
将 0 分配给 导致. 默认情况下,YY_INPUT 宏定义如下。
#define YY_INPUT(buf, 结果, max_size) \
{\
int yyc=getchar(); \
结果=(EOF == yyc)? 0 : (*(buf)= yyc, 1); \
}
请注意,如果定义了 YY_CTX_LOCAL(见下文),则附加第一个参数,
包含解析器上下文,传递给 YY_INPUT。
YY_调试
如果定义了这个符号,那么额外的代码将包含在解析器中
在解析器时将大量神秘信息打印到标准错误
在跑。
YY_开始
调用此宏来标记将可用的输入文本的开始
在作为“yytext”的行动中。 这对应于语法中出现的“<”。
这些被转换成预期成功的谓词。 默认的
定义
#define YY_BEGIN (yybegin=yypos, 1)
因此保存当前输入位置并返回 1 ('true') 作为结果
谓词。
YY_END 这个宏对应于语法中的“>”。 同样,它是一个谓词,所以
默认定义在“成功”之前保存输入位置。
#define YY_END (yyend=yypos, 1)
YY_解析(T)
这个宏将解析器入口点(yyparse 和 yyparsefrom)声明为类型
T. 默认定义
#define YY_PARSE(T) T
使 yyparse() 和 yyparsefrom() 具有全局可见性。 如果他们不应该
在其他源文件中外部可见,可以重新定义这个宏来声明
他们“静态”。
#define YY_PARSE(T) 静态 T
YY_CTX_LOCAL
如果在编译生成的解析器期间定义了这个符号,那么全局
解析器状态将保存在可以声明的“yycontext”类型的结构中
作为局部变量。 这允许解析器的多个实例共存并
是线程安全的。 解析函数 yy解析() 将被声明为期待第一个
'yycontext *' 类型的参数,一个包含全局变量的结构实例
解析器的状态。 这个实例必须被分配并初始化为零
客户端。 一个简单但完整的例子如下。
#包括
#定义YY_CTX_LOCAL
#include "the-generated-parser.peg.c"
int main()
{
yy上下文ctx;
memset(&ctx, 0, sizeof(yycontext));
而 (yyparse(&ctx));
0返回;
}
请注意,如果此符号未定义,则编译后的解析器将静态
分配其全局状态,并且既不是可重入的也不是线程安全的。 还要注意
解析器 yycontext 结构在第一次自动初始化
yy解析() 叫做; 这种结构 必须 因此被正确初始化为零
在第一次调用之前 yy解析()。
YY_CTX_MEMBERS
如果定义了 YY_CTX_LOCAL(见上文),则可以定义宏 YY_CTX_MEMBERS
扩展到客户想要的任何其他成员字段声明
包含在 'yycontext' 结构类型的声明中。 这些额外的
否则生成的解析器会忽略成员。 'yycontext' 的实例
与当前活动的解析器关联的在操作中可用
指针变量 yy.
YY_BUFFER_SIZE
文本缓冲区的初始大小,以字节为单位。 默认为 1024 和缓冲区
在解析过程中,只要需要满足需求,大小就会加倍。 一个应用程序
通常解析更长的字符串可以增加这个以避免不必要的
缓冲区重新分配。
YY_STACK_SIZE
变量和动作堆栈的初始大小。 默认为 128,即
在解析过程中需要时加倍以满足需求。 具有的应用程序
具有许多局部变量的深层调用堆栈,或者在一个
单个成功匹配,可以增加此项以避免不必要的缓冲区
重新分配。
YY_MALLOC(YY, 尺寸)
所有解析器相关存储的内存分配器。 参数是
当前 yycontext 结构和要分配的字节数。 默认的
定义是: malloc(尺寸)
YY_REALLOC(YY, PTR, 尺寸)
用于动态增长存储的内存重新分配器(例如文本缓冲区和
变量栈)。 参数是当前的yycontext结构,
先前分配的存储,以及该存储应分配的字节数
长大。 默认定义为:realloc(PTR, 尺寸)
YY_免费(YY, PTR)
内存释放器。 参数是当前的 yycontext 结构和
要释放的存储。 默认定义为:free(PTR)
YY发布
释放 yycontext 结构持有的所有资源的函数的名称。
默认值为“yyrelease”。
可以在操作中引用以下变量。
坦克 *yybuf
此变量指向解析器的输入缓冲区,用于存储具有
尚未匹配。
INT yypos
这是要匹配和消耗的下一个字符的偏移量(以 yybuf 为单位)。
坦克 *yy文本
由“<”和“>”分隔的最近匹配的文本存储在此变量中。
INT 伊伦
此变量表示 'yytext' 中的字符数。
yy上下文 *年
此变量指向与
当前活动的解析器。
希望释放与解析器关联的所有资源的程序可以使用
以下功能。
yyrelease(yycontext*yy)
返回与关联的所有解析器分配的存储 yy 到系统。 存储
将在下一次调用时重新分配 yy解析()。
请注意,yycontext 结构本身的存储空间永远不会被分配或回收
含蓄地。 应用程序必须在自动存储中分配这些结构,或使用
卡洛克()和 免费() 来明确地管理它们。 以下部分中的示例
演示了一种资源管理方法。
专家组 例: 扩展 “ 解析器 CONTEXT
- yy 传递给动作的变量包含解析器的状态以及任何额外的
YY_CTX_MEMBERS 定义的字段。 这些字段可用于存储特定于应用程序的
特定调用的全局信息 yy解析(). 一个琐碎但完整的 腿
示例如下,其中 yycontext 结构被扩展为 数 的数量
到目前为止在输入中看到的换行符(否则语法会消耗并忽略
整个输入)。 的来电者 yy解析() 用途 数 打印行数
读取的输入。
%{
#定义YY_CTX_LOCAL 1
#定义YY_CTX_MEMBERS \
整数计数
%}
Char = ('\n' | '\r\n' | '\r') { yy->count++ }
| 。
%%
#包括
#包括
int main()
{
/* 在自动存储中创建本地解析器上下文 */
yy上下文 yy;
/* 上下文*必须*在第一次使用前初始化为零*/
memset(&yy, 0, sizeof(yy));
而 (yyparse(&yy))
;
printf("%d 换行\n", yy.count);
/* 释放与上下文关联的所有资源 */
yyrelease(&yy);
0返回;
}
诊断
挂 和 腿 在将语法转换为解析器时警告以下情况。
句法 错误
输入语法在某些方面格式不正确。 错误消息将包括
即将匹配的文本(通常从实际位置备份大量
错误)和最近考虑的字符的行号(即
通常是问题的真实位置)。
排除 '富' 用过的 但是 而不去 定义
语法引用了一个名为“foo”的规则,但没有给出它的定义。
尝试使用生成的解析器可能会导致链接器出错
由于与缺失规则相关的未定义符号。
排除 '富' 定义 但是 而不去 用过的
语法定义了一个名为“foo”的规则,然后忽略了它。 关联的代码
规则包含在生成的解析器中,这将在所有其他方面
健康。
可能 无穷 左 递归 in 排除 '富'
从规则“foo”引出的语法中至少存在一条路径
回到(递归调用)相同的规则而不消耗任何输入。
左递归,尤其是在标准文档中发现的,通常是“直接的”并且
意味着琐碎的重复。
#(6.7.6)
直接抽象声明符 =
LPAREN 抽象声明符 RPAREN
| 直接抽象声明符? LBRACKET 赋值表达式? 支架
| 直接抽象声明符? LBRACKET STAR RBRACKET
| 直接抽象声明符? LPAREN 参数类型列表? 帕伦
通过转换以下模式的部分,可以轻松消除递归
递归成一个可重复的后缀。
#(6.7.6)
直接抽象声明符 =
直接抽象声明头?
直接抽象声明符尾*
直接抽象声明符头 =
LPAREN 抽象声明符 RPAREN
直接抽象声明符尾 =
LBRACKET 赋值表达式? 支架
| LBRACKET 星形 RBRACKET
| LPAREN 参数类型列表? 帕伦
洞穴
接受空输入的解析器将 时刻 成功。 考虑下面的例子,
第一次尝试编写基于 PEG 的解析器并不是典型的:
程序 = 表达式*
表达式 = "随便"
%%
int main(){
而 (yyparse())
puts("成功!");
0返回;
}
无论标准输入上提供什么(如果有)输入,该程序都会永远循环。 许多
修复是可能的,最简单的方法是坚持解析器总是消耗一些
非空输入。 将第一行更改为
程序=表达式+
实现这一点。 如果预计解析器会消耗整个输入,那么明确地
还强烈建议要求文件结尾:
程序 = 表达式 + !。
这是有效的,因为解析器只会无法匹配(“!”谓词)任何字符
("." 表达式) 当它尝试读取超出输入末尾的内容时。
使用 onworks.net 服务在线使用挂钩