JavaScript implementation of the Links Notation parser using Bun and Peggy.js parser generator.
Install the package using your preferred package manager:
npm install links-notationbun add links-notationimport { Parser, Link } from 'npm:links-notation@^0.6.0';For contributors working on the source code:
cd js
bun installcd js
npm installCompile the Peggy.js grammar:
bun run build:grammarBuild the project:
bun run buildRun tests:
bun testWatch mode:
bun test --watchThis package includes TypeScript type definitions for improved developer experience with IntelliSense, autocomplete, and compile-time type checking.
import { Parser, Link, FormatOptions } from 'links-notation';
// TypeScript provides full type checking and autocomplete
const parser = new Parser({
maxInputSize: 10 * 1024 * 1024,
maxDepth: 1000,
});
const links: Link[] = parser.parse('(source: type target)');import { Parser, Link } from 'links-notation';
// Create parser
const parser = new Parser();
// Parse Lino format string
const input = `papa (lovesMama: loves mama)
son lovesMama
daughter lovesMama
all (love mama)`;
const result = parser.parse(input);
console.log(result);
// Access parsed structure
result.forEach((link) => {
console.log(link.toString());
});import { Link } from 'links-notation';
// Create links programmatically
const link = new Link('parent', [new Link('child1'), new Link('child2')]);
console.log(link.toString()); // (parent: child1 child2)
// Access link properties
console.log('ID:', link.id);
console.log('Values:', link.values);// Handle nested structures
const input = `parent
child1
child2
grandchild1
grandchild2`;
const parsed = await parser.parse(input);
// Work with groups
import { LinksGroup } from 'links-notation';
const group = new LinksGroup(parsed);
console.log(group.format());import {
Parser,
Link,
FormatOptions,
FormatConfig,
formatLinks,
} from 'links-notation';
// Create parser with options
const parser = new Parser({
maxInputSize: 5 * 1024 * 1024,
maxDepth: 500,
});
// Parse with full type safety
const links: Link[] = parser.parse('(id: value1 value2)');
// Create links programmatically
const link = new Link('parent', [new Link('child1'), new Link('child2')]);
// Use formatting options
const formatOptions = new FormatOptions({
lessParentheses: true,
maxLineLength: 80,
indentLongLines: true,
maxInlineRefs: 3,
groupConsecutive: false,
});
// Format with type-checked options
const formatted: string = link.format(formatOptions);
// Format multiple links
const output: string = formatLinks(links, formatOptions);papa (lovesMama: loves mama)
son lovesMama
daughter lovesMama
all (love mama)
papa has car
mama has house
(papa and mama) are happy
(linksNotation: links notation)
(This is a linksNotation as well)
(linksNotation supports (unlimited number (of references) in each link))
parent
child1
child2
grandchild1
grandchild2
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.
import { Parser, formatLinks } from 'links-notation';
const links = new Parser().parse(`value (
id "1"
label "one"
)`);
console.log(formatLinks(links)); // (value ((id 1) (label one)))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:
import { Parser, formatLinks } from 'links-notation';
const document =
'# the machines this deploys to\ndeploy: staging # only staging, for now\n';
console.log(formatLinks(new Parser().parse(document))); // (deploy: staging)
const plain = new Parser({ comments: false });
console.log(formatLinks(plain.parse('# a b\n'))); // (# a b)Main parser class for converting strings to links.
constructor(options)- Create a parser;options.commentsset tofalsereads#as an ordinary character instead of the start of a commentinitialize()- Initialize the parser (async)parse(input)- Parse a Lino string and return links
Represents a single link with ID and values.
constructor(id, values = [])- Create a new linktoString()- Convert link to string formatid- Link identifiervalues- Array of child values/links
Container for grouping related links.
constructor(links)- Create a new groupformat()- Format the group as a string
Thrown by parse(input) when the document does not parse. The message says
where the document stopped making sense and quotes the offending line:
import { Parser, ParseError } from 'links-notation';
try {
new Parser().parse('ci_gate x\nstage: rust: nextest\n');
} catch (error) {
console.error(error.message);
if (error instanceof ParseError) {
console.error(`${error.line}:${error.column} (offset ${error.offset})`);
}
}Syntax error at line 2, column 12: Expected "(", [ \t], [\r\n], or [^ \t\n\r(:)] but ":" found.
2 | stage: rust: nextest
| ^
offset- Offset of the offending position from the start of the documentline,column- Where the document stopped parsing, counted from 1found- The character found instead, ornullat the end of the documentlineText- The offending line, as writtensnippet- The offending line with a caret under the offending columnlocation- The position as the generated parser reports itcause- The error the generated parser threw
src/grammar.pegjs- Peggy.js grammar definitionsrc/Link.js- Link data structuresrc/LinksGroup.js- Links group containersrc/ParseError.js- Parse error with the position of the defectsrc/Parser.js- Parser wrappersrc/index.js- Main entry pointtests/- Test files
Run ESLint to check for code style issues:
bun run lintAuto-fix linting issues:
bun run lint:fixThis project uses pre-commit hooks that automatically run ESLint before commits. To set up pre-commit hooks locally:
# From repository root
pip install pre-commit
pre-commit install- Peggy.js (5.0.6) - Parser generator
- Bun runtime (development)
This project uses Prettier for code formatting.
npx prettier --write .npx prettier --check .These checks are also enforced in CI. Pull requests with formatting issues will fail the format check.
- Package:
links-notation - License: Unlicense (see LICENSE)