写我想要的,但它必须在某些标记之前或之后
Writing what I want but it has to be before or after certain tokens
所以我有这个 lex 文件:
%{
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "node.h"
#include "y.tab.h"
char *dupstr(const char *s);
void yyerror(char *s);
int octal(char *s);
%}
%%
$$.* ; /* comment */
$(.|\n)*$ ; /* comment */
">=" return GE;
"<=" return LE;
":=" return AT;
"~=" return NEQ;
"if" return IF;
"else" return ELSE;
"then" return THEN;
"elif" return ELIF;
"fi" return FI;
"for" return FOR;
"until" return UNTIL;
"step" return STEP;
"do" return DO;
"done" return DONE;
"repeat" return REP;
"stop" return STOP;
"return" return RET;
^"program" return PROG;
^"module" return MOD;
"start" return ST;
^"end" return END;
"void" return VD;
"const" return CT;
"number" return NB;
"array" return ARR;
"string" return SG;
"function" return FC;
"public" return PB;
"forward" return FW;
0|[1-9][0-9]* { errno = 0; yylval.i = strtol(yytext, 0, 10); if (errno == ERANGE)
yyerror("overflow in decimal constant"); return INTEGER; }
0[0-7]+ { yylval.i = octal(yytext); return INTEGER; }
0x[0-9a-fA-F]+ { yylval.i = strtol(yytext, 0, 16); return INTEGER; }
0b[01]+ { errno = 0; yylval.i = strtol(yytext+2, 0, 2); if (errno == ERANGE)
yyerror("overflow in binary constant"); return INTEGER; }
\'[^\\']\'|\'\[nrt\\']\'|\'\[a-fA-F0-9]\' { yytext[yyleng-1] = 0; yylval.s =
dupstr(yytext+1); return STRING; }
[A-Za-z][A-Za-z0-9_]* { yylval.s = dupstr(yytext+1); return ID; }
\"[^"]*\" { yytext[yyleng-1] = 0; yylval.s = dupstr(yytext+1); return STRING; }
[-+*/%^:=<>~|&?#<\[\]();!,] return *yytext;
[ \t\n\r]+ ; /* ignore whitespace */
. yyerror("Unknown character");
%%
char *getyytext() { return yytext; }
int yywrap(void) {
return 1;
}
int octal(char *s)
{
int i, a = 0, b = 0;
for (i = 0; i < strlen(s); i++) {
if (s[i] < '0' || s[i] > '7') break;
b = b * 8 + s[i] - '0';
if (b < a) {
yyerror("octal overflow");
break;
}
a = b;
}
return a;
}
我想要一个限制,允许我写任何我想写的东西,但前提是我在令牌程序和模块之前或令牌结束之后写它,这可能吗?我在各自的 yacc 文件上尝试了一些选项,但无法做到,而且我认为这是 lex 的问题,提前抱歉,这是我第一次使用这种语言,我在我的研究中没有发现任何可以帮助解决这个问题的东西问题。
您将需要一个 start condition,但这是一个非常简单的应用程序。每个开始条件适用于不同的词法环境。在您的情况下,您基本上有两个这样的环境:一个对应于不应解析的文本,另一个对应于您要分析的文本部分。
这通常称为 "island parsing",因为您正试图在一片非结构化文本的海洋中解析结构化信息的孤岛。
基于 Lex 的扫描器生成器有一个名为 <INITIAL>
的默认启动条件,这是词法分析器第一次启动时激活的条件。 <INITIAL>
中的规则不必写有明确的开始条件;其他规则。这在孤岛解析的情况下非常令人恼火,因为大多数规则都在孤岛开始条件中,这意味着条件名称必须预先添加到所有规则中。
但几乎可以肯定您实际上在使用 flex,如果是这样,您可以使用有用的 flex 扩展,它允许将规则块分配给起始条件。这就是我写这个答案的方式,如果它对你有用,那么你应该更改任何引用 "lex" 的构建规则,以便它们正确命名你正在使用的扫描仪生成器(因为如果你使用 flex 扩展,你将需要使用 flex 处理文件)。
正确编写解析器需要输入规范非常精确。您的简短问题中有许多未指明的情况;我首先列出我看到的那些,以及我选择的分辨率(通常是最省力的分辨率)。
在外部 <INITIAL>
开始条件中,任何不以单词 program
或 module
精确开始的文本行都是非结构化文本。您的问题并未表明您希望如何处理。您可以将它传递给解析器,忽略它,将它复制到 yyout
,或任何数量的其他替代方案。在这里,我忽略它,因为这是最简单的。应该清楚其他替代方案需要更改什么。
单词 program
或 module
是否必须是行中唯一被识别的东西?如果不是,那又能跟随什么呢?例如,此行是否符合条件:
program"FOO"{
(我不知道你的语言的语法是什么;我只是在这里提出假设。)最简单的解决方案是要求单词本身在一行上,但这不太可能要求:我们经常想把评论之类的东西和这些标记放在同一行。另一方面,如果行
programming is complicated because we're not using to thinking precisely
被视为已解析块的开始。所以我猜测,重要的是 program
(或模块)恰好位于行首,紧接着是空格(或行尾,这也是一个空格字符)的行).这将无法识别以下任一情况:
program$$ This is a comment
program;
但它会识别
program $$ This is a comment
program MyProgram
因此可能需要根据您的需要进行一些调整。
我也对岛后文字的精准处理产生了疑惑。你期望只有一个岛吗?或者你可以:
非结构化文本
非结构化文本
程序
...
结尾
非结构化文本
模块
...
结尾
非结构化文本
以下假设您想要同时处理两个岛屿,因为这是最简单的。相反,如果您想忽略 end
之后的 all 文本,则需要添加第三个开始条件,它只忽略所有文本。 (或者,如果您不想对岛后面的文本做任何事情,您可以在读取 end
令牌后发送重置输入流。)
一旦遇到 program
或 module
关键字,end
标记是否真的有必要位于行首?如果您需要,那么错误或无意中缩进的 end
将被您的扫描仪转换为 ID
。这在我看来不太可能,所以我忽略了限制。我还假设非结构化文本中以 end
开头的行仍然是非结构化文本;也就是说,<INITIAL>
规则甚至不需要尝试检测它。
同样,我不清楚program
和module
是否是岛内的合法令牌,或者它们是否应该被视为标识符。如果它们是合法的标记,是否有充分的理由将它们限制在一行的开头?我认为不是,所以我省略了限制。
也就是说,这是一个示例实现。我们首先声明开始条件(您可以阅读链接的 flex 文档以详细解释为什么我使用 %x
来声明它),它必须进入 flex 输入的第一部分,在 [=38 之前=]
%x ISLAND
%%
在 <INITIAL>
状态下,我们只关心以 program
或 module
开头的行。如上所述,我们还需要确保目标词后面跟有空格。这实际上有点棘手,因为否定匹配 ("lines which don't start with program
or module
") 很难写成正则表达式(没有否定先行断言,(f)lex 不提供)。我们没有尝试这样做,而是分别识别行中的第一个单词和行的其余部分,这使我们能够使用最长匹配规则。但首先,我们需要识别我们的特殊情况,即使用 BEGIN
特殊操作切换启动条件。这里我们使用flex的"trailing context"运算符/
来保证关键字后面是空格:
^program/[[:space:]] { BEGIN(ISLAND); return PROG; }
^module/[[:space:]] { BEGIN(ISLAND); return MOD; }
[[:alpha:]]+ ; /* Any other word (at the beginning of a line) */
[^[:alpha:]\n].* ; /* See below */
\n ; /* The newline at the end of the line */
第三条规则匹配行首的字母词。 [注一]
第四个规则匹配单词后的行的其余部分和不以单词开头的任何行。我们必须注意不要匹配行首的 \n
;如果不排除否定字符 class 中的 \n
,该模式将匹配一个空行的 \n
,然后是整个下一行,因此它会跳过 program
在它后面是一个空行的情况下。 (如果不清楚,您可能想试验一下。)
<ISLAND>
开始条件本质上是您已经编写的规则,包含在开始条件块中。出于这个原因,我没有重复所有规则;只有我改变的。请注意,在开始条件块内,flex 取消了规则必须在行首开始的限制。另请注意,无需引用仅由字母和数字组成的模式。只有带有元字符的模式才需要被引用。
<ISLAND>{ /* Open the block */
[[:space:]]+ ; /* Ignore whitespace */
end { BEGIN(INITIAL); return END; }
program { return PROG; }
module { return MOD; }
/* And all the rest of the rules. */
}
备注:
理论上,第三条规则可以匹配任何地方的字母单词,因为它没有锚定 ^
。实际上,除了在一行的开头之外,不可能触发此规则,因为第四条规则总是延伸到一行的末尾。但理论上,某些操作可能会在下一个要读取的字符是字母而不是行首的时刻调用 BEGIN(INITIAL)
。仔细检查代码会发现这是不可能的,但是 flex 不能做那种分析;从 flex 的角度来看,这是一种可能性,如果发生这种情况,则需要第三条规则。
我知道这一点,因为我总是在我的 flex 文件中使用 %option nodefault
,这会导致 flex 在可能没有规则适用于输入时警告我。由于我最初用锚点编写规则 3,flex 不得不警告我它可能匹配默认规则。所以我不得不删除锚点以删除该警告。但是尽管很烦人,我认为这个警告是有用的,因为在未来的某个时候,肯定有可能有人会引入一个 BEGIN
动作,它创造了一个条件,在这个条件下,一个字母单词的非锚定匹配将是必要的。
所以我有这个 lex 文件:
%{
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "node.h"
#include "y.tab.h"
char *dupstr(const char *s);
void yyerror(char *s);
int octal(char *s);
%}
%%
$$.* ; /* comment */
$(.|\n)*$ ; /* comment */
">=" return GE;
"<=" return LE;
":=" return AT;
"~=" return NEQ;
"if" return IF;
"else" return ELSE;
"then" return THEN;
"elif" return ELIF;
"fi" return FI;
"for" return FOR;
"until" return UNTIL;
"step" return STEP;
"do" return DO;
"done" return DONE;
"repeat" return REP;
"stop" return STOP;
"return" return RET;
^"program" return PROG;
^"module" return MOD;
"start" return ST;
^"end" return END;
"void" return VD;
"const" return CT;
"number" return NB;
"array" return ARR;
"string" return SG;
"function" return FC;
"public" return PB;
"forward" return FW;
0|[1-9][0-9]* { errno = 0; yylval.i = strtol(yytext, 0, 10); if (errno == ERANGE)
yyerror("overflow in decimal constant"); return INTEGER; }
0[0-7]+ { yylval.i = octal(yytext); return INTEGER; }
0x[0-9a-fA-F]+ { yylval.i = strtol(yytext, 0, 16); return INTEGER; }
0b[01]+ { errno = 0; yylval.i = strtol(yytext+2, 0, 2); if (errno == ERANGE)
yyerror("overflow in binary constant"); return INTEGER; }
\'[^\\']\'|\'\[nrt\\']\'|\'\[a-fA-F0-9]\' { yytext[yyleng-1] = 0; yylval.s =
dupstr(yytext+1); return STRING; }
[A-Za-z][A-Za-z0-9_]* { yylval.s = dupstr(yytext+1); return ID; }
\"[^"]*\" { yytext[yyleng-1] = 0; yylval.s = dupstr(yytext+1); return STRING; }
[-+*/%^:=<>~|&?#<\[\]();!,] return *yytext;
[ \t\n\r]+ ; /* ignore whitespace */
. yyerror("Unknown character");
%%
char *getyytext() { return yytext; }
int yywrap(void) {
return 1;
}
int octal(char *s)
{
int i, a = 0, b = 0;
for (i = 0; i < strlen(s); i++) {
if (s[i] < '0' || s[i] > '7') break;
b = b * 8 + s[i] - '0';
if (b < a) {
yyerror("octal overflow");
break;
}
a = b;
}
return a;
}
我想要一个限制,允许我写任何我想写的东西,但前提是我在令牌程序和模块之前或令牌结束之后写它,这可能吗?我在各自的 yacc 文件上尝试了一些选项,但无法做到,而且我认为这是 lex 的问题,提前抱歉,这是我第一次使用这种语言,我在我的研究中没有发现任何可以帮助解决这个问题的东西问题。
您将需要一个 start condition,但这是一个非常简单的应用程序。每个开始条件适用于不同的词法环境。在您的情况下,您基本上有两个这样的环境:一个对应于不应解析的文本,另一个对应于您要分析的文本部分。
这通常称为 "island parsing",因为您正试图在一片非结构化文本的海洋中解析结构化信息的孤岛。
基于 Lex 的扫描器生成器有一个名为 <INITIAL>
的默认启动条件,这是词法分析器第一次启动时激活的条件。 <INITIAL>
中的规则不必写有明确的开始条件;其他规则。这在孤岛解析的情况下非常令人恼火,因为大多数规则都在孤岛开始条件中,这意味着条件名称必须预先添加到所有规则中。
但几乎可以肯定您实际上在使用 flex,如果是这样,您可以使用有用的 flex 扩展,它允许将规则块分配给起始条件。这就是我写这个答案的方式,如果它对你有用,那么你应该更改任何引用 "lex" 的构建规则,以便它们正确命名你正在使用的扫描仪生成器(因为如果你使用 flex 扩展,你将需要使用 flex 处理文件)。
正确编写解析器需要输入规范非常精确。您的简短问题中有许多未指明的情况;我首先列出我看到的那些,以及我选择的分辨率(通常是最省力的分辨率)。
在外部
<INITIAL>
开始条件中,任何不以单词program
或module
精确开始的文本行都是非结构化文本。您的问题并未表明您希望如何处理。您可以将它传递给解析器,忽略它,将它复制到yyout
,或任何数量的其他替代方案。在这里,我忽略它,因为这是最简单的。应该清楚其他替代方案需要更改什么。单词
program
或module
是否必须是行中唯一被识别的东西?如果不是,那又能跟随什么呢?例如,此行是否符合条件:program"FOO"{
(我不知道你的语言的语法是什么;我只是在这里提出假设。)最简单的解决方案是要求单词本身在一行上,但这不太可能要求:我们经常想把评论之类的东西和这些标记放在同一行。另一方面,如果行
programming is complicated because we're not using to thinking precisely
被视为已解析块的开始。所以我猜测,重要的是
program
(或模块)恰好位于行首,紧接着是空格(或行尾,这也是一个空格字符)的行).这将无法识别以下任一情况:program$$ This is a comment program;
但它会识别
program $$ This is a comment program MyProgram
因此可能需要根据您的需要进行一些调整。
我也对岛后文字的精准处理产生了疑惑。你期望只有一个岛吗?或者你可以:
非结构化文本 非结构化文本 程序 ... 结尾 非结构化文本 模块 ... 结尾 非结构化文本
以下假设您想要同时处理两个岛屿,因为这是最简单的。相反,如果您想忽略
end
之后的 all 文本,则需要添加第三个开始条件,它只忽略所有文本。 (或者,如果您不想对岛后面的文本做任何事情,您可以在读取end
令牌后发送重置输入流。)一旦遇到
program
或module
关键字,end
标记是否真的有必要位于行首?如果您需要,那么错误或无意中缩进的end
将被您的扫描仪转换为ID
。这在我看来不太可能,所以我忽略了限制。我还假设非结构化文本中以end
开头的行仍然是非结构化文本;也就是说,<INITIAL>
规则甚至不需要尝试检测它。同样,我不清楚
program
和module
是否是岛内的合法令牌,或者它们是否应该被视为标识符。如果它们是合法的标记,是否有充分的理由将它们限制在一行的开头?我认为不是,所以我省略了限制。
也就是说,这是一个示例实现。我们首先声明开始条件(您可以阅读链接的 flex 文档以详细解释为什么我使用 %x
来声明它),它必须进入 flex 输入的第一部分,在 [=38 之前=]
%x ISLAND
%%
在 <INITIAL>
状态下,我们只关心以 program
或 module
开头的行。如上所述,我们还需要确保目标词后面跟有空格。这实际上有点棘手,因为否定匹配 ("lines which don't start with program
or module
") 很难写成正则表达式(没有否定先行断言,(f)lex 不提供)。我们没有尝试这样做,而是分别识别行中的第一个单词和行的其余部分,这使我们能够使用最长匹配规则。但首先,我们需要识别我们的特殊情况,即使用 BEGIN
特殊操作切换启动条件。这里我们使用flex的"trailing context"运算符/
来保证关键字后面是空格:
^program/[[:space:]] { BEGIN(ISLAND); return PROG; }
^module/[[:space:]] { BEGIN(ISLAND); return MOD; }
[[:alpha:]]+ ; /* Any other word (at the beginning of a line) */
[^[:alpha:]\n].* ; /* See below */
\n ; /* The newline at the end of the line */
第三条规则匹配行首的字母词。 [注一]
第四个规则匹配单词后的行的其余部分和不以单词开头的任何行。我们必须注意不要匹配行首的 \n
;如果不排除否定字符 class 中的 \n
,该模式将匹配一个空行的 \n
,然后是整个下一行,因此它会跳过 program
在它后面是一个空行的情况下。 (如果不清楚,您可能想试验一下。)
<ISLAND>
开始条件本质上是您已经编写的规则,包含在开始条件块中。出于这个原因,我没有重复所有规则;只有我改变的。请注意,在开始条件块内,flex 取消了规则必须在行首开始的限制。另请注意,无需引用仅由字母和数字组成的模式。只有带有元字符的模式才需要被引用。
<ISLAND>{ /* Open the block */
[[:space:]]+ ; /* Ignore whitespace */
end { BEGIN(INITIAL); return END; }
program { return PROG; }
module { return MOD; }
/* And all the rest of the rules. */
}
备注:
理论上,第三条规则可以匹配任何地方的字母单词,因为它没有锚定
^
。实际上,除了在一行的开头之外,不可能触发此规则,因为第四条规则总是延伸到一行的末尾。但理论上,某些操作可能会在下一个要读取的字符是字母而不是行首的时刻调用BEGIN(INITIAL)
。仔细检查代码会发现这是不可能的,但是 flex 不能做那种分析;从 flex 的角度来看,这是一种可能性,如果发生这种情况,则需要第三条规则。我知道这一点,因为我总是在我的 flex 文件中使用
%option nodefault
,这会导致 flex 在可能没有规则适用于输入时警告我。由于我最初用锚点编写规则 3,flex 不得不警告我它可能匹配默认规则。所以我不得不删除锚点以删除该警告。但是尽管很烦人,我认为这个警告是有用的,因为在未来的某个时候,肯定有可能有人会引入一个BEGIN
动作,它创造了一个条件,在这个条件下,一个字母单词的非锚定匹配将是必要的。