DEV Community

jkone27
jkone27

Posted on

Learning some Fantomas AST

Made a test encoder/decored for a simple namespace declaration with Fantomas OAK Ast, see fantomas tools page

given

Oak (1,0-1,15)
        ModuleOrNamespaceNode (1,0-1,15)
            ModuleOrNamespaceHeaderNode (1,0-1,15)
                MultipleTextsNode (1,0-1,9)
                    namespace (1,0-1,9)
                IdentListNode (1,10-1,15)
                    A (1,10-1,11)
                    B (1,12-1,13)
                    C (1,14-1,15)
Enter fullscreen mode Exit fullscreen mode

it will output A.B.C (lowered)

you can run it as dotnet fsi script.fsx or in vscode with dotnet sdk installed. cheers!

let oakAst = 
    "hello.one.two"
    |> FantomasWriter.makeNamespace  []

oakAst 
|> FantomasReader.extractNsFromOak 
|> Option.iter (printfn "NAMESPACE: %s")
Enter fullscreen mode Exit fullscreen mode

Fabulous.AST

if you want to make your life easier and drop the custom write part for Oak, you can make use of the awesome Fabulous.AST DSL nuget package

#r "nuget:Fabulous.AST"

open System
open Fabulous.AST
open type Fabulous.AST.Ast

// using Fabulous.AST life becomes easier for writing oak
let oakAst = 
    Ast.Oak(){
        Ast.Namespace("hello.one.two") {}
    }
    |> Gen.mkOak

Enter fullscreen mode Exit fullscreen mode

Here a Gist of It

#r "nuget:Fantomas.Core"
open Fantomas.Core
open Fantomas.Core.SyntaxOak
open Fantomas.FCS.Text
open System
(** `namespace A.B.C` : https://fsprojects.github.io/fantomas-tools
Oak (1,0-1,15)
ModuleOrNamespaceNode (1,0-1,15)
ModuleOrNamespaceHeaderNode (1,0-1,15)
MultipleTextsNode (1,0-1,9)
namespace (1,0-1,9)
IdentListNode (1,10-1,15)
A (1,10-1,11)
B (1,12-1,13)
C (1,14-1,15)
*)
module FantomasWriter =
let text v = SingleTextNode(v, Range.Zero)
let toMultipleTexts lst = MultipleTextsNode(lst |> Seq.map text |> Seq.toList ,Range.Zero)
let toIdentList (lst: string seq) =
let ll =
lst
|> Seq.map (fun l ->
let content = l |> text
if l = "." then
content |> IdentifierOrDot.KnownDot
else
content |> IdentifierOrDot.Ident
)
|> Seq.toList
if ll.Length > 0 then
IdentListNode(ll, Range.Zero) |> Some
else
None
let makeModuleOrNamespaceHeaderNode (nameSpaceString: string) =
let ns = nameSpaceString.ToLower().Trim().Split([|'.'|])
if ns.Length > 0 then
ModuleOrNamespaceHeaderNode(
None,
None,
[ "namespace" ] |> toMultipleTexts,
None,
false,
ns |> toIdentList,
Range.Zero) |> Some
else
None
let makeNamespace (nameSpaceString: string) decls =
let nsOpt = makeModuleOrNamespaceHeaderNode nameSpaceString
Oak([],
[
ModuleOrNamespaceNode(nsOpt, decls, Range.Zero)
], Range.Zero)
module FantomasReader =
let extractNs (iln: IdentListNode) =
iln.Content
|> List.collect (fun iod ->
match iod with
| IdentifierOrDot.Ident(stn) -> [ stn.Text ]
| _ -> []
)
|> fun l ->
if l.Length > 0 then
String.Join('.',l) |> Some
else
None
// Define a function to extract the namespace from an Oak AST
let extractNsFromOak (oak: Oak) =
oak.ModulesOrNamespaces.Head.Header
|> Option.bind (
fun header ->
match header.LeadingKeyword.Content with
| [x] when x.Text = "namespace" ->
header.Name
|> Option.bind extractNs
| _ -> None
)
// Example usage
let oakAst = FantomasWriter.makeNamespace "hello.one.two" []
oakAst
|> FantomasReader.extractNsFromOak
|> Option.iter (printfn "NAMESPACE: %s")

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay