Segno

Library

Turn a short typed instruction into structured data, safely.

Below is a feature-flag rule, {new-checkout, beta, 25%, [eu, us], sticky}, read into a typed value. Rearrange everything after the flag's name: the result stays the same, because each word finds the one field that takes it. Then type a word that doesn't belong, like mars, and Segno points at it and says what it expected.

Typed values, in any order

Pick a kind of rule, then type. The same notation means different things in different places: each place has a schema that says which fields it takes, and every error names what was expected.

Lattice's slots
Reads as
Bound value

Aliases, shortcuts, and one spelling per document

[x], {done} and {yes} all mean the same state. That is kind per span and messy per document, so Segno reports every span written differently from the document's most common spelling — with a fix. One state mark per line:

Try to make it slow

These are the inputs that took other parser libraries from one second to over sixteen, at 2k, 8k and 32k characters. Each step is 4 times longer, so steady parsing takes about 4 times as long; a parser that blows up takes 16 times as long or more.

Build your own parser experimental

Underneath the notation is a grammar engine. Segno only builds a parser it can prove reads in one pass, and says no to a grammar that would need to guess. A grammar is a few rules built from seq, alt, many, opt, ref and character sets; node marks the pieces you want back. Edit the grammar or the text: it compiles and parses as you type. Pick a refused grammar to see the compiler say no.

Parse tree
The same grammar as generated code

What generate(grammar) writes: straight-line TypeScript, no library at runtime. The notation ships this way.

First user: Lattice

Lattice renders slide decks from Markdown, and its inline code spans carry directives. It had grown 27 hand-written grammars for them, with 21 sigils between them; it is moving every one onto this notation. Some of them, beside what each becomes — try any row in the editor above.

How it compares

Segno beside the six parser libraries its first user evaluated, and beside the hand-written parsers it replaces — every candidate in its own process, on identical inputs, on one machine. Time per input on real spans, then the worst case on hostile input.

ParserStyleBracket listInline spanWorst hostile case, 32k chars
SegnoLL(1), proven at compile time734 ns45 ns7 ms
Hand-written (Lattice)recursive descent, by hand942 ns33 ns7 ms
PeggyPEG6,577 ns882 ns7.3 s
ChevrotainLL(k), tokens4,723 ns24,605 ns776 ms
ParsimmonPEG combinators54,267 ns1,073 nsdid not finish
NearleyEarley66,000 ns12,644 nsdid not finish
OhmPEG, memoized107,161 ns24,597 nsdid not finish
LezerLR—8,670 ns—

Measured 2026-09-29, Node 22, on a shared cloud machine: absolute times move with the machine, the ratios carry. Bracket lists: the 64 in Lattice's decks, parsed into parts. Inline spans: all 4,645, 99% of them ordinary code. "Did not finish": a smaller input already took over half a second, so the next was stopped. Lezer's grammar set does not cover bracket lists. Segno's flat best case on nested brackets is its 31-level cap refusing the input, not a parse. Reproduce: npm run parser:bakeoff:versus.

This page runs the real library in your browser. Segno is Italian for sign or mark. Learn how Segno works, and how to use it →