首页 > 解决方案 > 写我想要的,但必须在某些标记之前或之后

问题描述

所以我有这个 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 的问题,提前抱歉,这是我第一次使用这种语言,我在研究中没有找到任何可以帮助解决这个问题的东西问题。

标签: flex-lexerlex

解决方案


您将需要一个启动条件,但这是一个非常简单的应用程序。每个开始条件适用于不同的词法环境。在您的情况下,您基本上有两个这样的环境:一个对应于不应解析的文本,另一个对应于您要分析的文本部分。

这通常被称为“孤岛解析”,因为您试图在非结构化文本的海洋中解析结构化信息的孤岛。

基于 Lex 的扫描器生成器有一个名为 的默认启动条件<INITIAL>,它在词法分析器第一次启动时处于活动状态。<INITIAL>不必用明确的开始条件编写规则;其他规则可以。在孤岛解析的情况下,这很烦人,因为大多数规则都处于孤岛开始条件中,这意味着条件名称必须预先存在于所有规则中。

但是您几乎可以肯定实际上是在使用 flex,如果是这样,您可以使用有用的 flex 扩展,它允许将规则块分配给开始条件。这就是我写这个答案的方式,如果它适合你,那么你应该更改任何引用“lex”的构建规则,以便它们正确命名你正在使用的扫描仪生成器(因为如果你使用 flex 扩展,你将需要用 flex 处理文件)。

正确编写解析器需要在输入规范中具有很高的精度。您的简短问题中有许多未指明的案例;我首先列出我看到的那些,以及我选择的解决方案(通常是最省力的解决方案)。

  1. 在外部<INITIAL>开始条件中,任何不以单词开头的文本行programmodule非结构化文本。您的问题并未表明您希望如何处理。您可以将其传递给解析器、忽略它、将其复制到yyout或任何数量的其他替代方案。在这里,我忽略它,因为这是最简单的。应该清楚其他替代方案需要更改的内容。

  2. 单词programmodule必须是唯一的东西才能被识别?如果没有,可以遵循什么?例如,这条线是否符合条件:

    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
    

    因此,可能需要根据您的需要进行一些调整。

  3. 我也对岛后文字的精确处理产生了怀疑。你期望只有一个岛吗?或者你可以:

    非结构化文本 非结构化文本程序 ... 结束非结构化文本模块 ... 结束非结构化文本

    以下假设您将要处理两个岛,因为它是最简单的。相反,如果您想忽略后面的所有end文本,则需要添加第三个开始条件,它只是忽略所有文本。(或者,如果您不想对岛后面的文本做任何事情,您可以在读取end令牌后发送重置输入流。)

  4. 一旦遇到or关键字,标记是否真的需要end位于行首?如果您需要,则扫描仪会将错误或无意的缩进转换为。在我看来这不太可能,所以我忽略了限制。我也在假设以非结构化文本开头的行仍然是非结构化文本;也就是说,规则甚至不需要尝试检测它。programmoduleendIDend<INITIAL>

  5. 同样,我不清楚岛内是否program以及是否是合法令牌,或者它们是否应该被视为标识符。module如果它们是合法标记,是否有充分的理由限制它们出现在一行的开头?我认为不是,所以我忽略了限制。

也就是说,这是一个示例实现。我们首先声明开始条件(您可以阅读链接的 flex 文档以详细解释我为什么%x要声明它),它必须进入 flex 输入的第一部分,在%%

%x ISLAND
%%

<INITIAL>状态中,我们只关心以program或开头的行module。如上所述,我们还需要确保目标词后跟空格。这实际上有点棘手,因为负匹配(“不以programmodule”开头的行)很难写成正则表达式(没有负前瞻断言, (f)lex 不提供)。我们没有尝试这样做,而是分别识别行中的第一个单词和行的其余部分,这允许我们使用最长匹配规则。但首先,我们需要识别我们的特殊情况,即使用BEGIN特殊动作切换开始条件。这里我们使用 flex 的“尾随上下文”/

^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 */

第三条规则匹配行首的字母词。[注 1] 第四条规则匹配单词后的其余行和不以单词开头的任何行。我们必须注意不要\n在一行的开头匹配 a;如果不排除\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. */
}

笔记:

  1. 理论上,第三条规则可以匹配任何地方的字母词,因为它没有锚定^. 实际上,除了在行首之外不可能触发此规则,因为第四条规则总是延伸到行尾。但理论上,某些动作可能会BEGIN(INITIAL)在下一个要读取的字符是字母而不是在行首的时刻调用。仔细检查代码会发现这是不可能的,但 flex 无法进行这种分析;从 flex 的角度来看,这是一种可能性,如果发生这种情况,则需要第三条规则。

    我知道这一点,因为我总是%option nodefault在我的 flex 文件中使用,这会导致 flex 警告我是否有可能没有规则适用于输入。而且由于我最初使用锚点编写规则 3,因此 flex 有义务警告我可以匹配默认规则。所以我不得不移除锚点以移除该警告。但是尽管有烦恼,但我认为警告很有用,因为在将来的某个时候,肯定有可能有人会引入一个BEGIN动作,该动作创造了一个条件,在这种条件下,字母词的非锚定匹配是必要的。


推荐阅读