sUTL

sUTL began as an attempt to solve a very ordinary problem: taking structured application data and turning it into some other structured shape without writing piles of one-off tree-walking code.

The route to it was less ordinary. Before sUTL there were three versions of bOTL, a deliberately limited transformation language inspired by Mustache. Behind bOTL was a more general observation about JSON, Python and JavaScript: there is a useful data model underneath all of them that does not really have a satisfactory name. Behind sUTL was a second observation, borrowed from Lisp: if programs and data share a representation, surprisingly powerful things become possible.

The result was a small, pure, Turing-complete language whose programs, inputs and outputs all inhabit the same structured value model.

Ten years later, after recovering the old sUTL website, implementations and examples, I think the underlying idea may be more relevant than it was when I first built it.

This is the story of how it got there, what has survived, and what a modern sUTL might look like.

Before sUTL: bOTL and the Mustache idea

I had been working on data transformation languages for several years before sUTL.

The first three versions were called bOTL, the bOTL Object Transformation Language. They all explored the same basic idea: take the philosophy of a text templating language such as Mustache and apply it directly to structured values instead of strings.

A text template usually resembles the text it is intended to produce. Given some data, parts of the template are replaced, repeated or omitted. This is powerful precisely because the template is not a general-purpose program. It is mostly a picture of the desired output with a small amount of behaviour embedded in it.

bOTL tried to do the same thing for structures made out of maps, arrays and simple values.

For example, rather than writing imperative code that created an output object, traversed the source, copied values into the right places and accumulated arrays, a transform could itself resemble the desired result:

{
    "name": "#$.person.name",
    "addresses": [
        {
            "_type": "#",
            "path": "$.person.addresses[*]",
            "transform": {
                "street": "#@.street",
                "town": "#@.town"
            }
        }
    ]
}

The third version of bOTL was the cleanest expression of that idea. I described it at the time as “Mustache for JSON” and, more precisely, as a logic-less template system for MLSNBN.

That version was deliberately constrained. It used JSONPath for selection, sections for repetition and scope, and recursive structural templates for construction. It was intended to be easy to understand, safe, portable and obviously not a general-purpose programming language.

That simplicity was attractive.

Then I tried to use it for more serious work.

The problem was not that bOTL lacked one particular feature. The problem was that real data transformation is computational. Once transformation requirements become interesting, people want to map, filter, fold, compare, branch, calculate, recurse, reuse transformations, parameterise them and compose them.

A deliberately non-programmable DSL then has two choices.

It can refuse those requirements, in which case it remains elegant but becomes useful only within a narrow envelope.

Or it can keep adding special cases: a traversal form here, a conditional rule there, a merge operator, matching rules, special deletion semantics, another kind of section. Eventually the supposedly simple language becomes a programming language in denial, only without the regularity and expressive power of a language that was designed as one.

I had already pushed earlier bOTL versions in that direction. bOTL 3 was an attempt to return to the clean Mustache philosophy.

Using it convinced me that the limitation itself was the problem.

The next question was therefore not “what feature should bOTL gain?”

It was:

What if this were a real programming language?

The missing name under JSON

There was another idea running through the bOTL work.

The value model is MLSNBN:

Map, List, String, Number, Boolean, Null.

The name is there so the model can be discussed without continually saying “JSON” when that does not actually mean JSON syntax.

JSON is, strictly speaking, a textual syntax. A program parses JSON text and obtains ordinary host-language values. In Python those values are typically dictionaries, lists, strings, numbers, booleans and None. In JavaScript they are objects, arrays, strings, numbers, booleans and null.

Most application code does not spend its time manipulating JSON text. It manipulates those values.

A YAML document can produce substantially the same values. So can MessagePack or CBOR, within an appropriate subset. A programmer can construct the same structure directly using Python literals or JavaScript object literals without JSON being involved at all.

So there is a useful abstract value model hiding underneath the serialization formats and host languages.

Formally:

MLSNBN value :=
    Map<String, MLSNBN value>
  | List<MLSNBN value>
  | String
  | Number
  | Boolean
  | Null

That simple definition is useful.

It tells an implementer exactly which subset of a host language matters. It distinguishes the semantic data model from its encodings. It also leads to clearer descriptions of systems that we usually call “JSON APIs”.

An API may use JSON on the wire, but often its real contract is better thought of as MLSNBN:

HTTP bytes
    ↓ JSON decoding
MLSNBN value
    ↓ application semantics

The same API could use another transport encoding without fundamentally changing the value model it exposes.

This distinction mattered to sUTL from the beginning.

sUTL does not fundamentally operate on JSON strings.

It operates on structured values.

JSON is merely one convenient serialization.

Lisp: programs made from ordinary data

The next influence was Lisp.

I am not, and was not, a serious Lisp programmer. What influenced me was a particular idea from Lisp that is powerful even before one knows much Lisp: programs and data can share a representation.

Paul Graham’s 2001 essay The Roots of Lisp was important here. Graham was explaining the remarkably small semantic core behind Lisp and McCarthy’s insight that lists could serve as the representation of both programs and data.

Peter Norvig later demonstrated the same economy from another angle in How to Write a (Lisp) Interpreter (in Python). Lispy maps Scheme concepts onto ordinary Python values and implements a useful evaluator in a remarkably small amount of code.

The lesson I took from work like this was not “parentheses are good”.

It was that a small evaluator over a small data universe can produce a surprisingly powerful language.

The traditional Lisp move is roughly:

program :=
    atom
    list(program...)

and because lists are also ordinary data, programs can be quoted, inspected, constructed, rewritten and evaluated.

That idea becomes especially interesting if the native data universe is not Lisp’s list-centric one, but MLSNBN.

Maps are already first-class application structures. Lists are already first-class. Strings, numbers, booleans and null are already there. Python and JavaScript both have natural native representations for all of them.

So I did not want to implement Lisp in JSON.

I wanted to ask a different question:

What would a Lisp-like language look like if it were designed natively for the MLSNBN value model?

That difference is central to sUTL.

A map in sUTL does not normally have to be encoded as a call to a make-map function. A map is already one of the fundamental structures of the language. A transform such as:

{
  "name": "^$.person.name",
  "town": "^$.person.address.town"
}

naturally means “construct a map with these evaluated values”.

The program resembles the result, retaining the best part of the templating heritage, while the language underneath is powerful enough to be a real programming language.

Crockford, JavaScript, Python, and the portable substrate

Douglas Crockford’s early writing about JavaScript and JSON supplied another piece of the puzzle.

Crockford famously described JavaScript as “Lisp in C’s clothing”. Beneath its C-like surface, JavaScript had first-class functions, closures, dynamic values, arrays and objects. It was much closer to the functional and Lisp traditions than its syntax initially suggested.

JSON itself highlighted something else: JavaScript’s ordinary literal structures happened to make a very useful portable representation of structured data.

Python independently had almost the same practical primitives:

Python              MLSNBN             JavaScript

dict       ←→        Map        ←→      object
list       ←→        List       ←→      array
str        ←→        String     ←→      string
int/float  ←→        Number     ←→      number
bool       ←→        Boolean    ←→      boolean
None       ←→        Null       ←→      null

That suggested a language whose program representation did not belong to either host.

A Python implementation could receive a transform as ordinary Python values. A JavaScript implementation could receive the equivalent transform as ordinary JavaScript values. The two could exchange it through JSON if needed, but JSON parsing was not part of the language semantics.

The implementations only had to agree on the value model and evaluation rules.

This was the part that felt more interesting than simply reimplementing Lisp.

Norvig’s Lispy showed how neatly Python could host Lisp’s representation.

I wanted to reimagine the representation itself for the structured-data domain.

The synthesis: Mustache on the surface, Lisp underneath

The best way I now know to describe the intellectual ancestry of sUTL is this:

Mustache supplied the idea that simple transformations should resemble their outputs and remain simple.

Functional programming, including ideas I associated with Haskell, reinforced purity, immutability and computation as transformation rather than mutation.

Lisp, McCarthy, Graham and Norvig supplied the insight that a small evaluator over programs represented as ordinary data can provide enormous expressive power.

Crockford, JavaScript and Python helped reveal the common structured-value substrate on which such a language could be portable.

The resulting design is not really “Lisp written in JSON”.

It is closer to:

a Lisp-like evaluation model designed natively for MLSNBN.

That is why ordinary maps and lists retain natural structural meaning. It is why quote and evaluation matter. It is why library transforms can themselves be ordinary values. And it is why programs can travel through systems normally intended only for data.

The move from bOTL to sUTL can therefore be summarized very simply:

bOTL transforms are MLSNBN templates. sUTL transforms are MLSNBN programs.

Homoiconicity without a source language

This leads to one of sUTL’s most unusual properties.

A conventional language normally has a textual source representation:

source text
    ↓ parser
abstract syntax tree
    ↓ evaluator/compiler
result

sUTL does not need the first step.

The structured value is already the program:

MLSNBN transform
    ↓ evaluator/compiler
MLSNBN result

You can serialize that program as JSON, YAML or another suitable format. You can construct it directly as Python or JavaScript values. A GUI can build it. An agent can generate it. A database can store it in a structured column.

But those are all ways of constructing or transporting the program. They are not the language itself.

This is a particularly literal form of homoiconicity.

The important claim is not merely the loose slogan “code is data”. All source code is data in some sense.

The stronger property is:

Programs use the same ordinary structural representation as the data those programs can inspect and construct.

For sUTL, source, transform and result all inhabit the same value universe:

source    ∈ MLSNBN
program   ∈ MLSNBN
result    ∈ MLSNBN

A simplified view of evaluation is therefore:

sUTL : source × transform × library → result
       MLSNBN   MLSNBN      MLSNBN     MLSNBN

This has consequences that are easy to underestimate.

Programs can be stored anywhere data can be stored.

Programs can be sent through ordinary API surfaces.

Programs can be versioned, hashed, signed and diffed.

Programs can construct other programs.

Libraries can contain programs as values.

A transformation pipeline can itself be represented as data.

None of this requires a special source-code transport.

Why quote and evaluate matter

The Lisp ancestry becomes most visible around quoting and evaluation.

A programming language that merely embeds a set of special operations in structured data is useful, but it has not yet crossed the important boundary.

The crucial move is being able to say:

Here is an ordinary value. Treat that value as a transform and evaluate it.

That is what makes higher-order construction possible.

Historical sUTL developed !, later !!, quoting and escape-from-quote forms to support this boundary between values-as-data and values-as-programs.

Once that exists, the language closes over its own representation.

A program can construct another transform, pass transforms around, store them in libraries and later evaluate them.

This is also why the old core library is interesting. Operations such as map and filter need not all be privileged syntax baked permanently into the evaluator. They can themselves be written as sUTL transforms.

bOTL accumulated transformation features.

sUTL instead acquired an evaluation model from which features could be built.

That was the real break.

What has survived

The original public sUTL site eventually disappeared, and the online Studio stopped working, but considerably more of the language survived than I expected.

The current reconstruction project has recovered the old Squarespace site, historical documentation, implementations, examples and much of the Studio material.

At the time of writing, the project contains:

  • an evidence-ranked reconstruction of the deployed sUTL0 dialect;
  • an executable conformance corpus with 82 cases: 79 normative, two provisional and one explicitly disputed;
  • a dependency-free Python 3 evaluator;
  • an independent browser/Node JavaScript evaluator;
  • a modern executable guide;
  • 44 complete archived Studio examples recovered from 63 known example IDs, with 19 still missing;
  • frozen results and tooling for comparing historical behaviour.

This is important because no single old implementation can safely be treated as the specification.

The production Python implementation contains some host-language leakage and historical defects. The handwritten JavaScript implementation represents an earlier stage of the language. The Haxe-generated JavaScript used by Studio includes later features but also its own bugs.

The reconstructed dialect therefore treats the surviving documentation, examples and implementations as evidence rather than assuming any one artifact is canonical.

That separation also matters for what comes next.

The archaeological sUTL0 project should remain an attempt to recover what the language actually was.

Future experiments should not silently rewrite that history.

Why the idea still seems useful

When I originally described sUTL as a transformation language, the obvious comparison was to templating and JSON manipulation tools.

Today I would describe its strongest use case somewhat differently.

sUTL is potentially a portable, deterministic, capability-safe computation language for structured values.

That is useful anywhere a system would like to accept computation from another party without accepting arbitrary executable code.

Consider an API returning large records.

A conventional endpoint may support projection:

GET /customers?fields=id,name,email

But projection only selects fields.

Instead, the endpoint could accept an sUTL transform and apply it to each record before returning anything.

The caller could reshape nested structures, calculate values, normalize data, extract selected descendants or construct an entirely different result.

The computation occurs near the data, so the service need not transmit a huge record when the caller only needs a small derived value.

Yet the caller has not been allowed to upload arbitrary JavaScript or Python.

The transform can only compute from values it has been given.

This is safe computation pushdown.

It is particularly attractive for agents. An agent receiving a 200 KB API response may only need a 2 KB derived structure. Rather than transferring everything and spending model tokens interpreting it, the agent could construct a small transform and ask the service to execute it locally.

The same idea applies to stored transformations.

A pipeline service might allow a caller to upload and persist a transform:

incoming record
      ↓
stored transform A
      ↓
stored transform B
      ↓
destination

The stored stages are executable, but they are not arbitrary foreign code.

Because they are pure transformations over MLSNBN, the runtime does not need to give them filesystem access, sockets, environment variables, process creation, credentials or reflection.

They can be versioned as data, replayed against historical inputs and moved between compatible runtimes.

That is a much smaller security problem than hosting general-purpose scripts.

Active data

There is a related idea that I find even more interesting now.

Instead of storing only a value, a system can store a transform that evaluates to that value.

Literal data is then simply the trivial case of a transform.

More interesting stored values can contain derived structure:

plain data
    ↓
data with calculated pieces
    ↓
parameterised document
    ↓
transformation template
    ↓
full computation

A configuration document could be a transform that evaluates against an environment to produce concrete configuration.

A stored customer “view” could be a transform evaluated against raw customer data.

An API representation could be stored as a transformation rather than as duplicated materialized data.

An invoice definition could be a transform that produces an invoice value from its context.

Because the program is itself MLSNBN, all of these definitions remain ordinary structured values at rest.

This is something Lisp made conceptually natural, but MLSNBN makes unusually interoperable.

Purity should remain non-negotiable

One temptation would be to make sUTL more useful by adding impure builtins:

http_get
read_file
database_query
now
random

I think that would destroy one of its best properties.

The useful invariant is:

A sUTL transform cannot do anything except compute a value from values it has been given.

That does not mean a larger system built around sUTL cannot perform effects.

The right model is much closer to a modern LLM tool harness.

If a program needs an HTTP operation, the transform can emit an MLSNBN value describing the requested operation:

{
  "$effect": "http",
  "args": {
    "method": "GET",
    "url": "https://example.com/customer/123"
  }
}

The sUTL evaluator has still done nothing impure.

An external harness can inspect that value, decide whether the operation is allowed, apply authentication and policy, execute it, and pass the MLSNBN result into the next transform.

The execution model becomes:

input
  ↓
pure sUTL
  ↓
effect description
  ↓
external harness
  ↓
effect result
  ↓
pure sUTL

This cleanly separates describing an effect from having authority to perform one.

The same transform can therefore run under a test harness, a browser harness, a locked-down server harness or an agent harness with different effect policies.

Effects remain inspectable, auditable and replayable.

sUTL stays pure.

Turing completeness and resource safety

Purity gives strong capability safety, but it does not by itself solve resource safety.

A Turing-complete transform can still loop forever, recurse excessively, traverse large structures repeatedly or construct enormous intermediate results.

A modern runtime should therefore be metered.

The natural model is fuel or another deterministic execution budget.

For example, an execution environment might enforce limits on:

evaluation steps
recursion depth
intermediate allocation
output size
wall-clock time

Every operation consumes some amount of budget. When the budget is exhausted, evaluation terminates with a resource-limit result.

This is a much simpler security boundary than trying to sandbox arbitrary JavaScript.

It also preserves the lesson learned from bOTL.

The answer to computational complexity was not to cripple the language until difficult transformations became impossible. It was to make the language expressive, then control the execution environment explicitly.

The performance problem

Historical sUTL could become slow when transforms became complicated.

Looking at the old implementations now, this is not mysterious.

They are fundamentally tree-walking interpreters.

During execution they repeatedly inspect the shape of transforms, parse compact path strings, construct small maps for builtin calls, copy scopes, resolve libraries by name, create intermediate arrays and recursively invoke the general evaluator.

A reduce over many items can repeatedly rebuild scope and re-enter the interpreter for the callback transform.

Nested library transforms multiply that cost.

The language itself is not inherently condemned to that performance profile.

A modern implementation should compile.

Compile the program, not the syntax every time

The compiler would lower a transform into an intermediate representation or bytecode, so paths, builtins and library references are resolved once rather than rediscovered on every evaluation.

The source representation should remain MLSNBN.

But execution could look like:

MLSNBN sUTL
    ↓
validate
    ↓
compile
    ↓
internal IR / bytecode
    ↓
execute many times

A transform such as:

{
  "name": "^$.person.name",
  "score": ["&+", "^$.a", "^$.b"]
}

might compile conceptually into:

MAKE_MAP 2
  KEY "name"
    PATH_HEAD SOURCE ["person", "name"]
  KEY "score"
    ADD
      PATH_HEAD SOURCE ["a"]
      PATH_HEAD SOURCE ["b"]

The compiler resolves once what the old interpreter rediscovers on every evaluation.

Paths should be compiled once rather than repeatedly split and interpreted.

A head path such as ^$.person.address.street should walk directly to the first result rather than constructing intermediate lists and taking element zero afterwards.

Wildcard paths can use iterators rather than materializing a new array at every step.

Scope can be represented by cheap frames or slots rather than copied maps.

Library references can be resolved during compilation.

Constant expressions can be folded.

Pure repeated subexpressions can potentially be memoized.

Core library transforms can themselves compile to the same IR, and selected stable operations could eventually be intrinsified where doing so preserves exact semantics.

For static transforms applied to many records, compilation changes the economics dramatically:

receive transform once
        ↓
validate + compile + cache
        ↓
execute against 500,000 records

The cost of understanding the program is paid once.

Native code generation is also plausible

An even more aggressive tier could emit JavaScript and let a modern JIT optimize the result.

A simple compiled sUTL transform might ultimately become code not far from:

function transform(src) {
    return {
        name: src.person?.name ?? null,
        total: src.a + src.b
    };
}

The language’s metaprogramming means not every transform can always be statically reduced that far. A program may construct a new transform dynamically and evaluate it.

But even that can be handled with compile-and-cache:

constructed transform
      ↓
canonical hash
      ↓
already compiled?
    ↙       ↘
  yes       no → compile
    ↓
 execute

A sensible implementation could therefore have several tiers:

MLSNBN sUTL
    ↓
compiled IR / bytecode
    ↓
optional generated host code

The important optimization is architectural, not merely rewriting the old interpreter in a faster systems language.

Historical sUTL and a future sUTL

I would keep two projects conceptually separate.

The first is recovered sUTL0.

Its purpose is archaeological: preserve the site, examples, implementations and actual deployed semantics; document disagreements honestly; and make the historical language executable again.

That work should remain conservative.

The second could eventually be a new language version or implementation project whose premise is:

Programs remain MLSNBN.
Evaluation remains pure and deterministic.
The language remains capable of expressing real computation.
Implementations are designed around compilation, metering, validation and generated programs.

It could keep the aspects of sUTL that now seem strongest:

  • programs, inputs and results share the MLSNBN value model;
  • ordinary maps and lists retain natural structural meaning;
  • simple transforms still resemble the result they produce;
  • transforms are portable values rather than source-code strings;
  • programs can construct and evaluate programs;
  • there are no ambient side effects;
  • external effects are described as values and performed only by a harness.

The implementation could then modernize aggressively underneath those semantics.

A language for moving computation through data systems

When sUTL was first built, “a transformation language for JSON-like data” was a reasonable description.

I now think that description misses the most interesting property.

The world has standardized heavily around MLSNBN-shaped data. HTTP APIs, document databases, message queues, event systems, configuration files and agent tool protocols continually move these structures around.

At the same time, there is increasing value in safely moving small pieces of computation closer to the places where data lives.

Usually those two worlds are separated.

Data is portable and safe but passive.

Programs are expressive but require special transport, execution environments and security boundaries.

sUTL sits in an unusual place between them.

A transform is an ordinary structured value.

It can pass through an API that only knows how to carry data.

It can sit in a database record.

It can be emitted by an agent.

It can be inspected before execution.

It can be hashed, cached and versioned.

Yet when handed to an evaluator it is a real program.

That was already implicit in the original design.

The modern opportunity is to take that property seriously.

Perhaps the best description of the idea is no longer “JSON transformation language” at all.

It is:

sUTL is a pure programming language native to MLSNBN: a way to make structured data computational without turning it into arbitrary host-language code.

That is what bOTL was slowly reaching toward.

It is what Lisp made imaginable.

It is what Python and JavaScript made portable.

And, with agents now routinely generating structured calls and programs being pushed ever closer to data, it may be a more useful idea today than it was in 2015.


Further reading and surviving sources

Historical background:

Surviving code and reconstruction:

See it working

From theory to executable language