@bgotink/kdl

This package contains a parser and stringifier for the KDL Document Language, a node-based, human-friendly configuration and serialization format.

The parser in this package focuses on parsing documents in a way that allows for format-preserving modifications. This is most useful when working with KDL files maintained by humans.

If you don't care about formatting or programmatic manipulation, you might want to check out the official parser kdljs instead.

[!CAUTION] This package handles KDL 2.0.0-draft.4, a draft of the KDL v2 spec. There might still be breaking changes to that specification before it is finalized.

Use version 0.1.6 of this package if you want a stable version that supports KDL v1.

Install

yarn add @bgotink/kdl

Usage

import {parse, format} from "@bgotink/kdl";

const doc = parse(String.raw`
	node "value" #"other value"# 2.0 4 #false \
			#null -0 {
		child; "child too"
	}
`);

doc.nodes[0].children.nodes[0].entries.push(
	parse(
		String.raw`/-lorem="ipsum" \
			dolor=#true`,
		{as: "entry"},
	),
);

assert.equal(
	format(doc),
	String.raw`
	node "value" #"other value"# 2.0 4 #false \
			#null -0 {
		child /-lorem="ipsum" \
			dolor=#true; "child too"
	}
`,
);

JSON-in-KDL (JiK)

This package exports function from @bgotink/kdl/json to parse and stringify KDL documents as JSON-in-KDL (JiK) 4.0.0. For information on the format, see the JiK 4.0.0 specification.

import {parse} from "@bgotink/kdl/json";

assert.deepEqual(
	parse(
		String.raw`
			- {
				prop #false
				otherProp {
					- 0
					- 2
					- 4
				}
			}
		`,
	),
	{
		prop: false,
		otherProp: [0, 2, 4],
	},
);

There are four functions:

  • parse and stringify turn text into the encoded JSON value and back. These functions are useful for encoding/decoding entire JiK files.
  • toJson and fromJson turn KDL nodes into the encoded JSON value and back. These functions give more fine-grained control over the output, and can be used for e.g. encoding/decoding a JiK node embedded in a KDL document.

Quirks

This package turns KDL documents into JavaScript objects and vice versa. It is therefore limited by the JavaScript language.

Properties

Multiple properties with the same name are allowed. All duplicated will be preserved, meaning those documents will correctly round-trip. When using node.getProperty()/node.getProperties()/node.getPropertyEntry(), the last property with that name's value will be returned, effectively shadowing any earlier duplicates. Using node.getPropertyEntries()/node.entries does expose the shadowed duplicates, leaving it up to the caller to handle these. Passing the node through clearFormat() removes these shadowed duplicates.

Numbers

JavaScript stores all numbers as 64-bit IEEE 754 floating point numbers. This limits what integer values can be used safely. These limits are lower than you might expect if you're used to working in environments that have a separate 64-bit integer data type.

The original representation of parsed numbers is retained, unless clearFormat is called on the value or any entry/node/document containing the value.

License

This package is licensed under the MIT license, which can be found in LICENSE.md.

The test suite at test/upstream is part of the KDL specification and is available under the Creative Commons Attribution-ShareAlike 4.0 International License.

Parsing KDL

The parse(text[, options]) function parses a KDL document. The text can be passed in as string, Node.js buffer, TypedArray, ArrayBuffer, or DataView.

You can pass the as option to make the function parse something different from a KDL document:

import {parse, Document, Node} from "@bgotink/kdl";

assert(
	parse(
		String.raw`
			node Lorem Ipsum
		`,
	) instanceof Document,
);

assert(
	parse(
		String.raw`
			node Lorem Ipsum
		`,
		{as: "node"},
	) instanceof Node,
);

Locations

Setting the storeLocations option to true makes location information available in the getLocation function.

Two ways of tracking columns

By default the columns in error messages and in getLocation results are tracked by code point. This means that characters that span multiple code points will move the column forward quite a bit. For example: 😅 is a single code point but 🏳️‍🌈 consists of four code points.

Setting the graphemeLocations option to true instead track columns by grapheme. A grapheme is what we humans perceive as a single character. The pride flag that consists of four code points is a single grapheme.

Tracking by code points is the default for the simple reason that it seems to match how columns are tracked in editors like VS Code or Zed. There's also a 6.5x speed difference between the two methods, but even with graphemeLocations enabled the parser succeeds in parsing thousands of documents per second.

Quirks

This package turns KDL documents into JavaScript objects and vice versa. It is therefore limited by the JavaScript language.

Properties

Multiple properties with the same name are allowed. All duplicated will be preserved, meaning those documents will correctly round-trip. When using node.getProperty()/node.getProperties()/node.getPropertyEntry(), the last property with that name's value will be returned, effectively shadowing any earlier duplicates. Using node.getPropertyEntries()/node.entries does expose the shadowed duplicates, leaving it up to the caller to handle these. Passing the node through clearFormat() removes these shadowed duplicates.

Numbers

JavaScript stores all numbers as 64-bit IEEE 754 floating point numbers. This limits what integer values can be used safely. These limits are lower than you might expect if you're used to working in environments that have a separate 64-bit integer data type.

The original representation of parsed numbers is retained, unless clearFormat is called on the value or any entry/node/document containing the value.

Formatting KDL

The format(value) function turns a document, node, entry, identifier, value, or tag into a KDL string representing that value.

The KDL DOM classes contain not just their values, but also any information on whitespace and comments required to format the element. This makes this package good at manipulating KDL files maintained by humans, as it supports making modifications with as few changes to the file as possible.

import {parse, format} from "@bgotink/kdl";

const doc = parse(
	String.raw`
		node "value" #"other value"# 2.0 4 #false \
				#null -0 {
			child; "child too"
		}
	`,
);

doc.nodes[0].children.nodes[0].entries.push(
	parse(
		String.raw`/-lorem="ipsum" \
				dolor=#true`,
		{as: "entry"},
	),
);

assert.equal(
	format(doc),
	String.raw`
		node "value" #"other value"# 2.0 4 #false \
				#null -0 {
			child /-lorem="ipsum" \
				dolor=#true; "child too"
		}
	`,
);

All KDL DOM elements that wrap simple values—i.e. Value, Identifier, and Tag—have an optional representation property that declares how its value is to be formatted. This property is set for all parsed elements and ensures that formatting the element results in as few changes as possible. Take care when changing these values, as the representation is not validated when formatting the element.

// Do not do this!
import {Entry, Identifier, Node, Value, format} from "@bgotink/kdl";

const node = new Node(new Identifier("real_name"));
node.name.representation = "fake_name";

const entry = new Entry(new Value(42), new Identifier("property"));
entry.name.representation = "something_else";
entry.value.representation = "false";

assert.equal(format(node), "fake_name something_else=false");

Instances of Document, Node, Entry, and Tag, store information about whitespace. Just like the representation, these fields are not validated when formatting the DOM elements.

Reset

The clearFormat(value) function removes any and all formatting from a document, node, entry, value, identifier, or tag.

JSON-in-KDL

The @bgotink/kdl/json package entrypoint exposes functions to handle JSON-in-KDL, aka JiK. There are two families of functions:

The parse and stringify functions are built to be a drop-in replacement for the JSON.parse and JSON.stringify functions. These functions work well for managing entire JiK files.

The toJson and fromJson functions allow for more fine-grained control. There are extra options that support some none-standard JiK behaviour. Most importantly they work with a KDL Node, which makes these functions support JiK nodes embedded into regular a KDL document.

All of these functions throw an InvalidJsonInKdlError if they encounter invalid JiK content.

@bgotink/kdl

Modules

index

Index

Classes

Interfaces

BOM

A Byte-Order Mark at the start of a document

Properties

text

text: string

The BOM text, i.e. '\ufeff'.

Defined in

src/model/whitespace.d.ts:17


type

type: "bom"

A property to differentiate the different types of whitespace

Defined in

src/model/whitespace.d.ts:12


EscLine

An escaped newline

Properties

text

text: string

The escaped newline

Defined in

src/model/whitespace.d.ts:64


type

type: "line-escape"

A property to differentiate the different types of whitespace

Defined in

src/model/whitespace.d.ts:59


InlineWhitespace

Regular plain old whitespace characters

Properties

text

text: string

The whitespace's text

Defined in

src/model/whitespace.d.ts:32


type

type: "space"

A property to differentiate the different types of whitespace

Defined in

src/model/whitespace.d.ts:27


LineSpaceSlashDash

A slashdash comment in a document, i.e. a slashdash commented node

Properties

preface

preface: PlainNodeSpace[]

Any whitespace between the slashdash token and the value

Defined in

src/model/whitespace.d.ts:149


type

type: "slashdash"

A property to differentiate the different types of whitespace

Defined in

src/model/whitespace.d.ts:144


value

value: Node

The escaped value

Defined in

src/model/whitespace.d.ts:154


Location

Location inside source text

Properties

endColumn

endColumn: number

Column of the last character of the Token. 1-indexed.

Defined in

src/locations.js:10


endLine

endLine: number

Line of the last character. 1-indexed.

Defined in

src/locations.js:9


endOffset

endOffset: number

Offset behind the last character. 0-indexed.

Defined in

src/locations.js:8


startColumn

startColumn: number

Column of the first character of the Token. 1-indexed.

Defined in

src/locations.js:7


startLine

startLine: number

Line of the first character. 1-indexed.

Defined in

src/locations.js:6


startOffset

startOffset: number

Offset of the first character. 0-indexed.

Defined in

src/locations.js:5


MultilineComment

A multiline comment

Properties

text

text: string

The comment text, including the comment tokens themselves

Defined in

src/model/whitespace.d.ts:79


type

type: "multiline"

A property to differentiate the different types of whitespace

Defined in

src/model/whitespace.d.ts:74


Newline

A single newline

Note a newline can consist of multiple characters: \r\n is a single newline.

Properties

text

text: string

The newline

Defined in

src/model/whitespace.d.ts:49


type

type: "newline"

A property to differentiate the different types of whitespace

Defined in

src/model/whitespace.d.ts:44


NodeSpaceSlashDash

A slashdash comment inside a node, i.e. a slashdash commented argument, property, or child block

Properties

preface

preface: PlainNodeSpace[]

Any whitespace between the slashdash token and the value

Defined in

src/model/whitespace.d.ts:114


type

type: "slashdash"

A property to differentiate the different types of whitespace

Defined in

src/model/whitespace.d.ts:109


value

value: Entry | Document

The escaped value

Defined in

src/model/whitespace.d.ts:119


SingleLineComment

A single-line comment

Properties

text

text: string

The comment's text, starting at the // and ending with a newline unless the comment ended at the end of the file

Defined in

src/model/whitespace.d.ts:94


type

type: "singleline"

A property to differentiate the different types of whitespace

Defined in

src/model/whitespace.d.ts:89

Type Aliases

LineSpace

LineSpace: (PlainLineSpace | LineSpaceSlashDash)[]

Whitespace in a document, i.e. before/after/between nodes

Defined in

src/model/whitespace.d.ts:160


NodeSpace

NodeSpace: (PlainNodeSpace | NodeSpaceSlashDash)[]

Whitespace inside of a node, e.g. between two arguments in a node.

Defined in

src/model/whitespace.d.ts:125


PlainLineSpace

PlainLineSpace: BOM | InlineWhitespace | Newline | SingleLineComment | MultilineComment

A single plain whitespace item in a document, i.e. before/after/between nodes

Defined in

src/model/whitespace.d.ts:130


PlainNodeSpace

PlainNodeSpace: InlineWhitespace | EscLine | MultilineComment

A single plain whitespace item inside of a node, e.g. between two arguments in a node.

Defined in

src/model/whitespace.d.ts:100

Functions

clearFormat()

clearFormat<T>(v): T

Type Parameters

T extends Identifier | Tag | Value | Entry | Node | Document

Parameters

v: T

Returns

T

Defined in

src/clear-format.js:122


format()

format(v): string

Parameters

v: Identifier | Tag | Value | Entry | Node | Document

Returns

string

Defined in

src/format.js:149


getLocation()

getLocation(element): undefined | Location

Get location information of the given parsed element

If the element was not created by the parser, or if the parser option storeLocations was not set to true, the result will be undefined.

Parameters

element: Identifier | Tag | Value | Entry | Node | Document

Returns

undefined | Location

Defined in

src/locations.js:27


parse()

parse(text, options)

parse(text, options): Value

Parse the given text as a value.

The text should not contain anything other than the value, i.e. no leading or trailing whitespace, no comments, no tags.

Parameters

text: string | ArrayBuffer | DataView | Int8Array | Uint8Array | Int16Array | Uint16Array | Int32Array | Uint32Array

options

options.as: "value"

options.graphemeLocations?: boolean

options.storeLocations?: boolean

Returns

Value

Defined in

src/parse.d.ts:23

parse(text, options)

parse(text, options): Identifier

Parse the given text as a identifier.

The text should not contain anything other than the identifier, i.e. no leading or trailing whitespace, no comments, no tags.

Parameters

text: string | ArrayBuffer | DataView | Int8Array | Uint8Array | Int16Array | Uint16Array | Int32Array | Uint32Array

options

options.as: "identifier"

options.graphemeLocations?: boolean

options.storeLocations?: boolean

Returns

Identifier

Defined in

src/parse.d.ts:46

parse(text, options)

parse(text, options): Entry

Parse the given text as an entry.

The text can contain extra whitespace, tags, and comments (though no slashdash comments of entire nodes)

Parameters

text: string | ArrayBuffer | DataView | Int8Array | Uint8Array | Int16Array | Uint16Array | Int32Array | Uint32Array

options

options.as: "entry"

options.graphemeLocations?: boolean

options.storeLocations?: boolean

Returns

Entry

Defined in

src/parse.d.ts:69

parse(text, options)

parse(text, options): Node

Parse the given text as a node.

The text can contain extra whitespace, tags, and comments.

Parameters

text: string | ArrayBuffer | DataView | Int8Array | Uint8Array | Int16Array | Uint16Array | Int32Array | Uint32Array

options

options.as: "node"

options.graphemeLocations?: boolean

options.storeLocations?: boolean

Returns

Node

Defined in

src/parse.d.ts:91

parse(text, options)

parse(text, options): LineSpace

Parse the given text as a whitespace in a document.

Parameters

text: string | ArrayBuffer | DataView | Int8Array | Uint8Array | Int16Array | Uint16Array | Int32Array | Uint32Array

options

options.as: "whitespace in document"

options.graphemeLocations?: boolean

options.storeLocations?: boolean

Returns

LineSpace

Defined in

src/parse.d.ts:107

parse(text, options)

parse(text, options): NodeSpace

Parse the given text as a whitespace in a node.

Parameters

text: string | ArrayBuffer | DataView | Int8Array | Uint8Array | Int16Array | Uint16Array | Int32Array | Uint32Array

options

options.as: "whitespace in node"

options.graphemeLocations?: boolean

options.storeLocations?: boolean

Returns

NodeSpace

Defined in

src/parse.d.ts:127

parse(text, options)

parse(text, options?): Document

Parse the given text as a document.

The text can contain extra whitespace, tags, and comments.

Parameters

text: string | ArrayBuffer | DataView | Int8Array | Uint8Array | Int16Array | Uint16Array | Int32Array | Uint32Array

options?

options.as?: "document"

options.graphemeLocations?: boolean

options.storeLocations?: boolean

Returns

Document

Defined in

src/parse.d.ts:149

Class: Document

A document is a collection of zero or mode Nodes

Constructors

new Document()

new Document(nodes?): Document

Parameters

nodes?: Node[] = []

Returns

Document

Defined in

src/model/document.js:49

Properties

nodes

nodes: Node[]

The nodes in this document

Defined in

src/model/document.js:37


trailing

trailing: undefined | string

Trailing whitespace

Defined in

src/model/document.js:44

Methods

appendNode()

appendNode(node): void

Add the given node at the end of this document

Parameters

node: Node | Document

Returns

void

Defined in

src/model/document.js:71


clone()

clone(): Document

Create an identical copy of this document

Returns

Document

Defined in

src/model/document.js:58


findNodeByName()

findNodeByName(name): undefined | Node

Return the last node in this document with the given name

This function returns the last node instead of first to be in line with how properties are defined in the KDL specification where the last property with the given name is used and the rest is shadowed.

Parameters

name: string

Returns

undefined | Node

Defined in

src/model/document.js:171


findNodesByName()

findNodesByName(name): Node[]

Return all nodes with the given node name

Changes to the returned array are not reflected back onto this document itself, and updates to the document won't reflect in the returned array.

Parameters

name: string

Returns

Node[]

Defined in

src/model/document.js:157


findParameterizedNode()

findParameterizedNode(name, parameter?): undefined | Node

Return the last node in this document with the given name, matching the parameter

If the parameter is undefined, this method looks for a node with any single arguments. If a parameter is passed, this method looks for a node with a single parameter, equal to the given parameter.

Parameters

name: string

parameter?: null | string | number | boolean

Returns

undefined | Node

Defined in

src/model/document.js:192


insertNodeAfter()

insertNodeAfter(newNode, referenceNode): void

Insert the given node to the document after the referenceNode, or at the beginning if no reference is passed

Parameters

newNode: Node | Document

referenceNode: null | Node

Returns

void

Throws

If the given referenceNode is not part of this document

Defined in

src/model/document.js:103


insertNodeBefore()

insertNodeBefore(newNode, referenceNode): void

Insert the given node to the document before the referenceNode, or at the end if no reference is passed

Parameters

newNode: Node | Document

referenceNode: null | Node

Returns

void

Throws

If the given referenceNode is not part of this document

Defined in

src/model/document.js:82


isEmpty()

isEmpty(): boolean

Return whether the document is empty

Returns

boolean

Defined in

src/model/document.js:227


removeNode()

removeNode(node): void

Remove the given node from this document

Parameters

node: Node

Returns

void

Throws

if the given node is not in this document

Defined in

src/model/document.js:123


removeNodesByName()

removeNodesByName(name): void

Remove all nodes with the given name from this document

Parameters

name: string

Returns

void

Defined in

src/model/document.js:218


replaceNode()

replaceNode(oldNode, newNode): void

Replace the old node with the new node in this document

Parameters

oldNode: Node

newNode: Node | Document

Returns

void

Throws

if the oldNode is not in this document

Defined in

src/model/document.js:139

Class: Node

A node is a node name, followed by zero or more arguments and/or properties, and children

Constructors

new Node()

new Node(name, entries?, children?): Node

Parameters

name: Identifier

entries?: Entry[] = []

children?: null | Document = null

Returns

Node

Defined in

src/model/node.js:105

Properties

beforeChildren

beforeChildren: undefined | string

Whitespace between the last entry and the children

Defined in

src/model/node.js:91


betweenTagAndName

betweenTagAndName: undefined | string

Whitespace between the tag and the node name

Defined in

src/model/node.js:98


children

children: null | Document

Children of the node

An empty array means the children block is present but empty, if the value is null then there is no children block.

Defined in

src/model/node.js:70


entries

entries: Entry[]

Entries of the node

Defined in

src/model/node.js:60


leading

leading: undefined | string

Leading whitespace

Defined in

src/model/node.js:77


name

name: Identifier

The name (also known as "tag name") of this node

Defined in

src/model/node.js:46


tag

tag: null | Tag = null

Tag attached to this value, if any

Defined in

src/model/node.js:53


trailing

trailing: undefined | string

Trailing whitespace

Defined in

src/model/node.js:84

Methods

addArgument()

addArgument(value, tag?, index?): void

Add the given value as argument to this node

The argument is added at the given index, or at the end. This index counts towards the arguments only, i.e. if the node has five entries, three of which are arguments then inserting an argument between the second and third can be achieved by passing 2 regardless of the whether properties and arguments are interspersed or not.

Parameters

value: null | string | number | boolean

The value to insert as argument

tag?: null | string

The tag to attach to the argument, if any

index?: number

The index

Returns

void

Defined in

src/model/node.js:269


appendNode()

appendNode(node): void

Add the given node at the end of this node's children

Parameters

node: Node | Document

Returns

void

Defined in

src/model/node.js:439


clone()

clone(): Node

Create an identical copy of this node

Returns

Node

Defined in

src/model/node.js:116


deleteProperty()

deleteProperty(name): void

Delete the property with the given name

Parameters

name: string

Returns

void

Defined in

src/model/node.js:421


findNodeByName()

findNodeByName(name): undefined | Node

Return the last node in this node's children with the given name

This function returns the last node instead of first to be in line with how properties are defined in the KDL specification where the last property with the given name is used and the rest is shadowed.

Parameters

name: string

Returns

undefined | Node

Defined in

src/model/node.js:517


findNodesByName()

findNodesByName(name): Node[]

Return all nodes with the given node name

Changes to the returned array are not reflected back onto this document itself, and updates to the document won't reflect in the returned array.

Parameters

name: string

Returns

Node[]

Defined in

src/model/node.js:503


findParameterizedNode()

findParameterizedNode(name, parameter?): undefined | Node

Return the last node in this node's children with the given name, matching the parameter

If the parameter is undefined, this method looks for a node with any single arguments. If a parameter is passed, this method looks for a node with a single parameter, equal to the given parameter.

Parameters

name: string

parameter?: null | string | number | boolean

Returns

undefined | Node

Defined in

src/model/node.js:532


getArgument()

getArgument(index): undefined | null | string | number | boolean

Return the argument at the given index, if present

This index counts towards the arguments only, i.e. if the node has five entries, three of which are arguments then passing 1 returns the second argument, regardless of the whether properties and arguments are interspersed or not.

Parameters

index: number

Returns

undefined | null | string | number | boolean

Defined in

src/model/node.js:227


getArgumentEntries()

getArgumentEntries(): Entry[]

Return a snapshot of all arguments of this node

Changes to the returned array are not reflected back onto this node itself, and updates to the node won't reflect in the returned array.

Returns

Entry[]

Defined in

src/model/node.js:197


getArgumentEntry()

getArgumentEntry(index): undefined | Entry

Return the argument entry at the given index, if present

This index counts towards the arguments only, i.e. if the node has five entries, three of which are arguments then passing 1 returns the second argument, regardless of the whether properties and arguments are interspersed or not.

Parameters

index: number

Returns

undefined | Entry

Defined in

src/model/node.js:242


getArguments()

getArguments(): (null | string | number | boolean)[]

Return a snapshot of all arguments of this node

Changes to the returned array are not reflected back onto this node itself, and updates to the node won't reflect in the returned array.

Returns

(null | string | number | boolean)[]

Defined in

src/model/node.js:185


getName()

getName(): string

Return the name of this node

Returns

string

Defined in

src/model/node.js:155


getProperties()

getProperties(): Map<string, null | string | number | boolean>

Return a snapshot of all properties of this node

Changes to the returned object are not reflected back onto this node itself, and updates to the node won't reflect in the returned object.

Returns

Map<string, null | string | number | boolean>

Defined in

src/model/node.js:333


getProperty()

getProperty(name): undefined | null | string | number | boolean

Return the value of the property with the given name, or undefined if it doesn't exist.

Parameters

name: string

Returns

undefined | null | string | number | boolean

Defined in

src/model/node.js:371


getPropertyEntries()

getPropertyEntries(): Entry[]

Return a snapshot of all properties of this node

Changes to the returned array are not reflected back onto this node itself, and updates to the node won't reflect in the returned array.

Returns

Entry[]

Defined in

src/model/node.js:350


getPropertyEntry()

getPropertyEntry(name): undefined | Entry

Return the property entry with the given name, or undefined if it doesn't exist.

Parameters

name: string

Returns

undefined | Entry

Defined in

src/model/node.js:382


getTag()

getTag(): null | string

Return the tag of this node, if any

Returns

null | string

Defined in

src/model/node.js:137


hasArgument()

hasArgument(index): boolean

Return the value at the given index, if present

This index counts towards the arguments only, i.e. if the node has five entries, three of which are arguments then passing 1 returns the second argument, regardless of the whether properties and arguments are interspersed or not.

Parameters

index: number

Returns

boolean

Defined in

src/model/node.js:212


hasArguments()

hasArguments(): boolean

Return whether this node has arguments

Returns

boolean

Defined in

src/model/node.js:173


hasChildren()

hasChildren(): boolean

Return whether this node has child nodes

Returns

boolean

Defined in

src/model/node.js:430


hasProperties()

hasProperties(): boolean

Return whether this node has properties

Returns

boolean

Defined in

src/model/node.js:321


hasProperty()

hasProperty(name): boolean

Return whether this node has the given property

Parameters

name: string

Returns

boolean

Defined in

src/model/node.js:360


insertNodeAfter()

insertNodeAfter(newNode, referenceNode): void

Insert the given node to the node's children after the referenceNode, or at the beginning if no reference is passed

Parameters

newNode: Node | Document

referenceNode: null | Node

Returns

void

Throws

If the given referenceNode is not part of this document

Defined in

src/model/node.js:461


insertNodeBefore()

insertNodeBefore(newNode, referenceNode): void

Insert the given node to the node's children before the referenceNode, or at the end if no reference is passed

Parameters

newNode: Node | Document

referenceNode: null | Node

Returns

void

Throws

If the given referenceNode is not part of this node's children

Defined in

src/model/node.js:450


removeArgument()

removeArgument(index): void

Remove the argument at the given index

The index counts towards the arguments only, i.e. if the node has five entries, three of which are arguments then the last argument can be removed by passing 2, regardless of whether the third argument is also the third entry.

Parameters

index: number

Returns

void

Defined in

src/model/node.js:301


removeNode()

removeNode(node): void

Remove the given node from this node's children

Parameters

node: Node

Returns

void

Throws

if the given node is not in this node's children

Defined in

src/model/node.js:471


removeNodesByName()

removeNodesByName(name): void

Remove all nodes with the given name from this document

Parameters

name: string

Returns

void

Defined in

src/model/node.js:542


replaceNode()

replaceNode(oldNode, newNode): void

Replace the old node with the new node in this node's children

Parameters

oldNode: Node

newNode: Node | Document

Returns

void

Throws

if the oldNode is not in this node's children

Defined in

src/model/node.js:486


setName()

setName(name): void

Set the name of this node to the given name

Parameters

name: string

Returns

void

Defined in

src/model/node.js:164


setProperty()

setProperty(name, value, tag?): void

Set the given property on this node

This function updates the property entry with the given name, if it exists.

Parameters

name: string

value: null | string | number | boolean

tag?: null | string

Returns

void

Defined in

src/model/node.js:402


setTag()

setTag(tag): void

Set the tag of this node to the given tag

Parameters

tag: undefined | null | string

Returns

void

Defined in

src/model/node.js:146


create()

static create(name): Node

Create a new node with the given name

Parameters

name: string

Returns

Node

Defined in

src/model/node.js:25

Class: Entry

An entry represents either an argument or a property to a node

Constructors

new Entry()

new Entry(value, name): Entry

Parameters

value: Value

name: null | Identifier

Returns

Entry

Defined in

src/model/entry.js:95

Properties

betweenTagAndValue

betweenTagAndValue: undefined | string

Whitespace between the tag and the value

Defined in

src/model/entry.js:89


equals

equals: undefined | string

Equals sign

Defined in

src/model/entry.js:82


leading

leading: undefined | string

Leading whitespace

Defined in

src/model/entry.js:68


name

name: null | Identifier

The name of this entry if it's a property, or null if it's an argument

Defined in

src/model/entry.js:47


tag

tag: null | Tag = null

Tag attached to this value, if any

Defined in

src/model/entry.js:61


trailing

trailing: undefined | string

Trailing whitespace

Defined in

src/model/entry.js:75


value

value: Value

The value of this entry

Defined in

src/model/entry.js:54

Methods

clone()

clone(): Entry

Create an identical copy of this entry

Returns

Entry

Defined in

src/model/entry.js:105


getName()

getName(): null | string

Return the name of this entry, if any

Returns

null | string

Defined in

src/model/entry.js:140


getTag()

getTag(): null | string

Return the tag of this entry, if any

Returns

null | string

Defined in

src/model/entry.js:122


getValue()

getValue(): null | string | number | boolean

Return the value of this entry

Returns

null | string | number | boolean

Defined in

src/model/entry.js:158


isArgument()

isArgument(): boolean

Return whether this entry is an argument

Returns

boolean

Defined in

src/model/entry.js:176


isProperty()

isProperty(): boolean

Return whether this entry is a named property

Returns

boolean

Defined in

src/model/entry.js:185


setName()

setName(name): void

Set the name of this entry to the given name

Parameters

name: undefined | null | string

Returns

void

Defined in

src/model/entry.js:149


setTag()

setTag(tag): void

Set the tag of this entry to the given tag

Parameters

tag: undefined | null | string

Returns

void

Defined in

src/model/entry.js:131


setValue()

setValue(value): void

Set the name of this entry to the given name

Parameters

value: null | string | number | boolean

Returns

void

Defined in

src/model/entry.js:167


createArgument()

static createArgument(value): Entry

Create a new argument entry with the given value

Parameters

value: null | string | number | boolean

Returns

Entry

Defined in

src/model/entry.js:15


createProperty()

static createProperty(name, value): Entry

Create a new property entry for the given key and value

Parameters

name: string

value: null | string | number | boolean

Returns

Entry

Defined in

src/model/entry.js:26

Class: Tag

A tag is tied to anode or entry

Constructors

new Tag()

new Tag(name): Tag

Parameters

name: string

Returns

Tag

Defined in

src/model/tag.js:41

Properties

leading

leading: undefined | string

Leading whitespace

Defined in

src/model/tag.js:29


name

readonly name: string

The tag itself

Defined in

src/model/tag.js:48


representation

representation: undefined | string

String representation of the tag

Defined in

src/model/tag.js:22


trailing

trailing: undefined | string

Trailing whitespace

Defined in

src/model/tag.js:36

Methods

clone()

clone(): Tag

Create an identical copy of this tag

Returns

Tag

Defined in

src/model/tag.js:63

Class: Identifier

An

Constructors

new Identifier()

new Identifier(name): Identifier

Parameters

name: string

Returns

Identifier

Defined in

src/model/identifier.js:27

Properties

name

readonly name: string

The identifier itself

Defined in

src/model/identifier.js:34


representation

representation: undefined | string

String representation of the identifier

Defined in

src/model/identifier.js:22

Methods

clone()

clone(): Identifier

Create an identical copy of this identifier

Returns

Identifier

Defined in

src/model/identifier.js:49

Class: Value

A value represents a primitive in KDL, i.e. a string, boolean, number, or null

Values are always tied to an entry.

Constructors

new Value()

new Value(value): Value

Parameters

value: null | string | number | boolean

Returns

Value

Defined in

src/model/value.js:29

Properties

representation

representation: undefined | string

String representation of the value

Defined in

src/model/value.js:24


value

readonly value: null | string | number | boolean

The value itself

Defined in

src/model/value.js:36

Methods

clone()

clone(): Value

Create an identical copy of this value

Returns

Value

Defined in

src/model/value.js:51

Class: InvalidKdlError

Error thrown when invalid KDL is encountered

Extends

  • Error

Constructors

new InvalidKdlError()

new InvalidKdlError(message?): InvalidKdlError

Parameters

message?: string

Returns

InvalidKdlError

Inherited from

Error.constructor

Defined in

node_modules/typescript/lib/lib.es5.d.ts:1082

new InvalidKdlError()

new InvalidKdlError(message?, options?): InvalidKdlError

Parameters

message?: string

options?: ErrorOptions

Returns

InvalidKdlError

Inherited from

Error.constructor

Defined in

node_modules/typescript/lib/lib.es5.d.ts:1082

Properties

cause?

optional cause: unknown

Inherited from

Error.cause

Defined in

node_modules/typescript/lib/lib.es2022.error.d.ts:24


message

message: string

Inherited from

Error.message

Defined in

node_modules/typescript/lib/lib.es5.d.ts:1077


stack?

optional stack: string

Inherited from

Error.stack

Defined in

node_modules/typescript/lib/lib.es5.d.ts:1078


prepareStackTrace()?

static optional prepareStackTrace: (err, stackTraces) => any

Optional override for formatting stack traces

Parameters

err: Error

stackTraces: CallSite[]

Returns

any

See

https://v8.dev/docs/stack-trace-api#customizing-stack-traces

Inherited from

Error.prepareStackTrace

Defined in

node_modules/@types/node/globals.d.ts:143


stackTraceLimit

static stackTraceLimit: number

Inherited from

Error.stackTraceLimit

Defined in

node_modules/@types/node/globals.d.ts:145

Methods

captureStackTrace()

static captureStackTrace(targetObject, constructorOpt?): void

Create .stack property on a target object

Parameters

targetObject: object

constructorOpt?: Function

Returns

void

Inherited from

Error.captureStackTrace

Defined in

node_modules/@types/node/globals.d.ts:136

json

Index

Classes

Interfaces

FromJsonOptions

Options for the fromJson function

Extends

Properties

allowEntries?

optional allowEntries: boolean

Whether to allow literal children to be encoded into values or properties

Defaults to true. This value can be defined specifically for arrays, objects, or the root node.

Defined in

src/json.d.ts:166


allowEntriesInArrays?

optional allowEntriesInArrays: boolean

Whether to allow literal items in the array to be encoded as values on a node

If set to false, all array items will be encoded as children.

If set to true, all leading literal values of arrays will be encoded as node values instead.

The default value is the value of allowEntries, which in turn defaults to true.

Defined in

src/json.d.ts:177


allowEntriesInObjects?

optional allowEntriesInObjects: boolean

Whether to allow literal properties in the object to be encoded as property on a node

If set to false, all node properties will be encoded as children.

If set to true, all properties with literal values of objects will be encoded as node properties instead. Note that this changes the order of properties, which are assumed not to matter in JSON.

The default value is the value of allowEntries, which in turn defaults to true.

Defined in

src/json.d.ts:189


allowEntriesInRoot?

optional allowEntriesInRoot: boolean

Whether to allow literal children to be encoded as values or properties on the root node

This property only has effect if the given value is an object or an array. Literal values are always encoded as values on the root node.

The default value of this option is the value of allowEntriesInArrays or allowEntriesInObjects, depending on the type of the value.

Defined in

src/json.d.ts:199


indentation?

optional indentation: string | number

The indentation to give each nested level of node

If a string is passed, that string is used as indentation. If a number higher than zero is passed, the indentation is set to the whitespace character repeated for that number of times. If zero is passed or no indentation is given, no newlines with indentation will be inserted into the output.

Inherited from

StringifyOptions.indentation

Defined in

src/json.d.ts:256


nodeName?

optional nodeName: string

Name of the root node to create

If no name is passed, the node will be called "-".

Defined in

src/json.d.ts:158


replaceJsonValue()?

optional replaceJsonValue: (key, value, originalValue) => unknown

Replacer function called for every JSON value in the data being transformed

The replacer can return any JSON value, which will be used instead of the original value. If undefined is returned, the value will be discarded.

If the originalValue had a toJSON method, it will be called and the result will be the value parameter. In all other cases value and originalValue will be the same value.

Parameters

key: string | number

The name of the property or the index inside an array

value: unknown

The value being handled

originalValue: unknown

The original value

Returns

unknown

Inherited from

StringifyOptions.replaceJsonValue

Defined in

src/json.d.ts:272


replaceKdlValue()?

optional replaceKdlValue: (key, value, jsonValue, originalJsonValue) => undefined | Entry | Node

Replacer function called for every KDL node or entry created

The replacer can return an entry or node. If an entry is returned but an entry would not be valid in the given location, it will be transformed into a node. If undefined is returned, the value will be discarded.

Parameters

key: string | number

The name of the property or the index inside an array

value: Entry | Node

The entry or node that was created

jsonValue: unknown

The JSON value that was transformed into the KDL value

originalJsonValue: unknown

Returns

undefined | Entry | Node

Inherited from

StringifyOptions.replaceKdlValue

Defined in

src/json.d.ts:290


JiKReviver()<T>

Reviver function that can be passed into parse or toJson

The function is called for every JSON value while it's being serialized. These values are replaced by the return value of this function.

Type Parameters

T

JiKReviver(value, key, data): undefined | T

Reviver function that can be passed into parse or toJson

The function is called for every JSON value while it's being serialized. These values are replaced by the return value of this function.

Parameters

value: JsonValue

The JSON value

key: string | number

The key of the value, empty string for the root value

data

The node or entry where the value was defined

data.location: Entry | Node

Returns

undefined | T

The value to use, if the value is undefined then the property is removed from the result

Defined in

src/json.d.ts:223


JsonObject

A JSON object

Indexable

[property: string]: JsonValue


StringifyOptions

Options for the stringify function

Extended by

Properties

indentation?

optional indentation: string | number

The indentation to give each nested level of node

If a string is passed, that string is used as indentation. If a number higher than zero is passed, the indentation is set to the whitespace character repeated for that number of times. If zero is passed or no indentation is given, no newlines with indentation will be inserted into the output.

Defined in

src/json.d.ts:256


replaceJsonValue()?

optional replaceJsonValue: (key, value, originalValue) => unknown

Replacer function called for every JSON value in the data being transformed

The replacer can return any JSON value, which will be used instead of the original value. If undefined is returned, the value will be discarded.

If the originalValue had a toJSON method, it will be called and the result will be the value parameter. In all other cases value and originalValue will be the same value.

Parameters

key: string | number

The name of the property or the index inside an array

value: unknown

The value being handled

originalValue: unknown

The original value

Returns

unknown

Defined in

src/json.d.ts:272


replaceKdlValue()?

optional replaceKdlValue: (key, value, jsonValue, originalJsonValue) => undefined | Entry | Node

Replacer function called for every KDL node or entry created

The replacer can return an entry or node. If an entry is returned but an entry would not be valid in the given location, it will be transformed into a node. If undefined is returned, the value will be discarded.

Parameters

key: string | number

The name of the property or the index inside an array

value: Entry | Node

The entry or node that was created

jsonValue: unknown

The JSON value that was transformed into the KDL value

originalJsonValue: unknown

Returns

undefined | Entry | Node

Defined in

src/json.d.ts:290


ToJsonOptions

Options for the toJson function

Properties

ignoreValues?

optional ignoreValues: boolean

Whether to ignore values on the root node

Turning this option on deviates from the JiK standard by ignoring all values on the root node. This makes it possible to encode parameterized nodes as JiK.

For example, every book node in the following document is a JiK node:

book "The Fellowship of the Ring" {
  author "J.R.R. Tolkien"
  publicationYear 1954
}

book "Dune" publicationYear=1965 {
  author "Frank Herbert"
}

Here's how this could be turned into an map containing all books:

const books = new Map(
  document.findNodesByName('book').map(node => [
	   node.getArgument(0),
    toJson(node, {ignoreValues: true}),
  ]),
)
Defined in

src/json.d.ts:62


ToJsonReviver<T>

Extra option to modify the return value of the toJson function

Type Parameters

T

Properties

reviver

reviver: JiKReviver<T>

Reviver to use

Defined in

src/json.d.ts:89


ToJsonType<T>

Extra option for providing a type hint to the toJson function

Type Parameters

T

Properties

type

type: T

Type to use for the node

Possible values are:

  • object: The node must be a valid object, and nodes that are ambiguous and could be objects or something else are assumed to be an object
  • array: The node must be a valid array, and nodes that are ambiguous and could be arrays or something else are assumed to be an array
Defined in

src/json.d.ts:79

Type Aliases

JsonValue

JsonValue: null | number | boolean | string | JsonObject | JsonValue[]

A JSON value

Defined in

src/json.d.ts:20

Functions

fromJson()

fromJson(value, options?): Node

Encode the given JSON value into a JiK node

Parameters

value: JsonValue

The JSON value to encode

options?: FromJsonOptions

Returns

Node

Throws

If the given value contains cycles.

Defined in

src/json.d.ts:208


parse()

parse(text, reviver)

parse(text, reviver?): JsonValue

Parse the given JiK text to its encoded JSON value

Parameters

text: string

The JiK text to parse

reviver?: JiKReviver<JsonValue>

Returns

JsonValue

Throws

If the given text is not a valid JiK document

Defined in

src/json.d.ts:236

parse(text, reviver)

parse(text, reviver): unknown

Parse the given JiK text to its encoded JSON value

Parameters

text: string

The JiK text to parse

reviver: JiKReviver<unknown>

Returns

unknown

Throws

If the given text is not a valid JiK document

Defined in

src/json.d.ts:243


stringify()

stringify(value, options)

stringify(value, options?): string

Stringify the given JSON value into JiK text

Parameters

value: unknown

The JSON value to encode

options?: StringifyOptions

Optional options

Returns

string

Throws

If the given JSON value contains cycles.

Defined in

src/json.d.ts:305

stringify(value, replacer, indentation)

stringify(value, replacer?, indentation?): string

Stringify the given JSON value into JiK text

This function's signrature is explicitly kept similar to JSON.stringify.

Parameters

value: unknown

The JSON value to encode

replacer?

indentation?: string | number

The indentation to give each nested level of node, either the actual indentation string or the number of spaces

Returns

string

Throws

If the given JSON value contains cycles.

Defined in

src/json.d.ts:315


toJson()

toJson(nodeOrDocument, options)

toJson(nodeOrDocument, options): JsonObject

Extract the JSON value encoded into the given JiK node or document.

If passed a document, the document must contain a single node, which acts as the root of the JiK value.

Parameters

nodeOrDocument: Node | Document

A valid JiK node or a document containing a single node which is a valid JiK node

options: ToJsonOptions & ToJsonType<"object"> & object

Returns

JsonObject

See

https://github.com/kdl-org/kdl/blob/76d5dd542a9043257bc65476c0a70b94667052a7/JSON-IN-KDL.md

Throws

If the given node is not a valid JiK node or if the given document doesn't contain exactly one node

Defined in

src/json.d.ts:101

toJson(nodeOrDocument, options)

toJson(nodeOrDocument, options): JsonValue[]

Extract the JSON value encoded into the given JiK node or document.

If passed a document, the document must contain a single node, which acts as the root of the JiK value.

Parameters

nodeOrDocument: Node | Document

A valid JiK node or a document containing a single node which is a valid JiK node

options: ToJsonOptions & ToJsonType<"array"> & object

Returns

JsonValue[]

See

https://github.com/kdl-org/kdl/blob/76d5dd542a9043257bc65476c0a70b94667052a7/JSON-IN-KDL.md

Throws

If the given node is not a valid JiK node or if the given document doesn't contain exactly one node

Defined in

src/json.d.ts:114

toJson(nodeOrDocument, options)

toJson(nodeOrDocument, options?): JsonValue

Extract the JSON value encoded into the given JiK node or document.

If passed a document, the document must contain a single node, which acts as the root of the JiK value.

Parameters

nodeOrDocument: Node | Document

A valid JiK node or a document containing a single node which is a valid JiK node

options?: ToJsonOptions & Partial<ToJsonType<string>> & Partial<ToJsonReviver<JsonValue>>

Returns

JsonValue

See

https://github.com/kdl-org/kdl/blob/76d5dd542a9043257bc65476c0a70b94667052a7/JSON-IN-KDL.md

Throws

If the given node is not a valid JiK node or if the given document doesn't contain exactly one node

Defined in

src/json.d.ts:127

toJson(nodeOrDocument, options)

toJson(nodeOrDocument, options?): unknown

Extract the JSON value encoded into the given JiK node or document.

If passed a document, the document must contain a single node, which acts as the root of the JiK value.

Parameters

nodeOrDocument: Node | Document

A valid JiK node or a document containing a single node which is a valid JiK node

options?: ToJsonOptions & Partial<ToJsonType<string>> & Partial<ToJsonReviver<unknown>>

Returns

unknown

See

https://github.com/kdl-org/kdl/blob/76d5dd542a9043257bc65476c0a70b94667052a7/JSON-IN-KDL.md

Throws

If the given node is not a valid JiK node or if the given document doesn't contain exactly one node

Defined in

src/json.d.ts:142

Class: InvalidJsonInKdlError

Error thrown when encountering invalid JSON-in-KDL

Extends

  • Error

Constructors

new InvalidJsonInKdlError()

new InvalidJsonInKdlError(message): InvalidJsonInKdlError

Parameters

message: string

Returns

InvalidJsonInKdlError

Overrides

Error.constructor

Defined in

src/json.d.ts:7

Properties

cause?

optional cause: unknown

Inherited from

Error.cause

Defined in

node_modules/typescript/lib/lib.es2022.error.d.ts:24


message

message: string

Inherited from

Error.message

Defined in

node_modules/typescript/lib/lib.es5.d.ts:1077


name

name: string

Inherited from

Error.name

Defined in

node_modules/typescript/lib/lib.es5.d.ts:1076


stack?

optional stack: string

Inherited from

Error.stack

Defined in

node_modules/typescript/lib/lib.es5.d.ts:1078


prepareStackTrace()?

static optional prepareStackTrace: (err, stackTraces) => any

Optional override for formatting stack traces

Parameters

err: Error

stackTraces: CallSite[]

Returns

any

See

https://v8.dev/docs/stack-trace-api#customizing-stack-traces

Inherited from

Error.prepareStackTrace

Defined in

node_modules/@types/node/globals.d.ts:143


stackTraceLimit

static stackTraceLimit: number

Inherited from

Error.stackTraceLimit

Defined in

node_modules/@types/node/globals.d.ts:145

Methods

captureStackTrace()

static captureStackTrace(targetObject, constructorOpt?): void

Create .stack property on a target object

Parameters

targetObject: object

constructorOpt?: Function

Returns

void

Inherited from

Error.captureStackTrace

Defined in

node_modules/@types/node/globals.d.ts:136

LL(1) Parser

This package contains an LL(1) parser, i.e. the parser iterates over the tokens without having to backtrack or look ahead. The parser achieves this by using a modified version of the KDL grammar defined in the KDL spec.

The parser iterates over the text per code point or per grapheme, depending on the value of the graphemeLocations option. It then looks at the first code point of every value (graphemes can contain multiple code points) and filters out code points disallowed by the KDL spec.

The parser works in two stages. First, it turns the stream of code points (or graphemes) into a stream of tokens. Then it iterates over the token stream to result in the KDL document.

The diagrams on this page are rendered using railroad-diagrams, a lovely library by Tab Atkins Jr, who happens to be involved in KDL too!

Tokenizer

The first step of the parser turns the stream of code points (or graphemes) into a stream of tokens. A token is an object with the following properties:

  • type: the token type
  • text: the text of the token, this can contain multiple code points / graphemes
  • start: the location of the first code point of this token in the source text
  • end: the location of the first code point after this token in the source text

The start and end locations are used when throwing errors upon encountering invalid KDL text, so these are stored even if the storeLocations option is false. These locations contain three properties: offset is the zero-indexed location of the character in the text, line and column are the one-indexed line and column positions. The offset can be used to programmatically find the token in the text, line and column are more interesting for human readers to e.g. see where in the document they've made a mistake.

Token types are stored as integer, rather than a human readable string because of two reasons. Firstly, "human readable" doesn't mean "the person running the parser understands it", so the usefulness of string types is questionable. Secondly, string comparison is slower than number comparison.

That speed bump granted by number comparison is also why the tokenizer looks at the code point's integer value to assign split the text in to tokens rather than compare string values. Regular expressions are avoided entirely.

Parser

The parser is a "recursive descent" parser. That means the starts at the top-level, e.g. parseDocument when asked to parse a document, and that function recurses into other parser functions, e.g. parseNode to parse each individual node.

document

In the KDL spec all line-space are used as line-space* so to simplify this grammar, the line-space itself takes care of the "zero or more" part.

The document non-terminal is used in node-children below, because distinguishing between node and final-node as defined in the KDL spec is impossible without unbounded look-ahead. Instead, the document non-terminal is modified to support ending on a base-node with or without node-terminator.

There's one downside to this rewritten non-terminal: It works in the LL(1) parser but I am unable to write it down in BNF or any derivative. If someone else has any idea, feel free to make the necessary changes!

plain-node-space

The KDL spec's plain-node-space is always used with either the + or * modifier. Instead of doing the same, the plain-node-space in this grammar is it's own +, so it's either used plain for + or marked optional for *.

line-space

Compared to the line-space defined in the KDL spec, this version includes its own "zero or more" operator.

node-space

The optional-node-space and required-node-space non-terminals defined in the KDL spec are combined into a single non-terminal. This node-space non-terminal does one extra thing that isn't shown in the diagram: it remembers whether the last subrule it applied was a plain-node-space.

base-node

The node-prop-or-arg and node-children paths are only allowed if the last consumed node-space ended with a plain-node-space. Note node-prop-or-arg always ends on a node-space.

node

node-prop-or-arg

The node-prop-or-arg non-terminal is very different from its sibling in the KDL spec in order to remove any need for look-ahead:

  • If the first token is a tag (called type in the spec), then it must be an argument
  • If the first token is a number or a keyword, then it must be an argument
  • If the first token is a string, then we need to check if there's an equals sign.

Looking for the equals sign required unbounded lookahead thanks to the allowed node-space between the property name and the equals sign. By changing this non-terminal so it also consumes any node-space that comes after the property or argument, we can remove the need for the look-ahead.

node-children

node-terminator

The document non-terminal supports ending on a node without node-terminator, so this non-terminal doesn't need to include EOF.

tag

escline

multiline-comment

single-line-comment

value

keyword

number

string

The parser itself is greatly simplified and very lenient when it comes to strings, with post-processing added to filter out invalid strings. All multiline quoted and raw strings are post-processed to remove leading whitespace. All quoted strings are post-processed to replace any escapes.