微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

F#转换基于DU的AST的功能方法

如何解决F#转换基于DU的AST的功能方法

在OOP领域,例如使用访客模式的Roslyn及其语法重写器。 很好,因为已经有一个基本的重写器类,该类定义了所有不做任何事情的访问方法,而我只需要重写我关心的方法即可。

与DU类AST相比,什么是可比的解决方案?

例如,如果我想编写一个访问AST的每个节点的函数,并用以下snippet (not made by me)解析)

我可以这样编写转换器函数

// strip all class type modifiers because of reasons
let typeTransformer (input:CSharpType) : CSharpType =
    match input with
    | Class (access,modifier,name,implements,members) ->
        Class (access,None,members)
    | _ -> input

let rec nameSpaceTransformer typeTransformer (input:NamespaceScope) : NamespaceScope =
    match input with
    | Namespace (imports,names,nestednamespaces) ->
        Namespace (imports,List.map (nameSpaceTransformer typeTransformer) nestednamespaces)
    | Types (names,types) ->
        Types (names,List.map typeTransformer types)

这已经很麻烦了,但是随着深入其中,情况变得越来越糟。 这种表示是否不适合进行此类转换?

编辑:我实际上正在寻找的一种方式是,我可以仅定义特定的转换函数,然后将这些函数自动应用于正确的节点,而其他所有内容保持不变。

Here is my best try so far on a simplified example (Fable REPL) 注意注释后的最后两个let,在以后的用法中,只需要写那两个,然后用实际的AST实例调用transform replaceAllAsWithBsTransformer someAstRoot。 当然,此解决方案无法正常工作,因为它将需要递归记录。 (例如,transformMiddleNode函数实际上应该要求一个转换器记录,并要求它是transformleaf成员)。

这是我遇到的麻烦,我想说它可以通过OOP访问者模式很好地解决,但是我不知道如何在这里成功地进行镜像。

编辑2:

最后,我只是以

的形式实现了一个实际的访问者类。
type Transformer() =
    abstract member Transformleaf : Leaf -> Leaf
    default this.Transformleaf leaf = id leaf
    abstract member TransformMiddleNode : MiddleNode -> MiddleNode
    default this.TransformMiddleNode node =
        match node with
        | MoreNodes nodeList ->
            List.map this.TransformMiddleNode nodeList
            |> MoreNodes
        | Leaf leaf -> this.Transformleaf leaf |> Leaf
    abstract member TransformUpperNode : UpperNode -> UpperNode
    default this.TransformUpperNode node =
        match node with
        | MoreUpperNodes nodeList ->
            List.map TransformUpperNode nodeList |> MoreUpperNodes
        | MiddleNodes nodeList ->
            List.map TransformMiddleNode nodeList |> MiddleNodes
        ...

然后我可以定义特定的转换,例如:

type LeafTransformer()
    inherit Transformer()
    override this.Transformleaf leaf = someLeafTransformation leaf

其中someLeafTransformation: Leaf -> Leaf 这并不比OOP解决方案差一点(本质上是相同的,只是“底层”访问者界面被模式匹配所替代。

解决方法

您发布的代码肯定是按照“功能方式”来执行的。我不清楚这到底是“麻烦的”还是“变得越深的人变得越糟”。我认为这里的关键概念是尽可能简洁地编写您的函数(但要使它们变得不那么简明!),然后找出辅助函数和依赖于这些函数的更高级别函数的正确组合,并在需要时加上好的注释

您的第一个功能可能就是这样:

let transformModifier input =
    match input with
    | Class (a,modifier,c,d,e) -> (a,None,b,d)
    | _ -> input

这不太冗长,但仍然可读。实际上,由于现在唯一要做的就是更改class修饰符,因此它的可读性可能更高。

也许您可能想创建其他修改类的函数,并使用>>进行组合,然后从遍历整个树的更大函数中调用它们。

代码的最终可读性将主要取决于您(IMO)。

Expert F# F#Deep Dives 等书中对AST转换进行了很好的讨论。

,

我实际上正在寻找的一种方式是,我可以仅定义特定的转换函数,然后将这些函数自动应用于正确的节点,而其他所有内容保持不变。

我编写了一个名为FSharp.Text.Experimental.Transform的AST转换库,正是这样做的。巧合的是,我已经写了C# grammar definition,因此可以用来尝试解决“带状类修饰符”问题。

使用此库实现的解决方案,首先将C#语法定义输入到 GrammarProvider 类型提供程序中。类型提供程序将提供解析输入文本的方法,并为语法中的每个非终结符提供类型。

open FSharp.Text.Experimental.Transform

type CSharp = GrammarProvider<"csharp.grm">

接下来,您定义仅在您关心的节点上运行的转换函数。由于您关心转换类的修饰符,因此将 ClassModifier* part of the ClassDefinition grammar production

// This function replaces any list of class modifiers with the empty list
let stripClassModifiers (_: CSharp.ClassModifier list) = []

最后,您解析输入文本,应用转换函数,然后未解析为字符串:

CSharp.ParseFile("path/to/program.cs").ApplyOnePass(stripClassModifiers).ToString()

ApplyOnePass()方法将对AST执行一次遍历,在发现类修饰符列表的任何地方应用stripClassModifiers转换,并使所有其他节点保持不变。

该库包含用于更复杂转换的更强大的方法,但是我希望上面的示例足以说明这个想法。有关教程,示例,API参考以及有关其功能的更多详细信息,请参见the library documentation

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。