Visualizing the reduction
The code in this and previous blog posts can also be found here, on Codeberg. Check out the repo at the commit marked “blog 09”.
In the previous blog post, we implemented a visualization of the AST: the projection in projectional editing. We also adorned that visualization with the reduction of each value in the program, by explicitly calling the reducer. That doesn’t look like proper separation of concerns, so we’ll change that in this blog post.
Instead of explicitly calling the reducer at every spot where we might need the reduction of a value, we’ll compute the reduction ahead of time. Then, we store the result on a node in the AST, so the projection can just retrieve it from the AST, when and where it needs it.
At the moment, this would make virtually no difference performance-wise: we generate the HTML for our example program just once, so we also compute the reductions just the once. However, we’re also going to use the same principle to compute types of values and check constraints on AST nodes, and store the outcomes on the nodes from which they originate. Moreover, constraint checking and reduction benefit from already knowing the types of children of the AST node we’re dealing with. Finally, once we get to make editing a MyFPL program interactive (by making the projection editable), we would definitely benefit from any optimization to computing types and reductions, and checking constraints. But for now, we’ll use the simplest approach that works.
Expanding the structure with an annotation
So, what would be a good way of storing reduced values on the AST, given that those reduced values are:
- Completely derived from that same AST, and might change as soon as the original AST changes.
- ASTs themselves.
One way of doing that, would be to extend the types of the nodes in the AST, so that it has a “slot” in which you can those reduced values. Because we’re using LionWeb, that would mean that we’d have to create an additional reduction containment on a suitable classifier – so, on an interface or a concept – to hold such a derived AST. In our case, the Value interface would seem to be suitable. Because the original AST has to be created before the reducer can do its job, that reduction containment would have to have optional cardinality. All in all, this would make our implementation “ugly” in the sense that it mixes concerns – rather than separating them – and forces us to deal with unset values of the reduction containment.
A better solution is: use annotations. LionWeb’s annotations are a lightweight mechanism to augment nodes in the AST with information, without having to change any classifier in MyFPL’s structure that might be a node’s type. We just have to define a meta-type for the annotation itself — let’s call it Reduction.

Reduction annotation annotates and also contains ValueA Reduction annotation annotates a Value node, and has a result containment: the result of reducing the annotated Value node. In other words a figure:

Reduction – as an annotation of a reduced Value node – holds the reduction’s result Value nodeWe expand MyFPL’s structure accordingly by appending the following code to the packages/build/structure.ts file:
const Reduction = factory.annotation("Reduction").annotating(Value) âť¶
factory.containment(Reduction, "result").ofType(Value) âť·Defining the Reduction annotation (in the packages/build/structure.ts file)
- Create an annotation named
Reduction, and declare that it annotates nodes ofValue. - Create a (required) containment on
Reductionwith the reduction’s result, again of typeValue.
Running the generate NPM task of the build package produces a TypeScript class for the Reduction annotation.
Adding annotation nodes to the AST
Now that we have a Reduction class to instantiate, we can create a function that adds the reduction for each value in a Program:
export const addReductionsTo = (program: Program) => {
for (const value of program.values) {
const reductionAnno = Reduction.create(nanoid()) âť¶
reductionAnno.result = reduced(value) âť·
value.addAnnotation(reductionAnno) ❸
}
}Adding reductions to a Program AST (in the packages/my-fpl/reducer.ts file)
- Create a
Reductionnode. (I happen to like the postfix “Anno” as abbreviation.) - Call the reducer with the current
value, and set the annotation’sresultcontainment to the reduced value. - Add the reduction annotation to the
valuenode.
Now we just have to call this function with a program — e.g., our example program. We do that by adding the following line of code to the packages/my-fpl/example.ts file, before the invocation of the writeFileSync function in that file:
addReductionsTo(myProgram)Because we add the reductions of all (top-level) values of a Program to those Value nodes, we should call addReductionsTo only once!
Retrieving and visualizing the annotation
Next, we have to change the implementation of the Projection function, so that it doesn’t call the reduced reducer function itself on a Value AST node, but retrieves the reduced value from that node. Retrieval can be implemented as follows:
const reductionOn = (value: Value) =>
value.annotations.find((anno) => anno instanceof Reduction)?.resultRetrieve a reduction from an AST node (in the packages/my-fpl/reducer.ts file)
This function either returns the first – and presumably only – Reduction annotation node, or undefined. That means that only the first Reduction annotation node added to the node gets returned. Append the code from the listing above to the packages/my-fpl/reducer.ts file.
Finally, replace the following code in that same function and file:
<div className="value">
<Projection node={reduced(value)} />
</div>
with the following code:
{reductionOn(value) && âť¶
<div className="value">
<Projection node={reductionOn(value)!} /> âť·
</div>}
- This is a bit of “standard React idiom”. React doesn’t render
undefinedvalues (as returned from{…}expressions within JSX syntax), and also doesn’t complain — not by throwing anError, nor by writing something to the JS console. JavaScript’s&&and-operator “short-circuits” onundefined(and more generally: “falsy”) values: evaluatingundefined && <RHS>just yieldsundefinedwithout evaluating<RHS>. On the other hand:<object> && <RHS>yields<RHS>because<object>is “truthy”. So,{<LHS> && <JSX content>}renders<JSX content>when<LHS>is truthy, and nothing when<LHS>is falsy.Also make sure to import thereductionOnfunction fromreducer.tsinprojection.tsx. - The return type of the
reductionOnfunction isValue | undefined, but at this point in the code we can assume thatreductionOn(value)is notundefined, because of the short-circuiting behavior of&&. So, the use of TypeScript’s non-null assertion operator!is warranted here.
© 2026 Meinte Boersma (DSL Consultancy)