- Larva
- How Larva works
- Language packs
Language packs
How Larva loads a programming language - the pack folder, its manifest, the JSON requests of ABI 1, signatures and verification, registries, and project templates written in HIR.
On this page
Larva contains no programming language. larva knows Larva HIR, its own tools and the .queen project format, and nothing about the syntax of any source file. Each language is a language pack: a folder that holds the language's frontend (source to HIR), its view (HIR written back as that language's source) and its editor services, plus a manifest, language.json.
Antril is one such pack. The same larva builds, explains and edits any language whose pack is installed, and a library written in one language is used from another as a compiled .dll (see The .lva format).
larva (one process)
┌──────────────────────────────────────────────────────────────┐
│ build / lib / run / new / bind / types / show / search / lsp │
│ │ │
│ │ JSON request {"method": "compile", "params": {…}} │
│ ▼ │
│ LanguagePack.Call ──► larva_language_call(request, n, &m) ───┼──► pack library (Antril.dll)
│ ▲ │ frontend, view, editor
│ │ JSON answer {"result": …} or {"error": "…"} │ services
│ └──────────── larva_language_free(answer) ◄───────────┼──
│ │
│ HIR linker, lowering, IR, back ends (the language is done) │
└──────────────────────────────────────────────────────────────┘
Why packs
The design document states the goal plainly: Larva is the platform and a language is only a frontend that turns text into typed HIR. Every tool (build, packages, the language server, bindings, the type browser) works on HIR assemblies, so a new language gets all of them by answering a handful of requests. Keeping the language out of larva also means:
- A language ships and versions on its own. The Antril pack lives in its own repository and is loaded at run time;
larvadoes not reference it. larvacan check what it loads. A pack is code that runs insidelarva's process, so every file of it is hashed in its manifest and the manifest can be signed.- A program has one source language, and other languages enter as libraries.
LanguageRegistry.Forrefuses a build whose sources belong to two packs: "the sources are in A and B: a program is compiled from one language (the others' code as libraries, .dll)".
What a frontend implements
A pack is one library with two exported functions (the ABI) that answers requests. What each request asks of the language:
| Request | What the language does | Used by |
|---|---|---|
info |
Says what it is: name, extensions, keywords, the editor's token types, the HIR format it writes. | Loading, larva lsp (token legend), larva bind (reserved words) |
compile |
Source files to one HIR assembly (.lva bytes) and diagnostics. |
larva build, run, lib, types/show/search with sources, larva new (templates from sources) |
source |
An HIR assembly written back as the language's source, split into files. | larva bind --source, larva new |
catalog |
Runs types, show or search through the language's view. |
larva types, show, search |
lsp.* |
Editor features over one document. | larva lsp |
The language decides names, overloads, types, inference and its own sugar. It emits HIR that is already typed and resolved. How a virtual call dispatches, how a closure is built, how async becomes a state machine: Larva decides that during lowering, the same way for every language.
The view and the editor services are mostly shared code. A pack builds them on Larva.Hir.Services (a LanguageView subclass, a HirCatalog, a LanguageService and an IDocumentAnalysis of its own): see The catalog and language views. Antril's pack is AntrilLanguage (the request dispatcher) and LanguageExports (the two exported functions) in src/Antril/Pack/ of its repository.
The ABI
The manifest's abi names the version of the requests a pack answers. This larva makes the requests of ABI 1 (LanguagePack.Abi = 1); a pack with another abi is not loaded.
ABI 1 is two C functions, found by name in the pack's library:
/* One request: `request` is UTF-8 JSON of `length` bytes. The answer is UTF-8 JSON,
allocated by the pack; its length goes to *resultLength. */
uint8_t *larva_language_call(const uint8_t *request, int32_t length, int32_t *resultLength);
/* Releases an answer larva has read. */
void larva_language_free(uint8_t *result);
The request is always an object with the method and its parameters:
{ "method": "compile", "params": { "name": "Program", "sources": [{ "path": "C:/app/main.ant" }], "references": [], "library": false } }
The answer is {"result": …} or {"error": "message"}. LanguagePack.Call serializes the request, calls larva_language_call, parses the answer, calls larva_language_free on the buffer (always, even when the answer is not JSON), and turns an error into a LanguagePackException whose message larva prints. The Antril pack answers one request at a time (it takes a lock around its dispatcher) and turns any failure into an error answer, so nothing unwinds across the boundary.
Assemblies in requests
Many requests carry HIR assemblies (System.Core, the libraries a project names, a program just compiled). An assembly travels in one of two forms:
| Form | Meaning |
|---|---|
{"path": "C:/…/System.Core.dll"} |
A .dll or .lva file. The pack reads it and keeps it while the file does not change (Antril keys its cache by the file's write time and length), so the library is decoded once per larva lsp session, not once per keystroke. |
{"data": "TFZBBcMB…"} |
The assembly's bytes in base64: a program compiled a moment ago, never written to disk. |
LanguagePack.Reference(path) and LanguagePack.Reference(bytes) build these two forms.
Every request and its fields
The fields below are what larva sends and what it reads back. A pack may answer more (Antril adds types to compile's answer, which larva lib prints).
info
Sent once, when the pack is loaded (LanguagePack.Load). Takes no parameters.
| Answer field | Meaning |
|---|---|
name |
The language's name. |
abi |
The requests it answers (1). |
hir |
The .lva format it writes. If it differs from LvaFormat.Version (5), loading fails: "the language pack X writes Larva libraries of format N; this larva reads format 5". |
extensions |
Its source extensions ([".ant"]). |
keywords |
Its reserved words. larva bind renames C names that collide with them (BindingGenerator.Reserved). |
tokenTypes, tokenModifiers |
The semantic token legend for editors. larva lsp concatenates the legends of all packs, each pack's token numbers moved by an offset. |
compile
| Parameter | Meaning |
|---|---|
name |
The assembly's name (the project's PROJ name, Program, or the stem of larva lib -o). |
sources |
[{path, text?}]: the files. text replaces the file's contents (the editor's unsaved text). |
references |
Assemblies to compile against (see above): System.Core first, then libraries. |
library |
true for a library: its source files are named by their folder and file (System.Core/List.ant), not by where they were on disk, because a library leaves the machine it was built on. |
resources |
[{name, data}], data in base64: the project's resources, stored in the assembly. |
external |
Names of referenced assemblies that live in another GC domain (see GC domains). |
options |
What the project sets: {access, borrows, unsafe, safe} (BUILD access.default, whether the borrow checker is on, unsafe: deny, the files it applies to). |
| Answer field | Meaning |
|---|---|
assembly |
The .lva bytes in base64, or null when there are errors. |
diagnostics |
[{text, error, code, file, line, column}]. larva prints each text. |
timings |
{phase: milliseconds} of the frontend's phases. |
source
Writes an assembly as source. larva new uses it to write a template in the chosen language, and larva bind --source to write bindings as readable source.
| Parameter | Meaning |
|---|---|
assembly |
The assembly to write. |
references |
Libraries its types refer to (System.Core), so names resolve. |
files |
{"Full.TypeName": "File.ext"}: which file each type goes to. |
defaultFile |
The file for every other type. |
nonPublic |
Write private and internal members too. |
header |
Write the "decompiled from Larva HIR" header line (larva new turns it off: the result is source to work on). |
The answer is {files: {"File.ext": "text"}}, each file with its own imports at the top, once.
catalog
Runs larva types, show or search through the language's view. The command logic itself is shared (CatalogCommands in Larva.Hir.Services), so the output is the same shape in every language.
| Parameter | Meaning |
|---|---|
command |
types, show or search. |
words |
The command's words (a namespace, type names, a query). |
bodies, nonPublic |
--no-bodies and --public, inverted. |
assemblies |
The catalog: the program (when sources were given) first, System.Core, the --lib libraries. |
The answer is {output, error, exit}: larva writes output to stdout and error to stderr and exits with exit.
lsp.*
larva lsp keeps the protocol, the open documents, the projects and the decompiled files, and asks the pack of each document for its features. The requests:
| Request | Parameters | Answer |
|---|---|---|
lsp.analyze |
uri, path, text; others (the other source paths of its program); open ({path: text} of other open documents); workspace: {key, options, references, foreign: [{name, sources}]} |
The document's diagnostics, as LSP Diagnostics (range, severity, code, source, message). |
lsp.completion |
uri, line, character |
An LSP CompletionList; an item for a type of a namespace not imported yet carries the import edit. |
lsp.hover |
uri, line, character |
{contents: {kind: "markdown", value}} or null. |
lsp.definition |
uri, line, character |
{file, line, character} in the program's own source, or {decompiled: {…}, name} for a library's type (larva writes the text to a read-only file and finds the name in it). |
lsp.signatures |
uri, line, character |
An LSP SignatureHelp. |
lsp.symbols |
uri |
The document's outline (DocumentSymbols). |
lsp.tokens |
uri |
{data}: semantic tokens, five numbers each, relative to the previous one. |
lsp.workspaceSymbols |
query |
[{name, kind, container, file, line, character}], or decompiled instead of a place for library symbols. |
lsp.decompile |
type; bodies, view, assembly, arity, member (optional) |
{assembly, type, extension, text}, plus at, tokens and links for the language's own view, or null. |
lsp.close |
uri |
null: the document's analysis is dropped. |
lsp.reset |
none | null: every workspace and analysis is dropped (a .queen changed). |
workspace.key identifies a project's set of libraries: the pack builds the workspace (System.Core, the project's assemblies, dependencies compiled for another GC) once per key and reuses it for every document of that project. Details are in The language server.
There is no templates request: a template is HIR, and the source request writes it (see Templates).
The manifest
language.json sits at the root of the pack folder. This is the manifest of the Antril pack after larva languages pack with a key:
{
"id": "antril",
"name": "Antril",
"version": "0.8.0",
"description": "Antril for Larva: its frontend (Antril source to Larva HIR), its view (HIR written as Antril) and its editor services",
"author": "Antril Organization",
"license": "Proprietary",
"homepage": "https://antril.org",
"extensions": [
".ant"
],
"icon": "ant-file.ico",
"abi": 1,
"larva": "0.8.0",
"entry": "Antril.dll",
"files": {
"Antril.dll": "cb1cf4db81a4326caf4c0f20c3b5a8ed275750fd9754c4be70942e9224944a74",
"ant-file.ico": "19d0d02786974f63ebce851f4d5332d221d8644878b28a1b8c2f520a4f625331"
},
"hir": 5,
"publicKey": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAECKuwwfugOAJlRRLTo5jD0me19XVWzTeufDrsRFtPuFLAKYw1tA7Yq1I65THXQQcVxHU1aSXvZPHBV3ac25tpkw==",
"fingerprint": "f4ca87d2738082c5836de88a57ed0bfae9c16b52a3635651e47030b837f9e7a1",
"signature": "MEQCIAoozTyVoZfrjNfRsJecM8wu1q5as2AIXHSmMks\u002BHMJvAiBI7tgn5DVyajTOdLMznslRgiTtgR1U17CMd6vdOTeXwg=="
}
| Field | Group | Meaning |
|---|---|---|
id |
who | The pack's identifier (lowercase). Without it, the folder's name in lowercase. Two packs with the same id: the first found wins. |
name, version, description, author, license, homepage |
who | Shown by larva languages. |
extensions |
what | The source extensions it reads, each starting with .. A file's language is chosen by its extension, and .queen projects take as sources the extensions of all installed packs (QueenProject.SourceExtensions). |
icon |
what | An icon file of the pack. The installer gives the extension's files that icon. |
abi |
needs | The requests it answers. Must be 1. |
larva |
needs | The oldest larva it works with, compared number by number (LarvaVersion.Compare). |
hir |
needs | The .lva format it reads and writes. Must be 5. |
entry |
needs | Its library, relative to the folder. |
files |
security | Every file of the pack except the manifest, with its SHA-256 in lowercase hex. |
publicKey |
security | The author's public key: base64 of an ECDSA P-256 SubjectPublicKeyInfo. |
fingerprint |
security | SHA-256 (hex) of that key. |
signature |
security | The author's signature over the manifest (see below). |
registry |
security | Added by a registry when it publishes the pack: {fingerprint, signature} (see Registries). |
A pack's identity for caches is the hash its manifest lists for its library (LanguagePack.Identity). A System.Core compiled from sources is cached under a key made of the pack's id, that identity and the .lva format, so a new build of the frontend recompiles it.
Verification
LanguagePack.Verify runs before a pack is loaded, and larva languages runs it for every installed pack. It returns the first problem it finds:
abiis present and is 1.larva(if given) is not newer than this larva.hir(if given) is 5.entryis present,fileslists at least one file, and the entry is among them.- Every listed file is inside the folder, exists, and has the SHA-256 the manifest gives.
- No
.dll,.so,.dylibor.exeanywhere in the folder is missing fromfiles. Code the manifest does not list is never there by accident, and it is never loaded. - If the manifest has a
signature:fingerprintis the SHA-256 ofpublicKey, and the signature verifies against the manifest. - If the pack was installed from a registry: the registry's checks (see Registries).
A pack that fails is reported and not loaded. larva languages prints each pack and its status:
larva languages
Antril 0.8.0 (antril: .ant)
Antril for Larva: its frontend (Antril source to Larva HIR), its view (HIR written as Antril) and its editor services
author: Antril Organization
license: Proprietary
homepage: https://antril.org
…
signature: signed, key f4ca87d2738082c5
status: ok (files verified)
status: ok (files verified) means steps 1 to 8 passed: every file has the hash the manifest lists and, for a signed pack, the manifest is the one its author signed. The other statuses, each produced by changing the pack above:
status: NOT USABLE: ant-file.ico is not the file its manifest lists (changed since the pack was made)
status: NOT USABLE: extra.dll is not among its `files`
status: NOT USABLE: its signature does not match its manifest (changed since it was signed)
status: NOT USABLE: it needs larva 9.0.0 or later (this is 0.8.0)
A build that needs the pack stops with the same reason: "larva: the language pack Antril (…) cannot be used: ant-file.ico is not the file its manifest lists (changed since the pack was made)". A file whose extension no pack reads is refused before anything is loaded:
larva build --check Tiny.cpp
larva: Tiny.cpp: no installed language reads .cpp files (installed: Antril (.ant))
What is signed
The signature is ECDSA P-256 with SHA-256, in the DER (RFC 3279) encoding, over the canonical manifest (LanguagePack.Canonical):
- the manifest without its top-level
signatureandregistrykeys; - every object's keys in ordinal order, at every depth;
- no spaces, UTF-8, strings escaped the way the default JSON writer escapes them.
Because files is inside the signed text, the signature covers every file of the pack through its hash. Changing a library, adding one, or editing any manifest field breaks either a hash or the signature. publicKey and fingerprint are signed too, so the key cannot be swapped without the signature failing.
Keys and larva languages pack
larva languages key makes a signing key, a PKCS#8 PEM file with an ECDSA P-256 private key. It never writes over an existing file:
larva languages key antril.pem
antril.pem: a signing key (fingerprint f4ca87d2738082c5836de88a57ed0bfae9c16b52a3635651e47030b837f9e7a1); keep it private
public key: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAECKuwwfugOAJlRRLTo5jD0me19XVWzTeufDrsRFtPuFLAKYw1tA7Yq1I65THXQQcVxHU1aSXvZPHBV3ac25tpkw==
larva languages key antril.pem
larva languages: antril.pem exists (a key is not written over)
larva languages pack <folder> [--key <key.pem>] completes a pack's manifest in place (LanguagesCommand.PackFolder):
- It removes
publicKey,fingerprint,signatureandregistry. - It writes
files: every file of the folder exceptlanguage.json, in ordinal order, with its SHA-256. - It sets
abito 1 if missing. - It loads the frontend and asks
infowhich format it writes, and puts that inhir. The pack must therefore be built for the machine that packs it. - With
--key, it addspublicKey,fingerprintandsignature.
It prints the folder followed by a summary such as Antril 0.8.0, 2 file(s), signed (key f4ca87d2738082c5) (unsigned without a key). The fingerprint is shortened to 16 hex digits wherever larva shows it.
Loading
LanguageRegistry finds packs in these folders, in this order; the first pack of an id wins:
| Order | Folder | Typical use |
|---|---|---|
| 1 | Each folder of LARVA_LANGUAGES (separated by ; on Windows, : elsewhere) |
Development: a language's own build output (..\Antril\artifacts\pack). |
| 2 | Languages/ next to larva |
An installation: Languages/Antril/ with the native, signed pack. |
| 3 | The user's language folder: LARVA_USER_LANGUAGES, else %LOCALAPPDATA%/Larva/Languages |
Packs installed from a registry. |
Each subfolder that holds a language.json is a pack. Reading the manifests is cheap and loads no code; a pack's library is loaded the first time a request is made to it (LanguagePack.Load), after Verify passes.
Native packs and managed packs
Load looks at the entry library first:
- A native library (what an installation ships). larva loads it as a shared library and looks up
larva_language_callandlarva_language_freeamong its exports. The installedlarva.exeis compiled ahead of time and so is the Antril pack:Antril.dllis a native library of about 5 MB that exports those two functions. - A managed library (a pack built in a working copy). If the entry file carries managed metadata, larva loads it as a managed assembly and searches its public static methods for the two entry-point names, then calls them through their addresses, exactly as it would call the native exports. Only a working copy of larva, which can load managed code at run time, does this. An installed, ahead-of-time compiled larva refuses such a pack and says it loads native packs.
Either way the pack is then asked for info, and loading fails if the hir it reports is not 5.
The same pack source builds both ways: in Antril, LanguageExports.Call and LanguageExports.Free are marked with their export names, so publishing the library natively turns them into real exports, and a managed build keeps them as methods larva finds by those names.
Where the Antril pack comes from
The Antril repository builds its pack into artifacts/pack/Antril/ (Antril.dll, ant-file.ico, language.json). A working copy of Larva finds it through LARVA_LANGUAGES. An installation's packs are native and signed, placed in Languages/<Name>/ next to larva by the release scripts.
Registries
New in this version, and still changing: languages can come from a languages registry, the way packages come from Hive. These commands exist in the code now:
| Command | What it does |
|---|---|
larva languages search [<text>] |
The languages a registry has. |
larva languages install <id>[@<version>] |
Downloads the pack for this platform, verifies it, and installs it into the user's language folder. |
larva languages update [<id>] |
The installed languages (or one) at their newest usable version. |
larva languages remove <id> [--forget-key] |
Removes an installed language (and, with --forget-key, its pinned author key). |
larva languages publish <folder> [--key <author.pem>] |
A publication request: one subfolder per platform (win-x64, linux-x64, osx-arm64…), each packed, signed and zipped. Needs HIVE_TOKEN. |
larva languages status <request-id> |
Where a publication request stands. |
larva languages approve … |
For registry administrators: signs a request as the registry and publishes it into a folder registry. |
Each takes --provider <url>; without it, larva config languages.provider, else the built-in https://www.antril.org/languages-api/. A registry is a folder or an https address with index.json (every approved language), {id}/index.json (its versions and their packs) and {id}/{version}/{id}-{version}-{platform}.zip.
What changes for a pack installed this way (LanguagePack.FromRegistry, true for packs in the user's folder):
- The author must have signed it.
- A registry must have signed it too: the manifest's
registryfield holds{fingerprint, signature}, a signature over the manifest withoutregistry(LanguagePack.RegistryCanonical), so the author's signature is itself signed. The registry key must be one larva trusts: a key built into larva, or one added withlarva config languages.trust <public key>. - The author's key is pinned the first time the language is installed, in
trust.jsonof the user's language folder. A later version signed by another author key is refused.
An install downloads the zip for this machine's platform (LanguagePlatform.Current, for example win-x64), checks its SHA-256 against the registry's index, unpacks it into a staging folder (a path that would leave the pack refuses the whole pack), verifies everything above plus abi and larva, and only then moves it into place. larva languages shows a registry: line for such packs.
Templates are HIR
larva new makes a project from a template in the language you choose. A template is written once, in no particular language: its code is a HIR assembly, and the chosen language's pack writes it as source.
A template is a folder Templates/<Name>/. Its identifier is the folder name with - for each space (Console App is Console-App); lookup ignores case. It holds:
| File | Meaning |
|---|---|
template.json |
name, description, author, version, type (exe or library), namespace (the template's own, renamed to the project's), files ({"Namespace.Type": "FileName"}), optional main (the main file's name). |
template.lva |
The code as HIR, what an installation ships. |
src/ |
While a template is being written: its sources in any installed language, compiled when it is used. |
The Library template that comes with Larva:
{
"name": "Library",
"description": "A library other projects use: a class with a method",
"author": "Larva",
"version": "1.0.0",
"type": "library",
"namespace": "App",
"files": { "App.Greeter": "Greeter" }
}
larva new <template> <Name> --lang <language> (NewCommand.Create):
- Reads the template's HIR:
template.lva, or itssrc/compiled by their pack against System.Core. - Renames the template's namespace (
App, andApp.*) to the project's name, and the assembly too. - Places each type in the file
filesgives it (plus the language's extension); the rest go to the main file (main, else the project's name for a library orProgramfor an executable). - Asks the pack's
sourcerequest to write the assembly, withnonPublic: trueandheader: false. - Writes the sources and
<Name>.queen(with BUILD keys from this machine's defaults, seelarva config). Nothing is written over without--force.
larva new Library Shapes --lang antril
The project gets Shapes.queen and Greeter.ant, written by the Antril view from the template's HIR:
into namespace Shapes
{
/// What the library offers: a greeting.
public class Greeter
{
public:
/// A greeting for `name`.
static Greet(str name) : str => "Hello, " + name + "!";
}
}
The HIR in template.lva has no + on strings: it calls String.Concat<u8> twice. The Antril view writes the sugar back (see The catalog and language views).
larva new --pack <template folder> -o <folder> compiles a template's sources into <folder>/<Name>/template.lva next to a copy of template.json; an installation ships only that:
larva new --pack Templates/Library -o tpl
tpl\Library: Library (1 types)
Templates are found in LARVA_TEMPLATES (folders separated like LARVA_LANGUAGES), Templates/ next to larva, %LOCALAPPDATA%/Larva/Templates, and the Templates/ folder of a working copy of Larva above larva. larva new with no arguments lists the templates and the installed languages:
larva new
Templates (larva new <template> [Name] [--lang <language>]):
Console-App A program for the console: its Main prints a greeting
Library A library other projects use: a class with a method
Languages (--lang):
antril Antril 0.8.0 (.ant)
With only one language installed, --lang can be left out.
Environment variables
| Variable | Meaning |
|---|---|
LARVA_LANGUAGES |
Extra pack folders, searched first. |
LARVA_USER_LANGUAGES |
The user's language folder (where larva languages install puts packs). Default %LOCALAPPDATA%/Larva/Languages. |
LARVA_TEMPLATES |
Extra template folders, searched first. |
HIVE_TOKEN |
The token larva languages publish sends. |
Edit this page on GitHub Report an issue