是否可以在 F# 中为引用定义自定义解析器和 AST?

Is it possible to define a custom parser and AST for quotations in F#?

是否可以在 F# 中为引用定义自定义解析器和 AST?具体来说,代码

open Microsoft.FSharp.Quotations
let e = <@ 1+1 @>

生成

val e : Expr<int> = Call (None, op_Addition, [Value (1), Value (1)])

我想做两件事。第一,我想生成我自己的 AST。例如

val it : Expr = Add(Num 1,Num 1)

第二,我想定义自己的解析器而不使用 F# 语法。例如,写成

这样的东西会很好
let e = <@ 1++1 @>

当然,如果我不关心语法,我可以定义一个从 F# 语法树到我自己的语法树的程序转换。我不想那样做;我想使用自定义解析器。

对于某些背景,我想使用引号和反引号为某些 DSL 生成 AST。基本上,我想要以下内容

let e1 = <@ 1 @>
let e2 = <@ %e1 + 2 @>

生成 Add(Num 1,Num 2) 并且不受制于 F# 语法。

顺便说一句,虽然现在是一种非常不同的语言,但在带有 camlp4 的 OCaml 中,上述内容是可能的,所以它似乎是可行的,但我不确定是否有任何 F# 限制来阻止这种情况。

编辑 1

@tomas-petricek 回答了我的直接问题,但我正在寻找的更符合@mydogisbox。看起来嵌入式语言在 F# 中的完成方式与在 OCaml 或 Haskell 中的方式不同,我不知道。感谢您的提示。

完全有可能像您想要的那样为自定义语言安全地编译时,但我认为您关注的是错误的语言功能。看看FSharp.Data.SQLClient。它是一个 F# 类型的提供程序,它静态分析 sql 查询(即格式不正确的 sql 是一个编译错误)并在运行时执行它。换句话说,使用类型提供程序而不是引号来执行此操作。

可以包含在 F# 引号中的代码仅限于有效的 F# 语法。但是,您可以以非常灵活的方式使用它——它可以包含您想要的任何函数或自定义运算符。所以,如果你想要++,你可以定义它(即使没有实现):

let (++) a b = a + b

引用代码始终表示为 F# 引用(Expr 类型),但您可以将其转换为您需要的任何结构。例如,您可以定义一个可区分的联合:

type Expr = 
  | Add of Expr * Expr
  | SuperAdd of Expr * Expr
  | Num of int

并编写一个翻译函数,将引语变成你的 Expr:

打开Microsoft.FSharp.Quotations

let rec translate = function
  | DerivedPatterns.SpecificCall <@ (++) @> (_, _, [e1; e2]) -> 
      SuperAdd(translate e1, translate e2)
  | DerivedPatterns.SpecificCall <@ (+) @> (_, _, [e1; e2]) -> 
      Add(translate e1, translate e2)
  | Patterns.Value(n, t) when t = typeof<int> -> 
      Num(n :?> int)
  | _ -> failwith "Not supported"

现在,运行这个:

translate <@ 1 ++ (1 + 2) @> 

...returns SuperAdd (Num 1,Add (Num 1,Num 2)).

F# 引用的强大之处在于它们可以让您重新解释标准的 F# 代码,因此在其中放置任意语法将违反原则。但是您始终可以将自定义结构放入字符串中并编写自定义解析器 - 使用 FParsec、FsLex/FsYacc、活动模式或其他工具...