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

如何用Menhir解析生成GADT表达式?OCaml编译报错求助

问题描述

学习OCaml的GADT后,尝试基于《Real World OCaml》中的小型语言实现解释器,使用Menhir编写解析器。AST定义如下:

type _ value =
  | Int : int -> int value
  | Bool : bool -> bool value

type _ expr = 
  | Value: 'a value -> 'a expr
  | Eq : int expr * int expr -> bool expr
  | Plus : int expr * int expr -> int expr
  | If : bool expr * 'a expr * 'a expr -> 'a expr
  

let eval_value : type a. a value -> a = function
  | Int x -> x
  | Bool x -> x ;;  
  
let rec eval: type a. a expr -> a = function
  | Value v -> eval_value v
  | If (c, t, e) -> if eval c then eval t else eval e
  | Eq (x, y) -> eval x = eval y
  | Plus (x, y) -> eval x + eval y;

词法分析器代码:

{
    open Parser
    exception Error of string
}

rule token = parse
| [' ' '\t'] {token lexbuf}
| '\n' {Lexing.new_line lexbuf; token lexbuf}
| "if" {IF}
| "then" {THEN}
| "else" {ELSE}
| '=' {EQ}
| '+' {PLUS}
| "true" {BOOL (true)}
| "FALSE" {BOOL (false)}
| ['0'-'9']+ as i {NUM (int_of_string i)}
| '-'['0'-'9']+ as i { NUM (int_of_string i) }
| eof {EOF}
| _ { raise (Error (Printf.sprintf "At offset %d: unexpected character.\n" (Lexing.lexeme_start lexbuf))) }

解析器代码:

%{
    open Ast
%}

%token <int> NUM
%token <bool> BOOL
%token PLUS
%token IF
%token THEN
%token ELSE
%token EQ
%token EOF

%left PLUS

%start <'a Ast.expr> expr_toplevel

%%

expr_toplevel:
| e = expr EOF {e}

expr:
| x = expr PLUS y = expr {Plus (x, y)} 
| IF c = expr THEN x = expr ELSE y = expr {If (c, x, y)}
| x = expr EQ y = expr {EQ (x, y)}
| b = BOOL {Value (Bool b)}
| n = NUM {Value (Int n)}

编译时出现错误:

File "bin/parser.mly", line 27, characters 18-26:
Error: This expression has type bool value
but an expression was expected of type int value
Type bool is not compatible with type int

疑问:这是否和书中「GADT、局部抽象类型与多态递归」章节里将GADT当作普通变体时的错误一致?该如何修复?尝试了Menhir的--inspection flag但无效果。

原因分析

这确实是同一类问题。Menhir默认会将expr非终结符的类型推断为单一具体类型,而你的GADT定义中expr是多态的(可以是int expr或bool expr等)。类型检查器会强制expr的所有分支返回同一类型,导致Value (Bool b)(返回bool expr)和其他分支(比如Plus返回int expr)类型冲突,从而报错。

修复方案

需要明确告诉Menhir,expr非终结符是多态的,可以返回任意类型的'a expr。通过在解析器中使用局部抽象类型来标注expr的类型即可解决:

修改后的解析器代码:

%{
    open Ast
%}

%token <int> NUM
%token <bool> BOOL
%token PLUS
%token IF
%token THEN
%token ELSE
%token EQ
%token EOF

%left PLUS

%start <'a Ast.expr> expr_toplevel

%%

expr_toplevel:
| e = expr EOF {e}

(* 用局部抽象类型标注expr的多态性 *)
expr: 'a.
| x = expr PLUS y = expr {Plus (x, y)} 
| IF c = expr THEN x = expr ELSE y = expr {If (c, x, y)}
| x = expr EQ y = expr {Eq (x, y)}  (* 修复构造子大小写笔误 *)
| b = BOOL {Value (Bool b)}
| n = NUM {Value (Int n)}

关键修改点:

  • 在expr规则前添加'a.,表示该规则返回'a Ast.expr类型,且'a是局部抽象类型,允许每个分支返回不同的具体实例(比如int expr或bool expr)。
  • 修复了小笔误:原解析器中EQ (x, y)对应AST中的Eq构造子(注意大小写)。
验证修复

修改后重新编译解析器,错误会消失。可以测试以下输入:

  • 1 + 2 会被解析为Plus (Value (Int 1), Value (Int 2)),求值结果为3。
  • if 1 = 2 then true else false 会被解析为If (Eq (Value (Int 1), Value (Int 2)), Value (Bool true), Value (Bool false)),求值结果为false。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 08:13:26