【问题标题】:Is it possible to define a custom parser and AST for quotations in F#?是否可以在 F# 中为引用定义自定义解析器和 AST?
【发布时间】:2015-02-11 06:09:01
【问题描述】:

是否可以在 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 中不同,我不知道。感谢您的提示。

【问题讨论】:

  • F# 是一种支持图灵的编程语言。您在其中构建一个解析器,用于任何可合理解析的内容。您是要我们为您构建解析器吗?
  • 当然,我可以定义一些读取字符串的函数,并且主要执行我想要生成 AST 的操作。但是,将反引号放入其中是一件很痛苦的事情。最直接的方法是像 printf 一样,我们有特殊的标签 %d 和 %f,但是参数与占位符分开。由于 F# 已经有报价系统,我想使用它而不是重新发明轮子。
  • 你问是否可以写一个解析器。这是。这里还有什么可做的?反引号只是更多的语法;为什么这是一个问题?也许你的意思是,“有可能编写一个与 F# 语法挂钩的解析器”?我不知道。也许您坚持让您的解析器使用 F#(解析)机器进行反引号;这似乎很难,因为没有理由相信它会与您构建的解析器很好地集成。
  • 上面的具体问题不是是否可以生成解析器,而是是否可以将自定义解析器集成到F#的报价系统中。由于许多原因,这是一件常见的事情并且很有用。在 OCaml 中,这是通过camlp4 和语法let foo = &lt;myexpr:&lt; parsers + are + $e$ &gt;&gt; 完成的。在 Haskell 中,这是通过 Template Haskell 和语法 let foo = [myexpr| parsers + are + $e |] 完成的。好的报价工具为开发人员提供位置信息等信息,以提供合理的错误(calmp4 中的 _loc)。这已经完成了。

标签: f# dsl


【解决方案1】:

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

【讨论】:

    【解决方案2】:

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

    【讨论】:

    • 我以前没有听说过这个,它看起来像是 F# 嵌入语言的方式。谢谢指点!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-13
    • 2011-04-26
    • 1970-01-01
    • 1970-01-01
    • 2018-06-22
    • 1970-01-01
    相关资源
    最近更新 更多