liveBook · Manning

Almost-ast

As explained in Building User-Friendly DSLs

An almost-AST is a tree representation nearly identical to an abstract syntax tree except that reference objects contain target identifiers instead of object references.

An almost-AST is a tree nearly identical to an abstract syntax tree, except each reference object stores its target's identifier instead of a JavaScript object reference. Replacing { ref: targetObject } with { refId: targetObject.id } removes cycles, allowing the tree to be serialized with JSON.stringify. section 7.2

A practical serialization pipeline follows these steps:

  • Traverse AST objects depth-first, copying each object's id, concept, and recursively transformed settings. section 7.2
  • Convert reference objects into { refId: target.id }, while preserving an unresolved reference as { refId: undefined }. section 7.2
  • Serialize the resulting cycle-free structure to JSON and write it to persistent storage. section 7.2

Rental AST

A Rental record can contain an Attribute Reference whose target is the rental price before discount. In the almost-AST, that relation stores the target attribute's ID, while the referenced attribute remains a separate tree object. section 7.2

Deserialization reverses the transformation. It copies the serialized tree, indexes AST objects by ID, records references needing resolution, and then replaces each refId with the corresponding AST object reference. An undefined refId becomes an unresolved { ref: undefined } reference. section 7.3

... 
    { 
        id: "<id1>", 
        concept: "<concept1>", 
        settings: { 
            // ... 
        } 
    } 
... 
    { 
        id: "<id2>", 
        concept: "<concept2>", 
        settings: { 
            "<ref-property>": { 
                "refId": "<id1>" 
            }, 
            // ...other settings... 
        } 
    } 
...

Compare the serialized reference shape with the original AST reference shape. section 7.2

Listing 7.15 Transforming an AST into a cycle-free “almost-AST”
const serialize = (value) => {
    if (isAstObject(value)) {
        const serializedAstObject = {
            id: value.id,
            concept: value.concept,
            settings: {}
        }
        for (const propertyName in value.settings) {
            serializedAstObject.settings[propertyName] =
                 serialize(value.settings[propertyName])
        }
        return serializedAstObject
    }
    if (isAstReferenceObject(value)) {
        return ({  #1
            refId: isAstObject(value.ref)    #2
                     ? value.ref.id
                     : undefined
        })
    }
    if (Array.isArray(value)) {
        return value.map(serialize)
    }
    return value
}
module.exports.serialize = serialize

Inspect the recursive serialize function that produces a cycle-free almost-AST. section 7.2

How Building User-Friendly DSLs covers it

How deep it goes
Implements recursive serialization and deserialization, including unresolved references and deferred ID-based resolution.
Examples
Uses a Rental record with data attributes, nested Number nodes, and an Attribute Reference to demonstrate serialized IDs.
What it assumes
Assumes AST objects and depth-first traversal while handling reference objects explicitly in JavaScript.
What you can do after
Build it from scratch using recursive JavaScript transformations, JSON storage, ID lookup tables, and a reference-resolution pass.
How it connects
Connects abstract syntax trees, object references, depth-first traversal, serialization, deserialization, JSON, and reference resolution.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Building User-Friendly DSLs. The section links point to the parts of the book it draws on.