Tags: bgotink/kdl
Tags
v0.3.0 This release of `@bgotink/kdl` contains some major improvements and a few smaller breaking changes. **Breaking Changes** - The `Location` export is now called `StoredLocation`, `TokenLocation` is now called `Location`. - The shape of `StoredLocation` has changed from `location.startOffset` to `location.start.offset` etc. - The `tag` property has moved from the `Entry` to the `Value`. The `getTag`/`setTag` methods still exist on `Entry`. - The `Identifier` and `Value` classes are now mutable, i.e. their value can be changed. Use the `setName` / `setValue` method instead of modifying the `name` / `value` property directly to prevent issues in parsed documents. **Error improvements** Parts of the tokenizer and parser have been rewritten to improve the errors thrown by the `parse` function. More errors are now considered "recoverable", which means the parser can continue to see if there are more errors so it can give the caller more information than only the first mistake in the source text. Some of the error messages have been extended with more useful information. The `InvalidKdlError` class introduces two new properties `start` and `end` to point towards a specific location in the source text where the error occurs. These new properties allow for more precision and flexibility than the `token` property, which still exists. Many of the errors thrown by the parser are updated to point directly towards the character or characters that cause the error. **(De)Serialization Tools** This release introduces a new export: `@bgotink/kdl/dessert` which contains a bunch of utilities to deserialize KDL text into a JavaScript structure and serialize it back again. The API doesn't enforce any programming paradigm, working with regular functions and with classes. Here's an example with functions: ```ts import { type DeserializationContext, type SerializationContext, deserialize, serialize, } from "@bgotink/kdl/dessert"; type Tree = {value: number; left?: Tree; right?: Tree}; function treeDeserializer(ctx: DeserializationContext): Tree { return { value: ctx.argument.required("number"), left: ctx.child.single("left", treeDeserializer), right: ctx.child.single("right", treeDeserializer), }; } export function readTree(node: Node): Tree { return deserialize(node, treeDeserializer); } function treeSerializer(ctx: SerializationContext, tree: Tree) { ctx.argument(tree.value); if (tree.left) { ctx.child("left", treeSerializer, tree.left); } if (tree.right) { ctx.child("right", treeSerializer, tree.right); } } export function writeTree(tree: Tree): Node { return serialize("root", treeDeserializer, tree); } ``` and here's that same example using classes: ```ts import { type DeserializationContext, type SerializationContext, deserialize, serialize, } from "@bgotink/kdl/dessert"; class Tree { static deserialize(ctx: DeserializationContext): Tree { return new Tree( ctx.argument.required("number"), ctx.child.single("left", Tree), ctx.child.single("right", Tree), ); } constructor( readonly value: number, readonly left?: Tree, readonly right?: Tree, ) {} serialize(ctx: SerializationContext) { ctx.argument(this.value); if (this.left) { ctx.child("left", this.left); } if (this.right) { ctx.child("right", this.right); } } } export function readTree(node: Node): Tree { return deserialize(node, Tree); } export function writeTree(tree: Tree): Node { return serialize("root", tree); } ``` Both of these examples turn the following KDL node into a `Tree` structure and back. ```kdl root 10 { left 5 right 5 { left 2 { left 1; right 1 } right 3 { left 2; right 1 } } } ``` The dessert API supports preserving comments and formatting when making modifications to a KDL file. This preservation can be enabled by linking the `DeserializationContext` used to deserialize the value from KDL to the `SerializationContext` used when serializing the value back to KDL. This is done via the `SerializationContext`'s new `source` function. This takes two steps: 1. Store the `DeserializationContext` while deserializing, e.g. by adding it to a hidden/private property on the returned object 2. Pass the stored `DeserializationContext` to the `SerializationContext` inside the serializer It is important that step 2 is done at the start of the serializer. Doing so after making changes to the `SerializationContext` will throw an error. Here we've taken the `Tree` class from above and added in the necessary code to enable preserving formatting and comments: ```ts class Tree { static deserialize(ctx: DeserializationContext): Tree { const tree = new Tree( ctx.argument.required("number"), ctx.child.single("left", Tree), ctx.child.single("right", Tree), ); tree.#deserializationCtx = ctx; return tree; } // We store the deserialization context #deserializationCtx?: DeserializationContext; constructor( readonly value: number, readonly left?: Tree, readonly right?: Tree, ) {} serialize(ctx: SerializationContext) { // we pass the deserialization context used to create // this Tree instance to the serialization context, linking // them together ctx.source(this.#deserializationContext); ctx.argument(this.value); if (this.left) { ctx.child("left", this.left); } if (this.right) { ctx.child("right", this.right); } } } ```
PreviousNext