是否可以在 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、活动模式或其他工具...
是否可以在 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、活动模式或其他工具...