SExpressions 0.1.3
dotnet add package SExpressions --version 0.1.3
NuGet\Install-Package SExpressions -Version 0.1.3
<PackageReference Include="SExpressions" Version="0.1.3" />
<PackageVersion Include="SExpressions" Version="0.1.3" />
<PackageReference Include="SExpressions" />
paket add SExpressions --version 0.1.3
#r "nuget: SExpressions, 0.1.3"
#:package SExpressions@0.1.3
#addin nuget:?package=SExpressions&version=0.1.3
#tool nuget:?package=SExpressions&version=0.1.3
SExpressions
Read and write S-expression files in .NET without losing anything. Edit one value, save, and the rest of the file comes back byte for byte.
dotnet add package SExpressions
using SExpressions;
var doc = SDocument.Load("board.kicad_sch");
Console.WriteLine(doc.Find("kicad_sch/version")?.GetValueAsInt()); // 20250114
doc.Find("kicad_sch/uuid")!.SetValue(0, "00000000-0000-0000-0000-000000000001");
doc.Save("board.kicad_sch"); // only those 31 characters changed
No dependencies, net10.0.
Why not one of the others
Most S-expression libraries know a schema. When they meet a token they were never taught, they drop it — quietly, on save.
For KiCad files that's not cosmetic. A parser that doesn't know exclude_from_sim writes back a
schematic with the simulation exclusions gone, and nothing anywhere says so. We hit this with
kiutils; a single 145 KB schematic lost 89 exclude_from_sim tokens, 122 do_not_autoplace and
all 19 embedded_fonts.
This library has no schema, so there's nothing for it to not recognise. Quoting style, comments, source order and whitespace all survive the round trip.
That makes it a good fit for KiCad, Guile/Scheme config, and anything else where you need to change one thing and leave the file alone.
Usage
Read a value
var doc = SDocument.Load("board.kicad_pcb");
double thickness = doc.Find("kicad_pcb/general/thickness")!.GetValueAsDouble();
Walk the tree
foreach (var prop in doc.Forms.SelectMany(f => f.Descendants("property")))
Console.WriteLine($"{prop.GetValueAsString(0)} = {prop.GetValueAsString(1)}");
Add something
var net = new SExpression("net");
net.AddValue(42);
net.AddValue("GND", SQuoteStyle.Quoted);
doc.Forms[0].Add(net);
doc.Save("board.kicad_pcb");
Check a file parses, without loading it twice
if (!SExpressionParser.TryParseAll(File.ReadAllText(path), out var forms, out var error))
Console.Error.WriteLine(error);
There's a CLI too, in KiCadSharp:
dotnet tool install --global KiCadSharp.Cli
kicadsharp fmt board.kicad_sch --in-place
The model, briefly
| Type | What it is |
|---|---|
SDocument |
A whole file: every top-level form, plus the whitespace and comments between them. |
SExpression |
One form — a token and its items. |
SItem |
One item of a form, in source order: an atom, a child form, or a comment. |
SQuoteStyle |
Whether an atom was quoted. Parsed atoms remember; the writer never guesses. |
Values and children live in one ordered list, which is why (a 1 (b) 2) comes back as written
instead of reordered into values-then-children.
Full API reference: docs/api.md.
Performance
Roughly 65 MB/s parsing on a 145 KB KiCad schematic, and a save touches only the bytes that changed.
For a document you read once and throw away, SExpressionReader — a ref struct over
ReadOnlySpan<char>, the Utf8JsonReader pattern — skips the tree entirely: counting every symbol
across 1.59 MB of schematics costs zero bytes against 14.8 MB through the tree, and half the
time. It is read-only; editing still goes through the tree, which is what the byte-identical write is
built on.
Numbers, methodology and the BenchmarkDotNet project: docs/benchmarks.md.
Limits
It parses S-expressions, not Lisp. No reader macros, no dotted pairs, no #|block comments|#, no
evaluation. Block comments and a few other edge cases are listed in
docs/limits.md — worth a look before you point it at a format that isn't KiCad's.
Contributing
Issues and pull requests welcome. dotnet test runs the suite, including a corpus round-trip check
over real KiCad files.
License
MIT — see LICENSE.
Extracted from kicad-ultra, where it was called
SExpressionSharp. Renamed before the first NuGet push, since a package ID is permanent.
| Product | Versions 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. |
-
net10.0
- No dependencies.
NuGet packages (1)
Showing the top 1 NuGet packages that depend on SExpressions:
| Package | Downloads |
|---|---|
|
KiCadSharp
A .NET client for KiCad. Talks to a running KiCad over its nng IPC API to read and edit boards and project settings, and reads the on-disk s-expression symbol and footprint formats through SExpressions. The schematic side of KiCad's IPC API does not answer on KiCad 10; see the README. |
GitHub repositories
This package is not used by any popular GitHub repositories.