是否可以在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#限制可以阻止这种情况。
@ tomas-petricek回答了我的直接问题,但我正在寻找的更符合@mydogisbox。看起来嵌入式语言在F#中的表现与在OCaml或Haskell中不同,我不知道。感谢您的提示。
答案 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是编译错误)并在运行时执行它。换句话说,使用类型提供程序而不是引用来执行此操作。