Fabulous.AST
Welcome to the Fabulous.AST, an Abstract Syntax Tree (AST) Domain Specific Language (DSL) for F#.
Fabulous.AST uses Fantomas to generate F# code from AST. This means that you can use Fabulous.AST to generate F# code that is formatted according to the Fantomas style guide. It's designed to provide a simple and expressive way to represent code as a tree of nodes. This makes it easier to manipulate and analyze code programmatically.
Plain Fantomas
Let's take a look at an AST example in Fantomas:
#r "../src/Fabulous.AST/bin/Release/netstandard2.1/publish/Fantomas.FCS.dll"
#r "../src/Fabulous.AST/bin/Release/netstandard2.1/publish/Fantomas.Core.dll"
open Fantomas.FCS.Text
open Fantomas.Core
open Fantomas.Core.SyntaxOak
let implementationSyntaxTree =
Oak(
[],
[ ModuleOrNamespaceNode(
None,
[ BindingNode(
None,
None,
MultipleTextsNode([ SingleTextNode("let", Range.Zero) ], Range.Zero),
false,
None,
None,
Choice1Of2(IdentListNode([ IdentifierOrDot.Ident(SingleTextNode("x", Range.Zero)) ], Range.Zero)),
None,
[],
None,
SingleTextNode("=", Range.Zero),
Expr.Constant(Constant.FromText(SingleTextNode("12", Range.Zero))),
Range.Zero
)
|> ModuleDecl.TopLevelBinding ],
Range.Zero
) ],
Range.Zero
)
CodeFormatter.FormatOakAsync(implementationSyntaxTree)
|> Async.RunSynchronously
|> printfn "%s"
// produces the following code:
|
let x = 12
Using Fabulous.AST
Now let's take a look at same example using Fabulous.AST:
#r "../src/Fabulous.AST/bin/Release/netstandard2.1/publish/Fabulous.AST.dll"
open Fabulous.AST
open type Fabulous.AST.Ast
Oak() { AnonymousModule() { Value("y", Unquoted "12") } }
|> Gen.mkOak
|> CodeFormatter.FormatOakAsync
|> Async.RunSynchronously
|> printfn "%s"
// produces the following code:
|
let y = 12
Escape Hatch
You can use an Escape Hatch
to generate code that is not supported by Fabulous.AST yet by constructing the raw Fantomas.SyntaxOak node.
For example, the following code:
open type Fabulous.AST.Ast
let topLevelBinding: ModuleDecl =
BindingNode(
xmlDoc = None,
attributes = None,
leadingKeyword = MultipleTextsNode([ SingleTextNode("let", Range.Zero) ], Range.Zero),
isMutable = false,
inlineNode = None,
// We cannot define private yet using Fabulous.AST
accessibility = Some(SingleTextNode("private", Range.Zero)),
functionName = Choice1Of2(IdentListNode([ IdentifierOrDot.Ident(SingleTextNode("x", Range.Zero)) ], Range.Zero)),
genericTypeParameters = None,
parameters = List.Empty,
returnType = None,
equals = SingleTextNode("=", Range.Zero),
expr = Expr.Constant(Constant.FromText(SingleTextNode("12", Range.Zero))),
range = Range.Zero
)
|> ModuleDecl.TopLevelBinding
let sourceWithEscapeHatch =
Oak() {
AnonymousModule() {
Value("a", Unquoted "11")
EscapeHatch(topLevelBinding)
}
}
Gen.mkOak sourceWithEscapeHatch
|> CodeFormatter.FormatOakAsync
|> Async.RunSynchronously
|> printfn "%s"
// produces the following code:
|
Key takeaway
Using Fabulous.AST, you can easily create and manipulate ASTs like this one using F# functions. For example, you can add new nodes to the AST, modify existing nodes, or traverse the AST to perform analysis or transformation tasks.
Fabulous.AST is a powerful tool for anyone who works with code and wants to automate or streamline their development workflow. Whether you're a compiler writer, a code generator, or just someone who wants to write better code faster, Fabulous.AST can help you achieve your goals.
type Oak = inherit NodeBase new: parsedHashDirectives: ParsedHashDirectiveNode list * modulesOrNamespaces: ModuleOrNamespaceNode list * m: range -> Oak override Children: Node array member ModulesOrNamespaces: ModuleOrNamespaceNode list member ParsedHashDirectives: ParsedHashDirectiveNode list
--------------------
new: parsedHashDirectives: ParsedHashDirectiveNode list * modulesOrNamespaces: ModuleOrNamespaceNode list * m: range -> Oak
type ModuleOrNamespaceNode = inherit NodeBase new: header: ModuleOrNamespaceHeaderNode option * decls: ModuleDecl list * range: range -> ModuleOrNamespaceNode override Children: Node array member Declarations: ModuleDecl list member Header: ModuleOrNamespaceHeaderNode option member IsNamed: bool
--------------------
new: header: ModuleOrNamespaceHeaderNode option * decls: ModuleDecl list * range: range -> ModuleOrNamespaceNode
type BindingNode = inherit NodeBase new: xmlDoc: XmlDocNode option * attributes: MultipleAttributeListNode option * leadingKeyword: MultipleTextsNode * isMutable: bool * inlineNode: SingleTextNode option * accessibility: SingleTextNode option * functionName: Choice<IdentListNode,Pattern> * genericTypeParameters: TyparDecls option * parameters: Pattern list * returnType: BindingReturnInfoNode option * equals: SingleTextNode * expr: Expr * range: range -> BindingNode member Accessibility: SingleTextNode option member Attributes: MultipleAttributeListNode option override Children: Node array member Equals: SingleTextNode member Expr: Expr member FunctionName: Choice<IdentListNode,Pattern> member GenericTypeParameters: TyparDecls option member Inline: SingleTextNode option ...
--------------------
new: xmlDoc: XmlDocNode option * attributes: MultipleAttributeListNode option * leadingKeyword: MultipleTextsNode * isMutable: bool * inlineNode: SingleTextNode option * accessibility: SingleTextNode option * functionName: Choice<IdentListNode,Pattern> * genericTypeParameters: TyparDecls option * parameters: Pattern list * returnType: BindingReturnInfoNode option * equals: SingleTextNode * expr: Expr * range: range -> BindingNode
type MultipleTextsNode = inherit NodeBase new: content: SingleTextNode list * range: range -> MultipleTextsNode override Children: Node array member Content: SingleTextNode list
--------------------
new: content: SingleTextNode list * range: range -> MultipleTextsNode
type SingleTextNode = inherit NodeBase new: idText: string * range: range -> SingleTextNode override Children: Node array member Text: string
--------------------
new: idText: string * range: range -> SingleTextNode
module Range from Fantomas.FCS.Text
--------------------
[<Struct>] type Range = member End: pos member EndColumn: int member EndLine: int member EndRange: range member FileName: string member IsSynthetic: bool member Start: pos member StartColumn: int member StartLine: int member StartRange: range ...
type IdentListNode = inherit NodeBase new: content: IdentifierOrDot list * range: range -> IdentListNode override Children: Node array member Content: IdentifierOrDot list member IsEmpty: bool static member Empty: IdentListNode
--------------------
new: content: IdentifierOrDot list * range: range -> IdentListNode
static member CodeFormatter.FormatOakAsync: oak: Oak * config: FormatConfig -> Async<string>
module Async from Fantomas.Core
--------------------
type Async = static member AsBeginEnd: computation: ('Arg -> Async<'T>) -> ('Arg * AsyncCallback * obj -> IAsyncResult) * (IAsyncResult -> 'T) * (IAsyncResult -> unit) static member AwaitEvent: event: IEvent<'Del,'T> * ?cancelAction: (unit -> unit) -> Async<'T> (requires delegate and 'Del :> Delegate) static member AwaitIAsyncResult: iar: IAsyncResult * ?millisecondsTimeout: int -> Async<bool> static member AwaitTask: task: Task<'T> -> Async<'T> + 1 overload static member AwaitWaitHandle: waitHandle: WaitHandle * ?millisecondsTimeout: int -> Async<bool> static member CancelDefaultToken: unit -> unit static member Catch: computation: Async<'T> -> Async<Choice<'T,exn>> static member Choice: computations: Async<'T option> seq -> Async<'T option> static member FromBeginEnd: beginAction: (AsyncCallback * obj -> IAsyncResult) * endAction: (IAsyncResult -> 'T) * ?cancelAction: (unit -> unit) -> Async<'T> + 3 overloads static member FromContinuations: callback: (('T -> unit) * (exn -> unit) * (OperationCanceledException -> unit) -> unit) -> Async<'T> ...
--------------------
type Async<'T>
static member Ast.Oak: unit -> CollectionBuilder<Oak,ModuleOrNamespaceNode>
--------------------
module Oak from Fabulous.AST
--------------------
type Oak = inherit NodeBase new: parsedHashDirectives: ParsedHashDirectiveNode list * modulesOrNamespaces: ModuleOrNamespaceNode list * m: range -> Oak override Children: Node array member ModulesOrNamespaces: ModuleOrNamespaceNode list member ParsedHashDirectives: ParsedHashDirectiveNode list
--------------------
new: parsedHashDirectives: ParsedHashDirectiveNode list * modulesOrNamespaces: ModuleOrNamespaceNode list * m: range -> Oak
static member Ast.Value: name: WidgetBuilder<Pattern> * value: StringVariant -> WidgetBuilder<BindingNode>
static member Ast.Value: name: string * value: WidgetBuilder<Expr> -> WidgetBuilder<BindingNode>
static member Ast.Value: name: WidgetBuilder<Pattern> * value: WidgetBuilder<Expr> -> WidgetBuilder<BindingNode>
<summary> It takes the root of the widget tree and create the corresponding Fantomas node, and recursively creating all children nodes </summary>
module BindingNode from Fabulous.AST
--------------------
type BindingNode = inherit NodeBase new: xmlDoc: XmlDocNode option * attributes: MultipleAttributeListNode option * leadingKeyword: MultipleTextsNode * isMutable: bool * inlineNode: SingleTextNode option * accessibility: SingleTextNode option * functionName: Choice<IdentListNode,Pattern> * genericTypeParameters: TyparDecls option * parameters: Pattern list * returnType: BindingReturnInfoNode option * equals: SingleTextNode * expr: Expr * range: range -> BindingNode member Accessibility: SingleTextNode option member Attributes: MultipleAttributeListNode option override Children: Node array member Equals: SingleTextNode member Expr: Expr member FunctionName: Choice<IdentListNode,Pattern> member GenericTypeParameters: TyparDecls option member Inline: SingleTextNode option ...
--------------------
new: xmlDoc: XmlDocNode option * attributes: MultipleAttributeListNode option * leadingKeyword: MultipleTextsNode * isMutable: bool * inlineNode: SingleTextNode option * accessibility: SingleTextNode option * functionName: Choice<IdentListNode,Pattern> * genericTypeParameters: TyparDecls option * parameters: Pattern list * returnType: BindingReturnInfoNode option * equals: SingleTextNode * expr: Expr * range: range -> BindingNode
module SingleTextNode from Fabulous.AST
<summary> Contains all the single text nodes that are used in the AST </summary>
--------------------
type SingleTextNode = static member Create: idText: string -> SingleTextNode
module List from Fabulous.AST
--------------------
module List from Fantomas.Core
--------------------
module List from Microsoft.FSharp.Collections
--------------------
type List<'T> = | op_Nil | op_ColonColon of Head: 'T * Tail: 'T list interface IReadOnlyList<'T> interface IReadOnlyCollection<'T> interface IEnumerable interface IEnumerable<'T> member GetReverseIndex: rank: int * offset: int -> int member GetSlice: startIndex: int option * endIndex: int option -> 'T list static member Cons: head: 'T * tail: 'T list -> 'T list member Head: 'T member IsEmpty: bool member Item: index: int -> 'T with get ...
module Expr from Fabulous.AST
--------------------
type Expr = | Lazy of ExprLazyNode | Single of ExprSingleNode | Constant of Constant | Null of SingleTextNode | Quote of ExprQuoteNode | Typed of ExprTypedNode | New of ExprNewNode | Tuple of ExprTupleNode | StructTuple of ExprStructTupleNode | ArrayOrList of ExprArrayOrListNode ... static member Node: x: Expr -> Node member HasParentheses: bool member IsAnonStructRecord: bool member IsApp: bool member IsAppLongIdentAndSingleParenArg: bool member IsAppSingleParenArg: bool member IsAppWithLambda: bool member IsArrayOrList: bool member IsBeginEnd: bool member IsChain: bool ...
static member Ast.Constant: value: StringVariant -> WidgetBuilder<Constant>
--------------------
module Constant from Fabulous.AST
--------------------
type Constant = | FromText of SingleTextNode | Unit of UnitNode | Measure of ConstantMeasureNode static member Node: c: Constant -> NodeBase member IsFromText: bool member IsMeasure: bool member IsUnit: bool
static member Ast.EscapeHatch: node: 'T -> WidgetBuilder<'T>
--------------------
module EscapeHatch from Fabulous.AST