link-foundation / links-notation
Links Notation (lino) parser and formatter for PHP
Package info
github.com/link-foundation/links-notation
Language:JavaScript
pkg:composer/link-foundation/links-notation
Fund package maintenance!
Requires
- php: >=8.4
- ext-mbstring: *
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
- dev-main
- 0.31.3
- 0.31.2
- 0.31.1
- 0.31.0
- 0.30.2
- 0.30.1
- 0.30.0
- dev-issue-392-486a821ae7c0
- dev-fix/canonical-package-names
- dev-issue-192-0e457557ffbf
- dev-issue-380-e36f29acdbe1
- dev-issue-381-fdc9f650a507
- dev-issue-379-342d19981e8c
- dev-issue-377-e0fbca836336
- dev-issue-370-d53293a40128
- dev-issue-369-1299302aca67
- dev-issue-364-dd04c0e5845f
- dev-issue-350-08561c0480d4
- dev-issue-351-f39c9a31a186
- dev-issue-348-a1c0fc463277
- dev-issue-340-f295191f6144
- dev-issue-339-12b617cefd5b
This package is auto-updated.
Last update: 2026-11-10 23:35:48 UTC
README
| JavaScript | ||
|---|---|---|
| Rust | ||
| C# | ||
| Python | ||
| Go | ||
| Java | ||
| PHP | ||
| C++ | ||
| Swift | ||
| Kotlin |
Link Foundation's Links Notation Class Library.
Development starts in JavaScript and is checked against all ten hand-written bindings. See contributing, the parity contract, and release setup. All language CI now uses the same entry workflow.
This library gives you an ability to convert any string that contains links notation into a list of links and back to the string after modifications are made.
Links notation is based on two concepts references and links. Each reference references other link. The notation supports links with any number of references to other links.
Quick Start
C#
var parser = new Link.Foundation.Links.Notation.Parser(); var links = parser.Parse("papa (lovesMama: loves mama)");
JavaScript
import { Parser } from "links-notation"; const parser = new Parser(); const links = parser.parse("papa (lovesMama: loves mama)");
Rust
use links_notation::parse_lino; let links = parse_lino("papa (lovesMama: loves mama)").unwrap();
Python
from links_notation import Parser parser = Parser() links = parser.parse("papa (lovesMama: loves mama)")
Go
import lino "github.com/link-foundation/links-notation/go" links, _ := lino.Parse("papa (lovesMama: loves mama)")
Java
import io.github.linkfoundation.linksnotation.Parser; Parser parser = new Parser(); List<Link> links = parser.parse("papa (lovesMama: loves mama)");
PHP
use LinkFoundation\LinksNotation\Parser; $parser = new Parser(); $links = $parser->parse("papa (lovesMama: loves mama)");
C++
#include "links_notation.hpp" std::vector<links_notation::Link> links = links_notation::parse("papa (lovesMama: loves mama)");
Swift
import LinksNotation let links = try Parser().parse("papa (lovesMama: loves mama)")
Kotlin
import io.github.linkfoundation.linksnotation.Parser val links = Parser().parse("papa (lovesMama: loves mama)")
Examples
Links notation (lino)
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
Sequences (N-tuple)
I'm a friendly AI.
(I'm a friendly AI too.)
(linksNotation: links notation)
(This is a linksNotation as well)
(linksNotation supports (unlimited number (of references) in each link))
(sequence (of references) surrounded by parentheses is a link)
parentheses may be ommitted if the whole line is a single link
Indented Syntax
Links can also use indented syntax for better readability:
3:
papa
loves
mama
This is equivalent to:
(3: papa loves mama)
For indented IDs, Parser.parse('x:\n a\n b') reads (x: a b).
parse defines the canonical link values. In JavaScript, C++, Swift and Kotlin,
parseGroups exposes the indentation separately: the x: element is a self
reference with an empty value list, and its children are references a and b.
Folding those children into the element's values gives the same link as parse.
Explicit (a) remains a one-value link. Under an ordinary head such as
x: kind, child lines remain links and parse expands indentation into paths.
See the JavaScript folding example.
Multi-line Groups
A parenthesized group opens a nested context: its body starts fresh at indentation level zero and follows the same rules the root document does, so a line break inside parentheses is structure rather than decoration.
value (
id "1"
label "one"
)
This reads as (value ((id 1) (label one))) - two children, each a link of its
own - and not as one flat list in which the boundary between id and label
would be lost. Indentation works inside a group exactly as it does at the root,
and a body that stays on a single line still collapses to a single link, so
(a b c) is unchanged.
All ten implementations agree on this: each native test suite reads the
document above as the shared context case of
parity/text-fixtures.json, and
experiments/issue-282/parity parses it with the eight earlier ones side by
side and fails if any one reads it differently.
The complete rules are in the grammar.
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
That is the single link (deploy: staging): both comments are gone by the time
the document is read. 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 in every implementation, and every parser can be
told to read # as an ordinary character again for documents written before
comments existed - see the parser options in the language READMEs.
So that means that this text is also links notation. So most of the text in the world already may be parsed as links notation. That makes links notation the most easy an natural/intuitive/native one.
What is Links Notation?
Links Notation (Lino) is a simple, intuitive format for representing
structured data as links between entities references to links.
It's designed to be:
- Natural: Most text can already be parsed as links notation
- Flexible: Supports any number of references in each link
- Universal: Can represent doublets, triplets, and N-tuples
- Hierarchical: Supports nested structures with indentation
The notation uses two core concepts:
- References: Points to other links (like variables or identifiers)
- Links: Connect references together with optional identifiers
Streaming
Every maintained implementation can parse arbitrary chunks incrementally, including one-symbol and one-line chunks. A stream emits a top-level record only after its indented children, multiline quoted references, and parenthesized contexts are complete. The canonical parser validates each completed record, so streaming and whole-document parsing have the same syntax and output.
| Language | Push API | Lazy/native adapter |
|---|---|---|
| C# | LinkParsed event |
IEnumerable / IAsyncEnumerable |
| JavaScript | link, error, and end events |
sync / async iterables |
| Rust | link and error callbacks | Iterator |
| Python | link callback | generator / async generator |
| Go | callback and io.Writer |
iter.Seq2 |
| Java | Consumer<Link> |
Stream<Link> |
| PHP | callable | Generator |
| C++ | on_link / on_error callbacks |
ChunkedLinks / parse_chunks |
| Swift | onLink / onError handlers |
Sequence / AsyncSequence |
| Kotlin | onLink, onError and onEnd listeners |
Sequence via StreamParser.parse |
All variants expose write/feed and finish operations, absolute position tracking, reset/drain controls, and an unresolved-record size limit. Collection is enabled by default; disable it when callbacks or lazy iteration should keep memory bounded. See the cross-language parity experiment and each language README for runnable examples.
How a document was written
1 and '1' are the same reference, so equality and formatting treat
(t 1 '1') and (t 1 1) alike. The parser still records how each ID was
written: every parsed link with an ID carries its raw text ('1') and its
quote ('). This metadata is on by default; an AST mode leaves it out. For the
whole text, every implementation also returns a lossless concrete syntax tree
(CST): each character, comments, whitespace, blank lines and line endings
included, belongs to exactly one token, so the tree's text is the document.
| Language | Raw text and quote | AST mode | CST |
|---|---|---|---|
| C# | RawId, Quote |
new Parser { Syntax = false } |
ParseCst |
| JavaScript | rawId, quote |
new Parser({ syntax: false }) |
parseCst |
| Rust | parse_lino_to_syntax_links → raw_id(), quote() |
parse_lino |
parse_lino_to_cst |
| Python | raw_id, quote |
Parser(syntax=False) |
parse_cst |
| Go | RawID, Quote() |
Parser.Syntax = false |
ParseCst |
| Java | getRawId(), getQuote() |
new Parser(comments, false) |
parseCst |
| PHP | getRawId(), getQuote() |
new Parser(syntax: false) |
parseCst |
| C++ | raw_id, quote() |
ParserOptions::syntax = false |
parse_cst |
| Swift | rawId, quote |
ParserOptions(syntax: false) |
parseCst |
| Kotlin | rawId, quote |
Parser(ParserOptions(syntax = false)) |
parseCst |
Every implementation checks the shared golden data in
parity/syntax-fixtures.json and restores every
shared fixture exactly from its CST. See the
issue 364 case study.
Documentation
For detailed implementation guides and API references, see the language-specific documentation:
- C# Documentation - Complete API reference
- PDF Documentation - Complete reference for offline reading
- C# README - Installation and usage guide
- JavaScript README - Modern web development guide
- Rust README - High-performance parsing guide
- Python README - Python package guide
- Go README - Go package guide
- Java README - Java/Maven package guide
- PHP README - PHP/Composer package guide
- C++ README - C++17/CMake library guide
- Swift README - Swift package guide
- Kotlin README - Kotlin Multiplatform/Gradle library guide
Additional resources:
- Grammar - The notation in EBNF, with the indentation and nested context rules spelled out (syntax diagrams)
- Test Case Comparison - Test-by-test coverage comparison across all ten language implementations
- Links Theory 0.0.2 - Theoretical foundation that Links Notation fully supports
Test Coverage & Implementation Parity
All ten language implementations (C#, JavaScript, Rust, Python, Go, Java, PHP, C++, Swift, Kotlin) maintain equivalent core functionality, and the overlap is verified test by test rather than asserted:
| Language | Tests | Test categories |
|---|---|---|
| Python | 279 | 27 |
| JavaScript | 351 | 34 |
| Rust | 430 | 32 |
| C# | 325 | 30 |
| Go | 167 | 20 |
| Java | 214 | 19 |
| PHP | 265 | 26 |
| C++ | 380 | 32 |
| Swift | 349 | 35 |
| Kotlin | 358 | 33 |
The table is written by scripts/create-test-case-comparison.mjs, which reads the test files
themselves and also produces TEST_CASE_COMPARISON.md - the full matrix
of which implementation has which test, each cell linking to the test. The docs workflow runs
that script with --check on every pull request, so a test added in one language and forgotten in
the others shows up as a gap in the matrix instead of as a README that quietly went stale. It used
to be a hand-written list, and had drifted to six languages and six wrong counts.
Known Implementation Differences
Some language-specific features are intentional:
LinksGroup- a parsed group of links kept as one object - exists in JavaScript, C#, Java, C++, Swift and Kotlin. Python, Rust, Go and PHP expose the same structure as nestedLinkvalues instead.- Tuple conversion - building a link from a language tuple - is offered where the language has
the syntax for it: C# via implicit operators and
Rust via
Fromimplementations.
These differences are by design and do not affect core parsing/formatting functionality.
Binary links notation
The version 1 specification defines shared packets and a LiNo document mapping. JavaScript, Python, Rust, C#, Go, Java, PHP, C++, Swift and Kotlin provide codecs beside their text parsers; all check the same golden vectors. See the cross-language example for encoding options and byte-for-byte interoperability.
Exact Unicode identifiers and values round-trip with the reference literal contract introduced in 0.24.0, including mixed quotes, empty strings, controls and multiline text.