sysl

json

Recursive ownership — a value that contains itself through &T, and the program that found the language could not build a string.

A JSON reader and writer, checked by round-tripping: parse a document, render it, parse the result, and assert the two trees agree.

The axis: recursive ownership. A JSON document is the smallest honest example of a value that contains itself — an array holds values, and a value may be an array — so the type cannot be written at all without an indirection, and every walk over it has to take the reference apart:

enum Json
    Null
    Bool(b: bool)
    Num(n: long)
    Str(s: string)
    Arr(items: &Buf[&Json])
    Obj(fields: &Buf[Field])
end Json

Field is a struct holding a name and a &Json, and the & on that value is the edge that makes the cycle finite.

Numbers are integers, and that is a deliberate narrowing rather than a shortcut. A round trip is the assertion the program is checked by, and a float would make the check about the renderer’s digits instead of about the tree. Floats are another program’s axis.

What it found

A pattern does not reach through a reference. Field selection does — p.x works on a &T — so the asymmetry is a thing to learn rather than to guess. Every walk in the program starts *j match, which is the rule the reference now states outright: selection dereferences one level on its own and match does not, because “am I matching the reference or the thing” should stay a visible question.

There was no way to build a string out of bytes a program computed. This is the big one. The only sources of a string were a literal, a slice of an existing string, concatenation, and str — so unescaping was written as “copy the source between the escapes, and append a literal for each”, and \uXXXX reached text only through str(char(n)), a rendering function standing in for a constructor.

Answered by from_utf8, which validates a []u8 into a string. parse_string now gathers into one sink and converts once at the end, and an escape writes the bytes it encodes rather than being rendered into a string that is immediately concatenated away. The structure that had been forced is now the structure that is wanted — which is the best outcome a finding has.

There were no constants. A value shared by several functions had to be a nullary function. const was added afterwards, for a reason this program did not turn up on its own: a nullary function reads perfectly well in an expression, and what it cannot do is be an array bound.

There was no growable collection. An array and an object were each a linked list built by appending through the tail reference — honest for a tree of references, and not what the data is. Answered by Buf[T] reaching the standard library — sysl.buf, which a program asks for by name rather than getting for nothing: an array became a &Buf[&Json] and an object a &Buf[Field]. That deleted two list-cell structs, both tail-threading loops, and the two recursive renderers.

Worth noticing

The depth limit is private const max_depth: int = 64, and the finding above is why it can be. What a nullary function could never be is an array bound, which is the case that motivated the form: as declarations puts it, a const is folded into every use and has no address, and being usable where a constant is demanded is exactly what that buys.


Source · Next: hashmap — the trait system under load.

Search

Esc
to navigate to open Esc to close