Organizing the structure aspect

Share
đź’ˇ
This is post 05 in my blog series on MyFPL — see the index/table of contents.

The code in this and previous blog posts can also be found here, on Codeberg. Check out the repo at the commit marked “blog 05”.

This blog post will explain how I’ll organize MyFPL’s structure aspect. Specifically, I’ll explain the four central entities or meta-types of it. They’re called meta-types, because they’re “meta” to every in an AST, as well as to distinguish them from types defined by, and in, MyFPL itself.

Most – but not all – programs usually perform some kind of computation involving values. Such values typically are strings, numbers, booleans, enumerations, objects, arrays of those, and objects composing such values. These are the types of our language. So, we have the following relation between value and type.

Relations between Value and Type

I’m (ab-)using UML’s class hierarchy diagram for this — thanks to PlantUML. The interfaces named Value and Type correspond to the terms “value”, and “type”, respectively. I’ll keep using this convention. Arrows with dotted lines purely express a functional relation between entities through the text on those lines.

đź’ˇ
From now on, I’ll continue using the term “value” instead of “instance”, where it pertains to the MyFPL language itself. I do that because I think that that term implies the immutable nature of values in a natural way, whereas “instance” doesn’t. After all, I’m building a functional programming language, and immutability is a staple of such languages. And for good reasons: it makes programs much, much, much easier to reason about.

The perfect tongue-in-cheek quote that embodies that, is:

“Immutability changes everything” — Pat Helland, CIDR 2025, through Jennek Geels

I’ll keep using the term “instance” for instances of actual (TypeScript/UML/etc.) classes.

Next, we can think of how values of types are put into programs. The obviously most useful way to do this, is to run the program, at the same time providing it with input. But often, we also need to put literal values directly into the program’s code. Instead of relying on some external input providing those values, we use literals. As an example: the string "foo" is a literal for the value of the type string.

So, we now have the following relations between type, value, and literal:

Relations between Value, Type, and Literal

Then, we can start thinking about how computation is expressed. Computation is typically undertaken by performing operations: an operation takes in a number (including 0 in rare cases) of operands (which are values of types), and returns a result (which is again a value of some type).

So, we have the following relations between type, value, and operation:

Relations between Value, Type, Literal, and Operation

We can even slightly improve on this diagram by noting that because an Operation returns a value of some type, we can consider it to be a Value in its own right:

The base interfaces, including functional relations

When adding constructs to the MyFPL language, we’d typically add a concept that either implements Value or its Operation specialization. When adding types to the MyFPL language, we’d typically add a concept that implements Type, and quite likely a concept that implements Literal.

đź’ˇ
Every Operation is already a Value, so what’s the worth of adding the Operation interface? This is a valid question.

LionWeb

LionWeb is an initiative with the mission to create an ecosystem of interoperable components for building language-oriented modeling tools on the web. It currently consists of the following pieces:

  1. A specification for a JSON serialization format for models.
  2. A meta-metamodel – LionCore â€“ to define LionWeb languages with: metamodels that models conform to.
  3. Specifications for protocols to manipulate models: bulk and delta.

One of the core principles of the LionWeb initiative is to back these specifications up with working – but possibly prototypal – implementations. One of these implementations is the TypeScript one, which is published as a collection of NPM packages whose names are prefixed with @lionweb/. We’ll be using this implementation for the implementation of MyFPL.

A LionWeb language – i.e., an instance of the Language meta-meta-type from LionCore – has some identifying metadata, and consist of any number of entities. (We call this a meta-meta-type, because it’s a type from the meta-metamodel.) The most important kinds of entities:

Classifier

These come in the flavors of concepts (and “sub”-flavors abstract or concrete) and interfaces. A classifier has any number of features, which are either properties (for boolean- or string-typed values), containments (for children), and references.

Enumeration

A fixed collection of enumeration literals.

For more in-depth information about LionCore, see Introduction to languages and its specification. (Full disclosure: I’m one of the core members of the LionWeb team.)

Mapping to LionWeb

I’d like to map this type hierarchy to a LionWeb language. More precisely, we want to construct a LionWeb language that corresponds to MyFPL’s structure or metamodel. The entities (meaning concepts, interfaces, enumerations, etc.) in that language will correspond to the last diagram above.

We construct the LionWeb language using a bit of TypeScript code. Editors exist for LionCore – LionWeb’s meta-language – but I’ll avoid setting up another dependency at this point.

Constructing a LionWeb language

We need a small amount of boilerplate to be able to construct a LionWeb language:

Listing 5. 1. Start of the packages/build/src/structure.ts file

import { LanguageFactory } from "@lionweb/core" (1)
import { concatenator, lastOf } from "@lionweb/ts-utils"

const factory = new LanguageFactory("MyFPL", "0", concatenator("-"), lastOf) (2)

export const myFplStructure = factory.language (3)
  1. Import some members of the LionWeb NPM packages. This relies on having executed npm add @lionweb/core and npm add @lionweb/ts-utils before.
  2. Instantiate a LanguageFactory which is a helper class to build languages in a convenient way. This call also configures the name of the language ("MyFPL"), its stated version ("0"), and how the LionWeb IDs and keys of entities and their features are constructed from their names.
  3. Expose the constructed LionWeb language after extracting it from the factory as myFplStructure.

Next, we define the interfaces Type, Value, and Literal and Operation (that all extend Value):

Listing 5. 2. Constructing the Value, Type, Literal, and Operation classifiers (continuation of the packages/build/src/structure.ts file)

export const Value = factory.interface("Value") (1)
export const Type = factory.interface("Type")
export const Literal = factory.interface("Literal").extending(Value) (2)
export const Operation = factory.interface("Operation").extending(Value)
  1. Construct interfaces (as instances of LionWeb’s Interface type) named Value and Type.
  2. Construct interfaces named Literal and Operation, and extending the Value interface.

We also export all of these types, in case we need to reference them explicitly later on.

Generating from the LionWeb language

Having a LionWeb language is not enough: we also need to generate some code from it.

Listing 5. 3. Generation of code (and text) in the packages/build/src/build.ts file:

import { writeFileSync } from "fs" (1)
import { generateLanguage } from "@lionweb/class-core-generator" (2)
import { LionWebVersions } from "@lionweb/core"
import { generatePlantUmlForLanguage, languageAsText } from "@lionweb/utilities"

import { myFplStructure } from "./structure.js" (3)

generateLanguage(myFplStructure, "../my-fpl/src", LionWebVersions.v2023_1) (4)

writeFileSync("artifacts/structure.txt", languageAsText(myFplStructure)) (5)
writeFileSync("artifacts/structure.puml", generatePlantUmlForLanguage(myFplStructure))
  1. Import the writeFileSync from Node.js’ standard library. This relies on having executed npm add --save-dev @types/node before.
  2. Import some (more) members of the LionWeb NPM packages. This relies on having executed npm add @lionweb/class-core-generator and npm add @linoweb/utilities before.
  3. Import MyFPL’s structure (constructed as a LionWeb language) from the structure.ts file.
  4. Generate a file with an implementation of that language in the form of TypeScript types (classes, interfaces and their members/features, and enumerations). The generated file also contains a MyFPLBase class that encodes a copy of the MyFPL’s structure LionWeb language, and provides a couple of other reflective capabilities.
  5. Generate files under packages/build/artifacts containing a textual and graphical rendering of MyFPL’s structure LionWeb language.

We can run this code generation as follows (starting from the repo’s root):

cd packages/build
npm run generate (1)
  1. Run the code generation as the NPM task generate defined in package.json.

This produces the TypeScript file packages/my-fpl/src/MyFPL.g.ts. (The .g infix in the .g.ts file extension indicates it’s a generated TypeScript file.) It also should have a class MyFPLBase, which defines a copy of the MyFPL LionWeb language, but in a different way than we did in packages/build/src/structure.ts file.

Right now, this generated file should have interfaces Value, Type, Literal, and Operation. It has no classes yet, but that’ll change: every concept we’ll define later on will result in a class in this file. Each instance can serve as a node in an AST.

The packages/build/artifacts/structure.txt file contains a “textualization” of the MyFPL LionWeb language, and should look as follows:

language MyFPL
    version: 0
    entities (↓name):

        interface Literal extends Value

        interface Operation extends Value

        interface Type

        interface Value

This is a convenient, human-readable rendering of the language in a textual format, rather than as JSON. This is a “one-way syntax” in the sense that no parser for it has been implemented. In particular, details like IDs and keys are missing so the information in this format is anyway incomplete.

Also have a look at the packages/build/artifacts/structure.puml, and verify it replicates the last PlantUML diagram above, though without the arrows that express a functional relation between entities.

Expanding the language

We’ll be adding plenty of concepts – i.e., instances of the Concept meta-meta-type from LionCore – that implements Value (either directly or indirectly) along the way that correspond to constructs of MyFPL. A construct of MyFPL will typically correspond to a concept in the MyFPL LionWeb language — sometimes more than one. Such a concept either implements Value directly, or indirectly by implementing Literal or Operation. Types are going to correspond to concepts that implement Type. (Where appropriate, we’ll add interfaces as well.) Data on MyFPL constructs or types end up as features of the corresponding concepts.

Any programming language has many constructs, which we can categorize in various “areas”, e.g.:

  • Literals (which are constants): 0, "foo", etc.
  • Operations on values of various specific types
  • Declarations and definitions of named values, including functions
  • Flow control: if <condition> then <consequence_1> else <consequence_2>
  • Function invocation (f(…), AKA function call)
đź’ˇ
In this blog post, we’ve learnt how to organize (and implement) MyFPL’s structure aspect. In the next blog post, I’ll implement the notion of booleans.

© 2026 Meinte Boersma (DSL Consultancy)