当前位置: 首页 > 知识库问答 >
问题:

使bison开始使用与start规则不同的规则进行解析

华季同
2023-03-14

目前我正在开发一个源代码到源代码的编译器,我已经编写了一个bison解析器,可以正确地为输入创建AST。我现在需要对语法树进行几个转换,因此我需要在树中插入许多节点。

我可以手动创建我想添加到语法树中的所有结构/联合,但这似乎是非常繁重的工作。

对我来说,创建一个字符串会容易得多,我希望这个字符串能被我已有的解析器解析。然后,解析器应该返回这个字符串的树,我可以将其插入到我的原始语法树中。

不幸的是,字符串不能用我的解析器的start规则解析,因为它必须由子规则解析(例如,我的解析器解析包含语句的函数列表,字符串是单个语句)。

如何让bison解析字符串,从一个不同于开始规则的规则开始?

提前谢谢!

共有3个答案

包永新
2023-03-14

假代币并不能完全解决问题。当你想要解析某个语言的子规则时,你可能还需要重复调用该规则(多次调用yyparse)。

这就要求你用伪令牌“初始化”解析器,这会使它在每次调用时返回到有趣的规则,并保持实际非伪令牌流的正确状态。另外,您需要一种方法来检测yyparse调用是否遇到了EOF。

此外,理想情况下,您希望能够将调用与流上的yyparse和其他操作混合在一起,这意味着对Flex-Yacc组合执行的先行操作有精确的控制

我在TXR语言的解析器中解决了所有这些问题。在这种语言中,有一些有趣的子语言:Lisp语法和regex语法。

问题是提供一个Lisp读取函数,它从流中提取单个对象,并使流处于合理的状态(关于前瞻)。例如,假设流包含以下内容:

 (a b c d) (e f g h)

我们使用到达Lisp子语法所需的伪标记初始化解析器。然后调用yyparse。当yyparse完成时,它将消耗到这里的所有内容:

 (a b c d) (e f g h)
            ^ stream pointer is here
           ^ the lookahead token is the parenthesis

在这个调用之后,如果有人调用函数从流中获取字符,他们将获得e,而不是括号,很遗憾)。

总之,我们调用了yyparse,得到了(abcd)Lisp对象,流指针位于e,而先行标记是)。

下一次调用yyparse,它将忽略这个先行标记,我们将得到一个错误消息。我们不仅必须使用伪伪标记来初始化解析器,使其解析Lisp表达式,还必须让它在先行标记)处开始解析。

这样做的方法是将此令牌插入启动流。

在TXR解析器中,我实现了一个token stream对象,它最多可以占用四个pushback令牌。当调用yylex时,将从该回推中提取令牌,并且只有当其为空时才执行真正的lexing。

这在prime_解析器函数中使用:

void prime_parser(parser_t *p, val name, enum prime_parser prim)
{
  struct yy_token sec_tok = { 0 };

  switch (prim) {
  case prime_lisp:
    sec_tok.yy_char = SECRET_ESCAPE_E;
    break;
  case prime_interactive:
    sec_tok.yy_char = SECRET_ESCAPE_I;
    break;
  case prime_regex:
    sec_tok.yy_char = SECRET_ESCAPE_R;
    break;
  }

  if (p->recent_tok.yy_char)
    pushback_token(p, &p->recent_tok);
  pushback_token(p, &sec_tok);
  prime_scanner(p->scanner, prim);
  set(mkloc(p->name, p->parser), name);
}

解析器的recent_-tok成员跟踪最近看到的令牌,这使我们能够从最近的解析中访问前瞻令牌。

为了控制yylex,我在解析器中实现了这个黑客程序。l

/* Near top of file */

#define YY_DECL \
  static int yylex_impl(YYSTYPE *yylval_param, yyscan_t yyscanner)

/* Later */

int yylex(YYSTYPE *yylval_param, yyscan_t yyscanner)
{
  struct yyguts_t * yyg = convert(struct yyguts_t *, yyscanner);
  int yy_char;

  if (yyextra->tok_idx > 0) {
    struct yy_token *tok = &yyextra->tok_pushback[--yyextra->tok_idx];
    yyextra->recent_tok = *tok;
    *yylval_param = tok->yy_lval;
    return tok->yy_char;
  }

  yy_char = yyextra->recent_tok.yy_char = yylex_impl(yylval_param, yyscanner);
  yyextra->recent_tok.yy_lval = *yylval_param;

  return yy_char;

如果token pushback索引为非零,我们将弹出封装的token并将其返回给Yacc。否则我们称之为真正的lexeryylex_impl

注意,当我们这样做的时候,我们还可以窥视lexer返回的内容,并将其保存在最近的_-tok中。yy_char和最近的_tok。yy_lval

(如果yy_lval是一个堆分配的对象类型呢?幸好我们在这个项目中有垃圾收集功能!)

在匹配这些子语言的规则中,我必须使用yyapt。请注意,byacc_foolbusiness:这是让黑客与伯克利Yacc合作所需要的。(T.E.Dickey维护的版本,支持Bison可重入解析器方案。)

spec : clauses_opt              { parser->syntax_tree = $1; }
     | SECRET_ESCAPE_R regexpr  { parser->syntax_tree = $2; end_of_regex(scnr);
     | SECRET_ESCAPE_E n_expr   { parser->syntax_tree = $2; YYACCEPT; }
       byacc_fool               { internal_error("notreached"); }
     | SECRET_ESCAPE_I i_expr   { parser->syntax_tree = $2; YYACCEPT; }
       byacc_fool               { internal_error("notreached"); }
     | SECRET_ESCAPE_E          { if (yychar == YYEOF) {
                                    parser->syntax_tree = nao;
                                    YYACCEPT;
                                  } else {
                                    yybadtok(yychar, nil);
                                    parser->syntax_tree = nil;
                                  } }
     | SECRET_ESCAPE_I          { if (yychar == YYEOF) {
                                    parser->syntax_tree = nao;
                                    YYACCEPT;
                                  } else {
                                    yybadtok(yychar, nil);
                                    parser->syntax_tree = nil;
                                  } }
     | error '\n'               { parser->syntax_tree = nil;
                                  if (parser->errors >= 8)
                                    YYABORT;
                                  yyerrok;
                                  yybadtok(yychar, nil); }

     ;

    }

为什么要接受?我不记得了;好在我们有详细的变更记录信息:

* parser.y (spec): Use YYACCEPT in the SECRET_ESCAPE_E clause for
pulling a single expression out of the token stream. YYACCEPT
is a trick for not invoking the $accept : spec . $end production
which is implicitly built into the grammar, and which causes
a token of lookahead to occur.  This allows us to read a full
expression without stealing any further token: but only if the
grammar is structured right.

我认为这一评论略带误导性。隐式的$end生产导致了一个问题,这个问题不仅仅是不必要的前瞻性问题:它是前瞻性的,因为它实际上想要匹配EOF。我似乎记得,yyaccpt是一种退出解析器的方法,这样当下一个标记不是$end标记时,它就不会抛出语法错误,后者是EOF的内置表示。

不管怎样,Yacc最终还是展望未来;我们不希望它产生语法错误,因为前瞻并不像规则所预期的那样是文件的结尾。当我们有

 (a b c d) (e f g h)

我们有一个简单的语法规则来匹配一个表达式,(ef g h)东西看起来像是散乱的材料,这是一个语法错误!解析器获得第一个标记后,它再次调用yylex,并得到,这是一个语法错误;它希望yylex在该点指示EOF。yyaccpt是一种解决方法。我们让Yacc调用yylex,并取出第二个,并记下它,以便我们可以在下一个yyparse调用中将其推回;但我们防止Yacc对该令牌产生不适。

松俊才
2023-03-14

考虑到你似乎愿意做的事情,你可能会从这篇论文中找到有趣的想法。

它提供了从C代码支持具体语法的方法,比如(这里,从Bison解析器本身进行去语法):

exp: exp "&" exp
{
  // Sub−ASTs are used as−is (they are not printed, then parsed,
  // as in the previous example). Spaces are no longer critical.
  $$ = parse(Tweast() <<
             "if" << $1 << "then" << $3 << "<> 0 else 0");
};
蒲昀
2023-03-14

有一个简单的黑客,这在野牛FAQ中有描述。。

基本上,对于您希望能够使用的每个非终端,您创建一个伪令牌,然后创建一个“元开始”非终端,选择您想要使用的终端:

%token START_PROGRAM
%token START_STATEMENT
%token START_EXPRESSION

%start meta_start

%%

meta_start: START_PROGRAM program
          | START_STATEMENT statement
          | START_EXPRESSION expression
          ;

(在每个产品的操作中,您可以将2美元的价值保存在调用者可以获得的地方。)

现在您只需要安排您的lexer来传递正确的开始标记。您可以通过使用纯解析器和纯lexer并通过共享数据结构传递消息来做到这一点。这将是最好的方法,但是为了这个答案的目的,我将展示如何使用全局变量来实现它,因为原理是相同的:

extern int start_token;

// If all your start non-terminals produce a value of the same type, possibly a union
// type, you could arrange to return it instead of the error report.

int yyparse(int start) {
  // I left out the code to cause the lexer to scan a given string.
  start_token = start;
  return real_yyparse();
}

int yylex() {
  int rv = start_token;
  if (start_token)
    start_token = 0;
  else
    rv = real_yylex();
  return rv;
}
 类似资料:
  • 我想在Esper中编写一条规则,当过去15分钟内的步数为0且心率高于120时触发。我想出了以下规则: 我的心率类别有以下字段: 我的Steps类有以下字段: 我面临的问题是,我只想在过去15分钟内没有采取任何措施的情况下启动规则。现在,当两个步骤事件的步骤数相同时,它将激发。我知道我可能必须使用计时器。但是我不知道如何写这个规则。谁能帮帮我吗?

  • 我最近开始使用Esper编程,我有一个智能可穿戴设备,可以将计步器数据发送到我的笔记本电脑。然后,我使用esper处理这些数据。但假设我有多个智能穿戴设备,每个都有一个唯一的MAC地址。我使用时间窗口,我的问题是如何更改规则文件,使规则仅对具有相同MAC地址的事件触发,并基于此MAC地址采取适当的操作。我的初始化和规则是: “我的步骤”类具有以下字段: 以下是我插入事件的方式: 这是我的输出: 为

  • 我想达到的,大致可以概括为一个简单的或者有条件的。 虽然使用Fluent验证支持其他属性上的条件属性验证(When/Unless ),但似乎没有一种方法支持同一属性上的条件规则。还是我错过了什么? 本质上我想实现: 唯一的替代方法是将规则组合在一个自定义规则中? 然而,这紧密地耦合了规则逻辑,实际上它们是完全独立的条件,我可能想在其他对象/字段上使用。 有人建议在 FluentValidation

  • CloudGate解析规则可以直接导入使用,不需要任何额外的操作,非常方便! 规则列表 规则名称 下载地址 Surge https://async.be/Rule/Basic/Hosts Shadowrocket https://async.be/Rule/Basic/Hosts 解析规则 简要概述:通过实时同步Hosts信息源达到自动更新,同时使用解析模板进行生成。 无需任何其他操作,导入即可使

  • template.defaults.rules art-template 可以自定义模板解析规则,默认配置了原始语法与标准语法。 修改界定符 // 原始语法的界定符规则 template.defaults.rules[0].test = /<%(#?)((?:==|=#|[=-])?)[ \t]*([\w\W]*?)[ \t]*(-?)%>/; // 标准语法的界定符规则 template.def

  • Drools Guvnor有自己的版本控制系统,在生产使用中允许应用程序的用户修改规则和决策表,以适应业务的变化。然而,同样的资产仍然存在于开发版本控制系统中,在该系统中开发了应用程序的新功能。 这篇文章是为了寻找在使用Drools rules和Guvnor时关于规则开发和部署的见解/想法/经验。 下面是一些我一直困惑的关键概念。 首先,将drl文件和决策表部署到正式生产环境的最佳方式是什么?只需