如何解决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 举报,一经查实,本站将立刻删除。