Firethorn 1.2.0

dotnet add package Firethorn --version 1.2.0
                    
NuGet\Install-Package Firethorn -Version 1.2.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Firethorn" Version="1.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Firethorn" Version="1.2.0" />
                    
Directory.Packages.props
<PackageReference Include="Firethorn" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Firethorn --version 1.2.0
                    
#r "nuget: Firethorn, 1.2.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Firethorn@1.2.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Firethorn&version=1.2.0
                    
Install as a Cake Addin
#tool nuget:?package=Firethorn&version=1.2.0
                    
Install as a Cake Tool

Firethorn

CI NuGet

Red-Green syntax trees for F#. Inspired by Rowan.

Design Goals

  • Compact representation of trees.
  • Complete representation of the input text.
  • Trees should be cheaply updateable.
  • Handle partial data and errors.
  • Query nodes in the tree by their location in the source.

Architecture

There are two main layers to the syntax trees supported by Firethorn. The Green tree represents abstract syntactic data; the Red or Syntax tree attaches location and parent information to green tree nodes.

Trees which share structure can share portions of their green tree. e.g. in 1 + 1 the node for 1 may be the same green node. The red nodes however would differ as the locations within the tree are different.

A simplified definition of the green tree contains the following:

type GreenToken =
	{ Kind: SyntaxKind
	  Text: string }
and GreenNode =
	{ Kind: SyntaxKind
	  Width: int
	  Children: Choice<GreenNode, GreenToken> list }

In the green tree each node has no specific location, only a width. The width of a token is the width of the text it contains. The with of nodes is cached on the node. It is the sum of the widths of its children.

The red tree is the a layer over this tree similar to the following:

type SyntaxToken =
	{ Offset: int
	  Parent: SyntaxNode option
	  Green: GreenToken }
and SyntaxNode =
	{ Offset: int
	  Parent: SyntaxNode option
	  Green: GreenNode }

This adds absolute offsets of the start of each node. This means a green node on its own has no specific location. It only has a location derived from the offset of its parent red node.

AST Layer

On top of this low-level untyped tree we can then layer a typed AST. This is done by having cast methods for each node type that take the underlying SyntaxNode and provide a typed wrapper. An example for a simplified binary expression node:

type OperatorSyntax(syntax: SyntaxToken) =
	
	static member Cast(node: SyntaxNode) =
		if node.Kind = SyntaxKind PLUS then
			Some(OperatorSyntax(node))
		else
			None

type ConstantSyntax(syntax: SyntaxNode) =

	static member Cast(node: SyntaxNode) =
		if node.Kind = SyntaxKind CONST then
			Some(ConstantSyntax(node))
		else
			None

and BinarySyntax(syntax: SyntaxNode) =
	
	static member Cast(node: SyntaxNode) =
		if node.Kind = SyntaxKind BINEXPR then
			Some(BinarySyntax(node))
		else
			None

	member _.Left =
		syntax.Children()
		|> Seq.choose ExpressionSyntax.Cast
		|> Seq.tryHead

	member _.Operator =
		syntax.ChildrenWithTokens()
		|> Seq.tryPick(
			NodeOrToken.asToken
			>> (Option.bind OperatorSyntax.Cast)
		)
	
	member _.Right =
		syntax.Children()
		|> Seq.choose ExpressionSyntax.Cast
		|> Seq.skip 1
		|> Seq.tryHead

and ExpressionSyntax =
	| Bin of BinarySyntax
	| Const of ConstantSyntax
	
	static member Cast(node: SyntaxNode) =
		(BinarySyntax.Cast node |> Option.map Bin)
		|> Option.orElseWith (fun () -> ConstantSyntax.Cast |> Option.map Const)
		

In this model we have strongly typed tree structure defined by a union type ExpressionSyntax. Each node type in the tree can expose the important parts of that node with strongly typed properties. All properties return either Option or Seq values to model the fact that any part of the tree could be missing or empty.

Building Parse Trees

Trees can be built from the bottom up by repeatedly calling GreenToken.Create and GreenNode.Create. The final node can then be converted into a red tree with the SyntaxNode.CreateRoot method. This allows for ergonomic building of syntax during testing, and provides a simple API for programatically generated syntax such as macro expansion. For some simple languages and parsers this may be enough.

A higher level API is also provided to build nodes using GreenNodeBuilder. This type allows for incremental building of nodes and is particularly suited to top-down parser construction:

let builder = GreenNodeBuilder()
builder.StartNode(SytaxKind BINOP)
builder.Token(SyntaxKind OP, "+")
builder.Token(SyntaxKind NUM, "12")
builder.Token(SyntaxKind NUM, "45")
builder.FinishNode()

For cases where it isn't known ahead of time if a node will be needed the Mark API allows a point to be 'marked' and applied later to create a node. This would be equivalent to the above:

let mark = builder.Mark()
builder.Token(SyntaxKind OP, "+")
builder.Token(SyntaxKind NUM, "12")
builder.Token(SyntaxKind NUM, "45")
builder.ApplyMark(mark, SytaxKind BINOP)

Once a tee is built it can be converted into a single root node with the BuildRoot API:

let syntaxRoot = builder.BuildRoot(SyntaxKind PROGRAM)
Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on Firethorn:

Package Downloads
Feersum.CompilerServices

This package contains the compiler tooling used by the main Feersum compiler. Feersum Scheme is a Scheme implementation that compiles to .NET. Feersum provides a command line tool for batch compliation, a Scheme REPL, and a .NET SDK for MSBuild support.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.2.0 125 4/30/2026
1.1.0 113 4/29/2026
1.0.2 118 4/28/2026
1.0.1 143 4/28/2026
1.0.0 111 4/28/2026
0.4.1 1,017 6/23/2024
0.4.0 510 6/15/2023
0.3.3 289 6/15/2023
0.3.2 300 6/10/2023
0.3.1 820 5/1/2022
0.3.0 558 5/1/2022
0.2.0 459 12/28/2021
0.1.0 5,375 11/24/2021