EdnParser 0.3.0

dotnet add package EdnParser --version 0.3.0
NuGet\Install-Package EdnParser -Version 0.3.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="EdnParser" Version="0.3.0" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add EdnParser --version 0.3.0
#r "nuget: EdnParser, 0.3.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.
// Install EdnParser as a Cake Addin
#addin nuget:?package=EdnParser&version=0.3.0

// Install EdnParser as a Cake Tool
#tool nuget:?package=EdnParser&version=0.3.0

Extensible Data Notation Parser for C#

Build Status Codacy Badge

This is a .Net Core 2.0 implementation of an Extensible Data Notation (EDN) parser. It currently does not do EDN generation.

Quickstart

Create a new parser with Parser parser = new Parser(); and then pass in your EDN string to the Parse function.

using EdnParser;
using System.Linq;

...

String edn = "[db/connect {:host env/server :port env/port :user env/user :password env/password}]\n[db/list :tables]";
Parser parser = new Parser();
var parsed = parser.Parse(edn);
foreach (var val in parsed)
{
    foreach (var elem in val)
    {
        Console.Write(elem);
        Console.Write(" ");
    }
    Console.WriteLine();
}

Output:

db/connect {:host env/server, :port env/port, :user env/user, :password env/password}
db/list :tables

Parsing

Examples for all the parsing methods discussed here can be found in the ExampleProject.

There are three main ways to parse with this library:

  • Fully parse an EDN string
  • Partially parse an EDN string
  • Parse an EDN stream

Fully Parsing

When fully parsing, the parser assumes that you are providing a complete EDN string and will throw an error if the string is not a valid EDN string. It will then return a list of all EDN tokens back to you that you can handle.

This is done by calling Parse on a parser.

Usage:

String edn = "[db/connect {:host env/server :port env/port :user env/user :password env/password}]\n[db/list :tables]";
Parser parser = new Parser();
var parsed = parser.Parse(edn);
foreach (var val in parsed)
{
    foreach (var elem in val)
    {
        Console.Write(elem);
        Console.Write(" ");
    }
    Console.WriteLine();
}

Partially Parsing

When partially parsing, the parser assumes that the string is incomplete so part of it may be invalid. It will then do a greedy parse of the string to get as many valid tokens as possible and then assumes the rest of the string is simple incomplete. It will return a PartialParse which has the properties ParsedTokens (a list of parsed tokens) and Unparsed (a string of what could not be parsed).

Please note that since this is greedy, it will try to match whatever it is given even if it doesn't accurately represent the full EDN data. This means that if you have the string "1 4\ntrue false" but break it into the pieces "1 4\n tr" and "ue false" and ran it through the partial parsing, you would get the integers 1,4 and the symbol "tr" for the first half and the symbols "ue" and "false" for the second half with no unparsed strings. For this reason, it's recommended that you split your code up on some form of whitespace, such as newlines.

You can do a partial parse by calling ParseAsMuchAsPossible on a parser.

Usage:

Parser parser = new Parser();
String edn = "[db/connect \n{:host env/server :port env/port\n :user env/user :password env/password}]\n[db/list :tables]\n[unparsable";
var lines = edn.Split("\n");
var input = "";

foreach(var line in lines)
{
    var parsed = parser.ParseAsMuchAsPossible(input + line);
    foreach (var val in parsed.ParsedTokens)
    {
        foreach (var elem in val)
        {
            Console.Write(elem);
            Console.Write(" ");
        }
        Console.WriteLine();
    }
    input = parsed.Unparsed;
}
Console.WriteLine("Unparsable: " + input);

Output:

db/connect {:host env/server, :port env/port, :user env/user, :password env/password}
db/list :tables
Unparsable: [unparsable

Parse a Stream

The parser also gives you the option to parse a stream. This option is great if you have data that is too big to fit entirely in memory or if you want to process data as it comes in. This parsing mode is different than the others in that it doesn't aggregate everything into a single list but it instead calls a function every time it finds a complete token. The reason for this is that it is designed to be used for when you either don't have enough memory to fit your data, so it can't create a list that would fit in memory, or you need to process data as it comes in (say, from a WebSocket) so you don't want to wait until everything is done transmitting. The stream parser works with an internal buffer and carries over any unparsable input from the last time it pulled. It won't parse a token unless it is followed by some sort of delimiter (white space, starting bracket, etc) which means it will properly parse "5 tr|ue false" as the integer 5, followed by the booleans true and false.

Parsing by a stream is done by calling StreamParse or StreamParseAsync on a parser. StreamParse will synchronously parse a stream in the current thread while StreamParseAsync will launch a Task that will run the parsing. If either of these parsers encounter an exception while parsing (such as the stream closing prematurely) then it will call the error handler you provide (if present) and return.

Both of these functions take in required arguments of the StreamReader to use for parsing and an Action that will be called every time a new token is processed. They optionally take in an unparsed handler which is called after the stream is empty if there is any unparsed input; an error handler function which will be called with the current error and the last unprocessed input (not including what is in the buffer); the maximum unprocessed string length limit (by default there no limit), and the buffer size (defaults to 2048).

If you provide a maximum unprocessed string length limit and the parser exceeds that limit, then it will call your error handler with an InvalidDataException and quit parsing.

Usage:

Parser parser = new Parser();
using (StreamReader sr = new StreamReader(stream, Encoding.UTF8)) {
    Action<EdnValue> processEdnToken = (EdnValue val) =>
    {
        foreach (var elem in val)
        {
            Console.Write(elem);
            Console.Write(" ");
        }
        Console.WriteLine();
    };
    Action<String> handleUnprocessedInput = (String unprocessed) => Console.WriteLine("Unparsable: " + unprocessed);
    task = parser.StreamParseAsync(sr, processEdnToken, handleUnprocessedInput);
    task.Wait();
}

Generating Strings

To generate a string from EDN values, use the Generator static class' function CreateStringFrom. Below is an example:

// from a single value
string res = Generator.CreateStringFrom(
    new EdnVector((new EdnValue[] {
        new EdnInteger(0),
        new EdnInteger(1),
        new EdnFloat(2.3),
        new EdnKeyword(new EdnParser.Values.Keyword("hello"))
    })
));
// res = "[0 1 2.3 :hello]"

To do it for a list of values:

// from a list of values
string res = Generator.CreateStringFrom(
    (new EdnValue[] {
        new EdnInteger(0),
        new EdnInteger(1),
        new EdnFloat(2.3),
        new EdnKeyword(new EdnParser.Values.Keyword("hello"))
    }).ToList()
);
// res = "0\n1\n2.3\n:hello"

To include Windows-style carriage returns:

Generator.CreateStringFrom(
    (new EdnValue[] {
        new EdnInteger(0),
        new EdnInteger(1),
        new EdnFloat(2.3),
        new EdnKeyword(new EdnParser.Values.Keyword("hello"))
    }).ToList(),
    true
);
// res = "0\r\n1\r\n2.3\r\n:hello"
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp2.0 is compatible.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETCoreApp 2.0

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
0.3.0 1,026 8/19/2018
0.2.0 798 8/17/2018
0.1.0 843 8/8/2018

Changed Generator CreateStringFrom to use method overriding instead of default parameters.

Made abstract constuctors protected.

Fixed potential bug where EdnFloat and EdnInteger could return different string values depending on Locale settings.

Added optional carraige return parameter to EdnComment.ToString().

Added IEquatable interface many classes.

Changed EdnValue.Name to more consistently represent the string output of EdnValue interfaces.

Fixed EdnComment.ToString() to return a proper representation of a comment.

Fixed EdnMap equality to check that both maps have all the keys of the other map.

Made EdnValue.Name protected.

Changed EdnValue Equal to check type and string representation.

EdnFloat and EdnInteger now check "precision" as specified by the EDN spec

Renamed EdnValue.Name to EdnValue.StringRepresentation.

For full list, see https://github.com/tofurama3000/ExtensibleDataNotationParser/blob/master/CHANGELOG.md.