@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
andstringify
turn text into the encoded JSON value and back. These functions are useful for encoding/decoding entire JiK files.toJson
andfromJson
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
type
type:
"bom"
A property to differentiate the different types of whitespace
Defined in
EscLine
An escaped newline
Properties
text
text:
string
The escaped newline
Defined in
type
type:
"line-escape"
A property to differentiate the different types of whitespace
Defined in
InlineWhitespace
Regular plain old whitespace characters
Properties
text
text:
string
The whitespace's text
Defined in
type
type:
"space"
A property to differentiate the different types of whitespace
Defined in
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
type
type:
"slashdash"
A property to differentiate the different types of whitespace
Defined in
value
value:
Node
The escaped value
Defined in
Location
Location inside source text
Properties
endColumn
endColumn:
number
Column of the last character of the Token. 1-indexed.
Defined in
endLine
endLine:
number
Line of the last character. 1-indexed.
Defined in
endOffset
endOffset:
number
Offset behind the last character. 0-indexed.
Defined in
startColumn
startColumn:
number
Column of the first character of the Token. 1-indexed.
Defined in
startLine
startLine:
number
Line of the first character. 1-indexed.
Defined in
startOffset
startOffset:
number
Offset of the first character. 0-indexed.
Defined in
MultilineComment
A multiline comment
Properties
text
text:
string
The comment text, including the comment tokens themselves
Defined in
type
type:
"multiline"
A property to differentiate the different types of whitespace
Defined in
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
type
type:
"newline"
A property to differentiate the different types of whitespace
Defined in
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
type
type:
"slashdash"
A property to differentiate the different types of whitespace
Defined in
value
The escaped value
Defined in
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
type
type:
"singleline"
A property to differentiate the different types of whitespace
Defined in
Type Aliases
LineSpace
LineSpace: (
PlainLineSpace
|LineSpaceSlashDash
)[]
Whitespace in a document, i.e. before/after/between nodes
Defined in
NodeSpace
NodeSpace: (
PlainNodeSpace
|NodeSpaceSlashDash
)[]
Whitespace inside of a node, e.g. between two arguments in a node.
Defined in
PlainLineSpace
PlainLineSpace:
BOM
|InlineWhitespace
|Newline
|SingleLineComment
|MultilineComment
A single plain whitespace item in a document, i.e. before/after/between nodes
Defined in
PlainNodeSpace
PlainNodeSpace:
InlineWhitespace
|EscLine
|MultilineComment
A single plain whitespace item inside of a node, e.g. between two arguments in a node.
Defined in
Functions
clearFormat()
clearFormat<
T
>(v
):T
Type Parameters
• T extends Identifier
| Tag
| Value
| Entry
| Node
| Document
Parameters
• v: T
Returns
T
Defined in
format()
format(
v
):string
Parameters
• v: Identifier
| Tag
| Value
| Entry
| Node
| Document
Returns
string
Defined in
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
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
Defined in
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
Defined in
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
Defined in
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
Defined in
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
Defined in
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
Defined in
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
Defined in
Class: Document
A document is a collection of zero or mode Nodes
Constructors
new Document()
new Document(
nodes
?):Document
Parameters
• nodes?: Node
[] = []
Returns
Defined in
Properties
nodes
nodes:
Node
[]
The nodes in this document
Defined in
trailing
trailing:
undefined
|string
Trailing whitespace
Defined in
Methods
appendNode()
appendNode(
node
):void
Add the given node at the end of this document
Parameters
Returns
void
Defined in
clone()
clone():
Document
Create an identical copy of this document
Returns
Defined in
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
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
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
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
• referenceNode: null
| Node
Returns
void
Throws
If the given referenceNode is not part of this document
Defined in
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
• referenceNode: null
| Node
Returns
void
Throws
If the given referenceNode is not part of this document
Defined in
isEmpty()
isEmpty():
boolean
Return whether the document is empty
Returns
boolean
Defined in
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
removeNodesByName()
removeNodesByName(
name
):void
Remove all nodes with the given name from this document
Parameters
• name: string
Returns
void
Defined in
replaceNode()
replaceNode(
oldNode
,newNode
):void
Replace the old node with the new node in this document
Parameters
• oldNode: Node
Returns
void
Throws
if the oldNode is not in this document
Defined in
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
Defined in
Properties
beforeChildren
beforeChildren:
undefined
|string
Whitespace between the last entry and the children
Defined in
betweenTagAndName
betweenTagAndName:
undefined
|string
Whitespace between the tag and the node name
Defined in
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
entries
entries:
Entry
[]
Entries of the node
Defined in
leading
leading:
undefined
|string
Leading whitespace
Defined in
name
name:
Identifier
The name (also known as "tag name") of this node
Defined in
tag
tag:
null
|Tag
=null
Tag attached to this value, if any
Defined in
trailing
trailing:
undefined
|string
Trailing whitespace
Defined in
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
appendNode()
appendNode(
node
):void
Add the given node at the end of this node's children
Parameters
Returns
void
Defined in
clone()
clone():
Node
Create an identical copy of this node
Returns
Defined in
deleteProperty()
deleteProperty(
name
):void
Delete the property with the given name
Parameters
• name: string
Returns
void
Defined in
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
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
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
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
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
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
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
getName()
getName():
string
Return the name of this node
Returns
string
Defined in
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
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
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
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
getTag()
getTag():
null
|string
Return the tag of this node, if any
Returns
null
| string
Defined in
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
hasArguments()
hasArguments():
boolean
Return whether this node has arguments
Returns
boolean
Defined in
hasChildren()
hasChildren():
boolean
Return whether this node has child nodes
Returns
boolean
Defined in
hasProperties()
hasProperties():
boolean
Return whether this node has properties
Returns
boolean
Defined in
hasProperty()
hasProperty(
name
):boolean
Return whether this node has the given property
Parameters
• name: string
Returns
boolean
Defined in
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
• referenceNode: null
| Node
Returns
void
Throws
If the given referenceNode is not part of this document
Defined in
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
• referenceNode: null
| Node
Returns
void
Throws
If the given referenceNode is not part of this node's children
Defined in
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
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
removeNodesByName()
removeNodesByName(
name
):void
Remove all nodes with the given name from this document
Parameters
• name: string
Returns
void
Defined in
replaceNode()
replaceNode(
oldNode
,newNode
):void
Replace the old node with the new node in this node's children
Parameters
• oldNode: Node
Returns
void
Throws
if the oldNode is not in this node's children
Defined in
setName()
setName(
name
):void
Set the name of this node to the given name
Parameters
• name: string
Returns
void
Defined in
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
setTag()
setTag(
tag
):void
Set the tag of this node to the given tag
Parameters
• tag: undefined
| null
| string
Returns
void
Defined in
create()
static
create(name
):Node
Create a new node with the given name
Parameters
• name: string
Returns
Defined in
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
Defined in
Properties
betweenTagAndValue
betweenTagAndValue:
undefined
|string
Whitespace between the tag and the value
Defined in
equals
equals:
undefined
|string
Equals sign
Defined in
leading
leading:
undefined
|string
Leading whitespace
Defined in
name
name:
null
|Identifier
The name of this entry if it's a property, or null if it's an argument
Defined in
tag
tag:
null
|Tag
=null
Tag attached to this value, if any
Defined in
trailing
trailing:
undefined
|string
Trailing whitespace
Defined in
value
value:
Value
The value of this entry
Defined in
Methods
clone()
clone():
Entry
Create an identical copy of this entry
Returns
Defined in
getName()
getName():
null
|string
Return the name of this entry, if any
Returns
null
| string
Defined in
getTag()
getTag():
null
|string
Return the tag of this entry, if any
Returns
null
| string
Defined in
getValue()
getValue():
null
|string
|number
|boolean
Return the value of this entry
Returns
null
| string
| number
| boolean
Defined in
isArgument()
isArgument():
boolean
Return whether this entry is an argument
Returns
boolean
Defined in
isProperty()
isProperty():
boolean
Return whether this entry is a named property
Returns
boolean
Defined in
setName()
setName(
name
):void
Set the name of this entry to the given name
Parameters
• name: undefined
| null
| string
Returns
void
Defined in
setTag()
setTag(
tag
):void
Set the tag of this entry to the given tag
Parameters
• tag: undefined
| null
| string
Returns
void
Defined in
setValue()
setValue(
value
):void
Set the name of this entry to the given name
Parameters
• value: null
| string
| number
| boolean
Returns
void
Defined in
createArgument()
static
createArgument(value
):Entry
Create a new argument entry with the given value
Parameters
• value: null
| string
| number
| boolean
Returns
Defined in
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
Defined in
Class: Tag
A tag is tied to anode or entry
Constructors
new Tag()
new Tag(
name
):Tag
Parameters
• name: string
Returns
Defined in
Properties
leading
leading:
undefined
|string
Leading whitespace
Defined in
name
readonly
name:string
The tag itself
Defined in
representation
representation:
undefined
|string
String representation of the tag
Defined in
trailing
trailing:
undefined
|string
Trailing whitespace
Defined in
Methods
clone()
clone():
Tag
Create an identical copy of this tag
Returns
Defined in
Class: Identifier
An
Constructors
new Identifier()
new Identifier(
name
):Identifier
Parameters
• name: string
Returns
Defined in
Properties
name
readonly
name:string
The identifier itself
Defined in
representation
representation:
undefined
|string
String representation of the identifier
Defined in
Methods
clone()
clone():
Identifier
Create an identical copy of this identifier
Returns
Defined in
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
Defined in
Properties
representation
representation:
undefined
|string
String representation of the value
Defined in
value
readonly
value:null
|string
|number
|boolean
The value itself
Defined in
Methods
clone()
clone():
Value
Create an identical copy of this value
Returns
Defined in
Class: InvalidKdlError
Error thrown when invalid KDL is encountered
Extends
Error
Constructors
new InvalidKdlError()
new InvalidKdlError(
message
?):InvalidKdlError
Parameters
• message?: string
Returns
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
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
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
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
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
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
Defined in
nodeName?
optional
nodeName:string
Name of the root node to create
If no name is passed, the node will be called "-".
Defined in
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
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
The entry or node that was created
• jsonValue: unknown
The JSON value that was transformed into the KDL value
• originalJsonValue: unknown
Returns
Inherited from
StringifyOptions
.replaceKdlValue
Defined in
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
Returns
undefined
| T
The value to use, if the value is undefined
then the property is removed from the result
Defined in
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
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
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
The entry or node that was created
• jsonValue: unknown
The JSON value that was transformed into the KDL value
• originalJsonValue: unknown
Returns
Defined in
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
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
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 objectarray
: 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
Type Aliases
JsonValue
JsonValue:
null
|number
|boolean
|string
|JsonObject
|JsonValue
[]
A JSON value
Defined in
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
Throws
If the given value contains cycles.
Defined in
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
Throws
If the given text is not a valid JiK document
Defined in
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
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
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
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
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
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
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
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
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
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
Class: InvalidJsonInKdlError
Error thrown when encountering invalid JSON-in-KDL
Extends
Error
Constructors
new InvalidJsonInKdlError()
new InvalidJsonInKdlError(
message
):InvalidJsonInKdlError
Parameters
• message: string
Returns
Overrides
Error.constructor
Defined in
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 typetext
: the text of the token, this can contain multiple code points / graphemesstart
: the location of the first code point of this token in the source textend
: 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.