You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

递归下降解析扩展λ演算:处理F::=P{P}产生式的问题

问题:移除λ演算解释器解析器中的重复前瞻检查

问题背景

我正在用C语言编写一个基于简单λ演算的解释器,语言的EBNF文法如下:

S ::= E
E ::= 'fn' var '->' E | T {'+' T} | T {'-' T}
T ::= F {'*' F} | F {'/' F}
F ::= P {P}
P ::= var | number | '(' E ')'

我已经实现了不含第4条产生式(F ::= P {P})的解析器,但处理{P}部分时,当前的做法是在parse_factor里先检查前瞻token,如果是var、number或(就调用parse_primitive,但这和parse_primitive里的检查重复了,想去掉这个重复的检查。

我的代码如下:

Ast *parse_expr(Lexer *lexer) {
  if (match_token(lexer, TOK_KW_FN)) {
    expect_token(lexer, TOK_VAR);
    char *lexeme = get_last_lexeme(lexer);
    Ast *var = make_var(lexeme);
    expect_token(lexer, TOK_ARROW);
    Ast *body = parse_expr(lexer);
    return make_ast(var, body);
  } else {
    Ast *expr = parse_term(lexer);
    for (;;) {
      int op;
      if (match_token(lexer, TOK_PLUS)) {
        op = OP_ADD;
      } else if (match_token(lexer, TOK_MINUS)) {
        op = OP_SUB;
      } else {
        return expr;
      }
      Ast *term = parse_term(lexer);
      expr = make_binop(op, expr, term);
    }
  }
}

Ast *parse_term(Lexer *lexer) {
  Ast *term = parse_factor(lexer);
  for (;;) {
    int op;
    if (match_token(lexer, TOK_STAR)) {
      op = OP_MUL;
    } else if (match_token(lexer, TOK_SLASH)) {
      op = OP_DIV;
    } else {
      return term;
    }
    Ast *factor = parse_factor(lexer);
    term = make_binop(op, term, factor);
  }
}

Ast *parse_factor(Lexer *lexer) {
  Ast *factor = parse_primitive(lexer);
  for (;;) {
    int tok = lexer_peek(lexer);
    if (tok != TOK_LPAR && tok != TOK_VAR && tok != TOK_NUMBER) { /* 想移除这个重复检查,因为parse_primitive里已经做了 */
      return factor;
    }
    Ast *primitive= parse_primitive(lexer);
    factor = make_app(factor, primitive);
  }
}

Ast *parse_primitive(Lexer *lexer) {
  if (lexer_match(lexer, TOK_LPAR)) {
    Ast *expr = parse_expr(lexer);
    if (!lexer_match(lexer, TOK_RPAR)) {
      parser_error("expected ')'");
    }
    return expr;
  } else if (lexer_match(lexer, TOK_NUMBER)) {
    char *lexeme = get_last_lexeme(lexer);
    return make_number(lexeme);
  } else if (lexer_match(lexer, TOK_VAR)) {
    char *lexeme = get_last_lexeme(lexer);
    return make_var(lexeme);
  }
  parser_error("expected '(', number or var");
}

解决方案

核心是把primitive的匹配逻辑集中到一处,避免分散在多个函数里重复判断,提供两种可行方案:

方案1:新增“尝试解析primitive”的函数

新增一个不直接报错的try_parse_primitive函数,用于在parse_factor里尝试解析,失败则终止循环:

// 保存/恢复lexer状态的辅助函数,需自行实现(记录位置、行号等信息)
typedef struct {
  int pos;
  int line;
  // 其他需要保存的lexer状态字段
} LexerState;

LexerState lexer_save_state(Lexer *lexer) {
  LexerState state;
  state.pos = lexer->pos;
  state.line = lexer->line;
  return state;
}

void lexer_restore_state(Lexer *lexer, LexerState state) {
  lexer->pos = state.pos;
  lexer->line = state.line;
}

// 返回NULL表示当前token不是合法primitive,不触发报错
Ast *try_parse_primitive(Lexer *lexer) {
  LexerState state = lexer_save_state(lexer);
  
  if (lexer_match(lexer, TOK_LPAR)) {
    Ast *expr = parse_expr(lexer);
    if (!lexer_match(lexer, TOK_RPAR)) {
      lexer_restore_state(lexer, state);
      return NULL;
    }
    return expr;
  } else if (lexer_match(lexer, TOK_NUMBER)) {
    char *lexeme = get_last_lexeme(lexer);
    return make_number(lexeme);
  } else if (lexer_match(lexer, TOK_VAR)) {
    char *lexeme = get_last_lexeme(lexer);
    return make_var(lexeme);
  }
  
  lexer_restore_state(lexer, state);
  return NULL;
}

// 保留原有的parse_primitive,用于必须匹配primitive的场景
Ast *parse_primitive(Lexer *lexer) {
  Ast *ast = try_parse_primitive(lexer);
  if (!ast) {
    parser_error("expected '(', number or var");
  }
  return ast;
}

修改后的parse_factor无需手动检查token:

Ast *parse_factor(Lexer *lexer) {
  Ast *factor = parse_primitive(lexer);
  for (;;) {
    Ast *primitive = try_parse_primitive(lexer);
    if (!primitive) {
      return factor;
    }
    factor = make_app(factor, primitive);
  }
}

方案2:抽离token判断的辅助函数

如果不想新增函数,可以把primitive的token判断逻辑抽成一个独立函数,让parse_factor和parse_primitive复用:

bool is_primitive_token(int tok) {
  return tok == TOK_LPAR || tok == TOK_VAR || tok == TOK_NUMBER;
}

修改parse_factor的检查逻辑:

Ast *parse_factor(Lexer *lexer) {
  Ast *factor = parse_primitive(lexer);
  for (;;) {
    int tok = lexer_peek(lexer);
    if (!is_primitive_token(tok)) {
      return factor;
    }
    Ast *primitive= parse_primitive(lexer);
    factor = make_app(factor, primitive);
  }
}

后续扩展primitive类型时,只需要修改is_primitive_token一处即可,避免多处同步修改的遗漏问题。

内容的提问来源于stack exchange,提问作者Dendrit

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 20:24:54