Tomlyn provides public low-level building blocks for tooling, analyzers, formatters, and advanced scenarios. These APIs live in Tomlyn.Parsing and Tomlyn.Syntax.

API Purpose Allocations
TomlLexer Token stream (syntax highlighting, validation) Minimal - struct-based iteration
TomlParser Incremental semantic events (custom readers) Minimal - no intermediate DOM
SyntaxParser Full-fidelity syntax tree (round-tripping, tooling) Allocates tree nodes

TomlLexer (tokenization)

TomlLexer produces a flat stream of tokens. Use it for syntax highlighting, quick validation, or building custom parsers.

using Tomlyn.Parsing;

var lexer = TomlLexer.Create("name = \"Ada\"", sourceName: "config.toml");
while (lexer.MoveNext())
{
    var token = lexer.Current;
    Console.WriteLine($"{token.Kind} = {token.StringValue} @ {lexer.CurrentSpan}");
}

Token kinds

Tokens are identified by TokenKind:

Category Kinds
Structure OpenBracket, CloseBracket, OpenBracketDouble, CloseBracketDouble, OpenBrace, CloseBrace, Equal, Comma, Dot
Strings String, StringMulti, StringLiteral, StringLiteralMulti
Numbers Integer, IntegerHexa, IntegerOctal, IntegerBinary, Float
Booleans True, False
Date/time OffsetDateTimeByZ, OffsetDateTimeByNumber, LocalDateTime, LocalDate, LocalTime
Special Infinite, PositiveInfinite, NegativeInfinite, Nan, PositiveNan, NegativeNan
Trivia Whitespaces, NewLine, Comment
Meta BasicKey, Invalid, Eof

Lexer options

TomlLexerOptions controls decoding behavior:

var options = new TomlLexerOptions { DecodeScalars = true };
var lexer = TomlLexer.Create(toml, options, sourceName: "config.toml");

When DecodeScalars is true, escape sequences in strings are decoded during tokenization.

TomlParser (incremental parse events)

TomlParser produces a sequence of TomlParseEvent values via MoveNext() without building an intermediate DOM. This is ideal for building custom readers or streaming processors.

using Tomlyn.Parsing;

var parser = TomlParser.Create("[server]\nhost = \"localhost\"\nport = 8080");
while (parser.MoveNext())
{
    ref readonly var evt = ref parser.Current;
    switch (evt.Kind)
    {
        case TomlParseEventKind.PropertyName:
            Console.Write($"{evt.GetString()} = ");
            break;
        case TomlParseEventKind.String:
            Console.WriteLine($"\"{evt.GetString()}\"");
            break;
        case TomlParseEventKind.Integer:
            Console.WriteLine(evt.GetInt64());
            break;
        case TomlParseEventKind.StartTable:
            Console.WriteLine($"[table depth={parser.Depth}]");
            break;
    }
}

Parse events

Kind Description
StartDocument / EndDocument Document boundaries.
StartTable / EndTable Table boundaries (including inline tables).
StartArray / EndArray Array boundaries (including table arrays).
PropertyName A TOML key - use evt.GetString() to read it.
String A string value - use evt.GetString().
Integer An integer value - use evt.GetInt64().
Float A float value - use evt.GetDouble().
Boolean A boolean value - use evt.GetBoolean().
DateTime A date/time value - use evt.GetTomlDateTime().

Parser options

TomlParserOptions controls parsing behavior:

var parserOptions = new TomlParserOptions
{
    Mode = TomlParserMode.Tolerant,   // collect errors instead of throwing
    DecodeScalars = true,             // decode escape sequences
    CaptureTrivia = true,             // include comments/whitespace in events
    EagerStringValues = false,        // defer string materialization
};

var serializerOptions = new TomlSerializerOptions
{
    MaxDepth = 64,
};

var parser = TomlParser.Create(toml, parserOptions, serializerOptions);
Option Default Description
Mode Strict Strict throws on first error; Tolerant collects diagnostics.
DecodeScalars false Decode escape sequences in strings.
CaptureTrivia false Include trivia tokens (comments, whitespace).
EagerStringValues false Materialize string values eagerly.

Shared limits such as maximum nesting depth are configured via TomlSerializerOptions.MaxDepth and flow through low-level APIs like TomlParser, TomlReader, TomlWriter, and the overloads on SyntaxParser that accept TomlSerializerOptions.

Input sources

TomlParser.Create(...) accepts string and TextReader:

// From a file stream
using var reader = new StreamReader("config.toml");
var parser = TomlParser.Create(reader);

SyntaxParser (full-fidelity syntax tree)

SyntaxParser builds a complete DocumentSyntax tree that preserves every character from the original input - comments, whitespace, formatting. This is the right tool for formatters, analyzers, linters, and round-trip editing.

using Tomlyn.Parsing;

var doc = SyntaxParser.Parse("# config\nname = \"Ada\"\nage = 37", sourceName: "config.toml");

if (doc.HasErrors)
{
    foreach (var diag in doc.Diagnostics)
        Console.WriteLine(diag);
}

// Round-trip: ToString() reproduces the exact original text
Console.WriteLine(doc.ToString());

Parse vs ParseStrict

Method Behavior
SyntaxParser.Parse(...) Tolerant - collects errors in doc.Diagnostics, always returns a tree.
SyntaxParser.ParseStrict(...) Strict - throws TomlException on the first error.

Both accept string and TomlLexer inputs. Parse also accepts TextReader.

Syntax tree structure

The tree root is DocumentSyntax, which contains:

  • KeyValues - top-level key-value pairs (SyntaxList<KeyValueSyntax>)
  • Tables - table and table-array sections (SyntaxList<TableSyntaxBase>)
  • Diagnostics - parse errors/warnings (DiagnosticsBag)
  • HasErrors - quick check for errors

Key node types:

Node Description
KeyValueSyntax A key = value pair.
KeySyntax The key part (may be dotted).
StringValueSyntax A string value (basic, literal, multiline).
IntegerValueSyntax An integer value.
FloatValueSyntax A float value.
BooleanValueSyntax A boolean value.
DateTimeValueSyntax A date/time value.
ArraySyntax An array [...].
InlineTableSyntax An inline table {...}.
TableSyntax A [table] header section.
TableArraySyntax A [[table_array]] header section.

Trivia

Every SyntaxNode can carry LeadingTrivia and TrailingTrivia - lists of SyntaxTrivia containing comments, whitespace, and newlines.

Tree traversal

Use the visitor pattern or enumerable extensions:

using Tomlyn.Syntax;

// Visitor pattern
var visitor = new MyVisitor();
visitor.Visit(doc);

// Enumerable extensions
foreach (var node in doc.Descendants())
{
    if (node is StringValueSyntax str)
        Console.WriteLine(str.Value);
}

foreach (var token in doc.Tokens(includeCommentsAndWhitespaces: true))
{
    // Process every token
}

To implement a visitor, inherit from SyntaxVisitor and override the Visit methods for the node types you care about:

using Tomlyn.Syntax;

public sealed class StringCollector : SyntaxVisitor
{
    public List<string> Strings { get; } = new();

    protected override void Visit(StringValueSyntax node)
    {
        if (node.Value is not null)
            Strings.Add(node.Value);
        base.Visit(node);
    }
}

var collector = new StringCollector();
collector.Visit(doc);

Diagnostics

DiagnosticsBag is a collection of DiagnosticMessage items:

foreach (var diag in doc.Diagnostics)
{
    // diag.Kind    - Error or Warning
    // diag.Span    - source location
    // diag.Message - description
    Console.WriteLine($"{diag.Kind}: {diag.Message} at {diag.Span}");
}