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

时间:2015-02-08 15:29:30

标签: f# dsl

是否可以在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#语法。

顺便说一下,虽然现在语言非常不同,但在OCaml中可以使用camlp4进行上述操作,所以看起来可行,但我不确定是否有任何F#限制可以阻止这种情况。

编辑1

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

2 个答案:

答案 0 :(得分:3)

可以用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) @> 

...返回SuperAdd (Num 1,Add (Num 1,Num 2))

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

答案 1 :(得分:1)

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