Search Results for

    Links Notation Parser for C#

    C# implementation of the Links Notation parser using Pegasus parser generator and Platform.Collections.

    Installation

    Package Manager

    Install-Package Link.Foundation.Links.Notation
    

    .NET CLI

    dotnet add package Link.Foundation.Links.Notation
    

    PackageReference

    <PackageReference Include="Link.Foundation.Links.Notation" Version="0.9.0" />
    

    Build from Source

    Clone the repository and build:

    git clone https://github.com/link-foundation/links-notation.git
    cd links-notation/csharp
    dotnet build Link.Foundation.Links.Notation.sln
    

    Test

    Run tests:

    dotnet test
    

    Usage

    Basic Parsing

    using Link.Foundation.Links.Notation;
    
    // Create parser
    var parser = new Parser();
    
    // Parse Links Notation format string
    string input = @"papa (lovesMama: loves mama)
    son lovesMama
    daughter lovesMama
    all (love mama)";
    
    var links = parser.Parse(input);
    
    // Access parsed links
    foreach (var link in links)
    {
        Console.WriteLine(link.ToString());
    }
    

    Converting Back to String

    using Link.Foundation.Links.Notation;
    
    // Format links back to string
    string formatted = links.Format();
    Console.WriteLine(formatted);
    

    Working with Links

    // Create link programmatically
    var link = new Link<string>("id", new[] { "value1", "value2" });
    
    // Access link properties
    Console.WriteLine($"ID: {link.Id}");
    foreach (var value in link.Values)
    {
        Console.WriteLine($"Value: {value}");
    }
    

    Advanced Usage with Generic Types

    // Using numeric link addresses
    var parser = new Parser<ulong>();
    var numericLinks = parser.Parse("(1: 2 3)");
    
    // Working with custom address types
    var customParser = new Parser<Guid>();
    

    Streaming Parsing

    StreamParser accepts arbitrary chunks and raises LinkParsed only for complete top-level records. Disable collection for bounded-memory event use.

    var stream = new StreamParser { Collect = false };
    stream.LinkParsed += Console.WriteLine;
    stream.Write("profile:\n  name Ada\n");
    stream.Finish("next link");
    

    ParseChunks and ParseChunksAsync expose native enumerable adapters. The parser also provides Position, Drain, Reset, and MaxBufferSize.

    Syntax Examples

    Doublets (2-tuple)

    papa (lovesMama: loves mama)
    son lovesMama
    daughter lovesMama
    all (love mama)
    

    Triplets (3-tuple)

    papa has car
    mama has house
    (papa and mama) are happy
    

    N-tuples with References

    (linksNotation: links notation)
    (This is a linksNotation as well)
    (linksNotation supports (unlimited number (of references) in each link))
    

    Multi-line Groups

    A parenthesized group opens a nested context: its body starts fresh at indentation level zero and follows the same rules as the root document, so a line break inside parentheses is structure rather than decoration.

    value (
      id "1"
      label "one"
    )
    

    The document above parses to (value ((id 1) (label one))) - two children, each a link of its own - rather than to one flat list in which the boundary between id and label would be lost. A body that stays on a single line still collapses to a single link, so (a b c) is unchanged.

    var links = new Parser().Parse(@"value (
      id ""1""
      label ""one""
    )");
    
    Console.WriteLine(links[0]); // (value ((id 1) (label one)))
    

    Comments

    A # hides the rest of the line it stands on, so a document can carry prose about itself:

    # the machines this deploys to
    deploy: staging # only staging, for now
    

    Both comments are gone by the time the document is read, leaving the single link (deploy: staging). A # only opens a comment where a reference could begin, so a # inside a token (issue#1047) and a # inside a delimited reference ("#") stay ordinary characters.

    A formatter keeps the same rule from the other side: a reference that begins with a # is written quoted ('#tag'), so a document it writes reads back as itself.

    Comments are on by default, and a parser can be told to read # as an ordinary character again, for documents written before comments existed:

    var links = new Parser().Parse("# the machines this deploys to\ndeploy: staging # only staging, for now\n");
    Console.WriteLine(links[0]); // (deploy: staging)
    
    var plain = new Parser(comments: false);
    Console.WriteLine(plain.Parse("# a b\n")[0]); // (# a b)
    

    API Reference

    Classes

    • Parser<TLinkAddress>: Main parser class for converting strings to links (new Parser(comments: false) reads # as an ordinary character)
    • Link<TLinkAddress>: Represents a single link with ID and values
    • LinksGroup<TLinkAddress>: Container for grouping related links
    • ParseException: Thrown when a document does not parse

    Error Handling

    Parse throws a ParseException whose message says where the document stopped making sense and quotes the offending line with a caret under it:

    try
    {
        new Parser().Parse("ci_gate x\nstage: rust: nextest\n");
    }
    catch (ParseException error)
    {
        Console.Error.WriteLine(error.Message);
        Console.Error.WriteLine($"{error.Line}:{error.Column} (offset {error.Offset})");
    }
    
    Syntax error at line 2, column 12: unexpected ":"
    2 | stage: rust: nextest
      |            ^
    

    ParseException derives from FormatException, so callers that already catch FormatException keep working, and carries Offset, Line, Column, Found, LineText, Summary and Snippet for callers that report errors themselves.

    Nesting Limit

    Parser.MaxDepth is how deep links may nest (default: Parser.DefaultMaxDepth, 64, the same in every implementation). Every parenthesized group and every indentation level is one level, and the lines of a document start at level 0, so with new Parser(comments: true, maxDepth: 1) (a) is accepted while ((a)), (a (b)) and a group on an indented line are refused. A document nested deeper is refused with a ParseException whose MaxDepth is set, rather than recursed into until the stack runs out, and which points at the group or the line that is one level too deep:

    Nesting too deep at line 1, column 4: nesting depth exceeds the maximum of 3
    1 | ((((a))))
      |    ^
    

    MaxDepth is null for any other error. StreamParser reports the same error as a StreamParseException whose ParseError is the ParseException and whose Line, Column and Offset are counted from the start of the stream.

    Extension Methods

    • IListExtensions.Format(): Converts list of links back to string format
    • ILinksGroupListExtensions: Additional operations for link groups

    Maintenance

    Linting and Formatting

    Check code formatting:

    dotnet format --verify-no-changes --verbosity diagnostic
    

    Auto-fix formatting:

    dotnet format
    

    Pre-commit Hooks

    This project uses pre-commit hooks. To set up pre-commit hooks locally:

    # From repository root
    pip install pre-commit
    pre-commit install
    

    Note: C# formatting checks are integrated into the CI pipeline using dotnet format.

    Dependencies

    • .NET 10.0
    • Microsoft.CSharp (4.7.0)
    • Pegasus (4.1.0)
    • Platform.Collections (0.3.2)

    Maintenance

    Code Formatting

    This project uses dotnet format for code formatting.

    Format all files

    dotnet format
    

    Check formatting (without modifying files)

    dotnet format --verify-no-changes
    

    These checks are also enforced in CI. Pull requests with formatting issues will fail the format check.

    Documentation

    For complete API documentation, visit: Link.Foundation.Links.Notation Documentation

    • Edit this page
    In this article
    Back to top Generated by DocFX