The .em format
bitterasm compile writes a program’s emitted values to a .em file, and
an evaluator such as bitter reads it. This is the contract between them;
anything that reads .em should follow it.
A .em file is one JSON object:
{
"version": 1,
"requires": ["sections", "extern-labels"],
"module": "spec",
"exports": { "start": 0 },
"entries": [
{ "kind": "Struct", "id": "std.binary.bits",
"args": [{ "kind": "Const", "value": "8" }],
"fields": [["value", { "kind": "Int", "value": "7" }]],
"section": ".text" },
{ "kind": "Enum", "id": "spec.Mode", "args": [], "variant": "Slow",
"payload": { "kind": "Int", "value": "3" }, "section": ".text" },
{ "kind": "Deferred", "module": "spec_dep", "symbol": "far", "section": ".text" }
]
}
versionis1. It changes only when the file’s structure changes incompatibly. A reader must refuse any version it doesn’t know, and must refuse the unversioned plain-list files older compilers wrote.requireslists the language features the program actually uses. A reader must refuse a file that requires a feature it doesn’t know, because ignoring one produces wrong output with no error:sections: some entry has asection. Lay entries out grouped by section name, sections in order of first appearance, entries within a section in file order.extern-labels: some value is aDeferred(see below), which only a linker with the other file’s.emcan resolve.
moduleis the compiled file’s module path: its path relative to the deepest search root containing it, with dots (examples.x86_64.hello). A file under no search root is named relative to the working directory, with one leading.per level up plus one (..shared.util).exportsmaps each top-levelpublabel to its position: how many entries precede it.entriesis the emitted values, in emission order. Each is one of these, tagged bykind, plus an optionalsection:Int:valueis a decimal string, since integers are unbounded.Struct:id, genericargs, andfieldsas[name, value]pairs in declaration order.Enum:id, genericargs,variant, and an optionalpayload.Deferred: the value ofpublabelsymbolin the file whosemoduleis given, not known until link time.
A generic argument is {"kind": "Const", "value": "8"} or
{"kind": "Type", "type_kind": ..., ...}, where the type is
{"type_kind": "Builtin", "name": "int"}, or Struct/Enum with an id
and its own args.
Ids. A struct or enum’s id is its declaring module’s path plus its
name: std.binary.bits, spec.Mode. Ids are unique within one program, and
they’re what an evaluator matches on. Which ids an evaluator gives meaning
to is up to that evaluator. bitter understands std.binary.bits,
std.bitter.byte_order.LittleEndian, and std.bitter.deferred’s
Positioned, Deferred, BinOp and Op, and packs any other struct as
the concatenation of its fields.
A later version-1 file may add top-level fields that a reader can safely
ignore. Anything a reader must understand to produce correct output is
either a new requires feature or a new version.