在Flex / Bison中实现字符串插值

卡梅利恩博士

我目前正在为我设计的语言编写翻译。

词法分析器/解析器(GLR)是用Flex / Bison编写的,而主要的解释器是用D编写的-到目前为止,一切都可以正常工作。

我还想添加字符串插值,即识别包含特定模式(例如"[some expression]")的字符串文字并转换包含的表达式。我认为这应该在解析器级别上从相应的语法动作中完成。

我的想法是将插入的字符串转换/处理为使用简单串联后的样子(现在可以正常使用)。

例如

print "this is the [result]. yay!"

print "this is the " + result + ". yay!"

但是,我对如何在Bison中做到这一点感到困惑:基本上,如何告诉它重新解析特定的字符串(在构造主要AST的同时)?

有任何想法吗?

如果确实需要,可以通过生成可重入解析器来重新解析该字符串您可能还需要一个可重入的扫描器,尽管我想您可以使用flex的缓冲区堆栈将某些东西与默认扫描器一起弄混。确实,值得学习如何基于避免不必要的全局变量的通用原则构建可重入的解析器和扫描器,无论您是否为此目的而需要它们。

但是,您实际上并不需要进行任何修复。您可以一次完成整个解析。您只需要扫描仪中有足够的智能即可了解嵌套插值。

基本思想是让扫描程序将带有插值的字符串文字拆分为一系列令牌,这些令牌可以很容易地由解析器组合为适当的AST。由于扫描程序可能会从单个字符串文字中返回多个令牌,因此我们需要引入一个开始条件来跟踪扫描当前是否在字符串文字中。而且,由于插值可以嵌套,因此我们将使用flex的可选起始条件栈(启用了%option stack)来跟踪嵌套上下文。

这是一个粗略的草图。

如前所述,扫描仪具有额外的启动条件:SC_PROGRAM,默认值,在扫描仪扫描常规程序文本时有效,以及SC_STRING,在扫描仪扫描字符串时有效。SC_PROGRAM仅因为flex没有提供官方接口来检查启动条件堆栈是否为空,才需要它;除了嵌套之外,它与INITIAL顶层启动条件相同。开始条件栈用于跟踪插标记(的[]在本示例中),并且它需要的,因为内插表达可能使用方括号(作为数组下标,例如)或甚至可能包括一个嵌套插值字符串。既然SC_PROGRAM是,除一个例外,与相同INITIAL,因此我们将其设为包含规则。

%option stack
%s SC_PROGRAM
%x SC_STRING
%%

由于我们使用单独的开始条件来分析字符串文字,因此我们在解析时也可以规范转义序列。并非所有的应用程序都希望这样做,但这很普遍。但这并不是真正答案的重点,因此我省略了大部分细节。更有趣的是嵌入式插值表达式的处理方式,特别是深度嵌套的表达式。

最终结果是将字符串文字转换为一系列标记,可能表示嵌套结构。为了避免在扫描程序中实际进行解析,我们不做任何尝试来创建AST节点或以其他方式重写字符串文字的尝试;相反,我们只是将引号字符本身传递给解析器,以分隔字符串文字的顺序:

["]                 { yy_push_state(SC_STRING);    return '"'; }
<SC_STRING>["]      { yy_pop_state();              return '"'; }

插值标记使用一组非常相似的规则:

<*>"["              { yy_push_state(SC_PROGRAM);   return '['; }
<INITIAL>"]"        {                              return ']'; }
<*>"]"              { yy_pop_state();              return ']'; } 

上面的第二条规则避免了在启动条件堆栈为空时弹出(因为它将处于INITIAL状态)。不必在扫描仪中发出错误消息。我们可以将无与伦比的右括号传递给解析器,解析器随后将执行所需的任何错误恢复。

要结束SC_STRING状态,我们需要返回字符串的令牌,可能包括转义序列:

<SC_STRING>{
  [^[\\"]+          { yylval.str = strdup(yytext); return T_STRING; }

  \\n               { yylval.chr = '\n';           return T_CHAR; }
  \\t               { yylval.chr = '\t';           return T_CHAR; }
          /* ... Etc. */
  \\x[[:xdigit]]{2} { yylval.chr = strtoul(yytext, NULL, 16);
                                               return T_CHAR; }
  \\.               { yylval.chr = yytext[1];      return T_CHAR; }
}

将这样的转义字符返回到解析器可能不是最佳策略。通常我会使用内部扫描器缓冲区来累积整个字符串。但这只是出于说明目的。(这里省略了一些错误处理;这里有各种特殊情况,包括换行处理和令人讨厌的情况,其中程序中的最后一个字符是未终止的字符串文字中的反斜杠。)

在解析器中,我们只需要为插入的字符串插入一个串联节点。唯一的麻烦是,我们不希望在不带任何插值的字符串文字的常见情况下插入这样的节点,因此我们使用两种语法生成方式,一种用于带正好包含一个部分的字符串,一种用于带正则表达式的字符串。两件或更多件:

string : '"' piece '"'                 { $$ = $2; }
       | '"' piece piece_list '"'      { $$ = make_concat_node(
                                                prepend_to_list($2, $3));
                                       }
piece  : T_STRING                      { $$ = make_literal_node($1); }  
       | '[' expr ']'                  { $$ = $2; }
piece_list
       : piece                         { $$ = new_list($1); }
       | piece_list piece              { $$ = append_to_list($1, $2); }

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章