Landin the bootstrap compiler source
the bootstrap compiler

The Ada bootstrap compiler

This directory holds the bootstrap implementation described by ROADMAP.md. It is a real compiler under construction, not a prototype: spec.md is the normative language specification and tour.md explains the language, and nothing here may quietly decide language semantics.

Layout

compiler/ada/
  landin_common.gpr     build policy: language version, warnings, modes
  landin_lib.gpr        the compiler library, built by everything else
  refine.gpr            the `refine` executable
  landin_tests.gpr      the repository's own test program
  TOOLCHAIN.md          the pinned toolchain and warning policy
  src/
    base/               the root package and the exceptions
    source/             source snapshots, spans, line maps, provenance
    diagnostics/        diagnostic transport and text rendering
    platform/           host adapters and their native implementations
    stages/             target facts and the stage/pipeline seams
    syntax/             the tokens, the scan, the syntax table and the parse
    resolution/         declarations, scopes and what each name means
    checking/           the language's types, and what each node has
    checking/           the language's types, and what each node has
    driver/             the request/result boundary
    main/               the `refine` entry point
  tests/src/            the harness, the fakes and the suites

compiler/tests/ sits outside this directory on purpose. Fixtures describe the language, not this implementation, and must survive the bootstrap being replaced.

Package ownership

packageownsmust not
Landinthe namespace and the three exceptionscontain any logic
Landin.Sourceimmutable snapshots, byte offsets, spans, line mapsread a file, or know an encoding beyond bytes
Landin.Source storageheap-allocated text and line maps, never freed while the process livesput a source file in an automatic object
Landin.Source.Setsa compilation's snapshots and their identitiesacquire bytes from a host
Landin.Provenanceorigins and the declaration side tableknow what a declaration means
Landin.Source.Namesidentities for the byte runs a program namesknow that a spelling is reserved
Landin.Tokensthe lexical vocabulary, the token, the fault, the streamrender prose, assign a diagnostic code, or build a token
Landin.Tokens.Lexerthe scan, and the only construction of a tokenknow what a token means
Landin.Syntaxthe node table, extents, anchors, origins, soundnessknow that types or IR values exist, or hold a diagnostic
Landin.Syntax.Precedence[1820] as data: levels, operators, folds, first setscontain a parsing decision
Landin.Syntax.Parserthe parse, and the only construction of a treeassign a diagnostic code, or read a byte
Landin.Syntax.Dumpa canonical text for a treebe a stable interface or a serialisation
Landin.Syntax.Forestone tree per source for the whole compilation, on the heap and never freedhand out a tree that can be copied or written to
Landin.Resolutiondeclarations, scopes, and which declaration each name meanshold a diagnostic, or decide what a name may be called
Landin.Typesthe eleven types, and each one's width against a targethold a machine fact of its own, or ask the host for one
Landin.Checkingwhat type every node and every declaration hasdecide a rule, or hold a width
Landin.Diagnosticscodes, severities, labels, notes, orderingrender, or own the catalogue of codes
Landin.Diagnostics.Textdeterministic renderingdecide severity or ordering policy
Landin.Diagnostics.Catalogueevery diagnostic code, and what each requires of its occurrenceshold a message, or a code nothing raises
Landin.Diagnostics.Lexicalturning a scanner fault into a diagnosticinvent a code, or a roadmap item
Landin.Diagnostics.Syntacticturning a parse failure into a diagnostic, and naming the constructs only the parser can meetinvent a code, a construct, or a roadmap item
Landin.Diagnostics.Resolutionturning a duplicate or an unknown name into a diagnosticinvent a code, or attach a sentence to no place
Landin.Diagnostics.Checkingturning a type that does not agree into a diagnosticinvent a code, a construct, or a roadmap item
Landin.Platformthe host interfaces every effect goes throughperform an effect
Landin.Platform.Nativethe only filesystem implementationbe reached except through the interface
Landin.Platform.Native.Toolsthe only process spawning, and the only GNAT-specific dependencygrow a second host concern
Landin.Targetstarget facts and layout arithmeticask the host how wide a pointer is
Landin.Stagesthe compilation context, the stage interface, pipelines, and everything a stage builds that outlives itknow which stages exist, or which order they run in
Landin.Stages.Syntaxrunning the scan and the parse over a compilationkeep anything of its own, or decide reporting policy
Landin.Stages.Resolutionthe order the trees are walked inown the resolution table, or a code
Landin.Stages.Checkingthe three type passes and the assignment walkown a table, a code, or a width
Landin.Driverargument classification and the resultimplement a language rule
Refineprinting and the exit statuscontain a decision

Public specifications stay narrow, and a representation is private wherever a caller could otherwise assemble a value the package would not have produced: Landin.Targets.Target_Facts is private because a description must come from a named target, not from a record literal that happens to describe the development host. Where a type is limited, that is deliberate too: a compilation cannot be copied out from under its stages.

A snapshot's bytes and line map are allocated once and not freed. A compilation owns its sources for as long as it exists, the process is short, and a compiler that frees source text while a diagnostic still points into it has traded a leak for a dangling span. That is a decision, not an oversight; when the roadmap needs a longer-lived process it will be revisited there. R1.50 extends it to the trees for the same reason, and to the four tables a compilation now owns.

A compilation owns everything a stage builds that outlives the stage that built it: the interned identities, the declaration sites, the trees, and what every name in them means. Two facts about the seam decide that it has to be there rather than in a stage. Run takes Item as an in parameter of a limited interface, so a stage cannot keep anything in itself; and Stage_Reference is a library-level access type, so a stage object cannot be a local of one compilation either -- Append (Line, Local'Access) on a local instance is rejected as "non-local pointer cannot point to local object". The price is that Landin.Stages gains one with clause per representation as the roadmap adds them. The line that keeps it a seam is exact: this package may depend on a representation and may never depend on a stage. Ada enforces that for the specification only -- a parent's spec may not with its own child -- and a parent's *body* may, so landin-stages.adb growing a with Landin.Stages.Syntax to build a default pipeline is how the rule would actually be broken. Nothing but the rule stops it, and Landin.Driver is what owns the pipeline.

What is deliberately absent

There is no IR or backend here yet. What does exist is the whole frontend: refine scans and parses every .ldn file it is given, resolves every name in them as one module, checks the type of everything and that every name is assigned before it is read, reports what none of the four could read, and produces nothing when a file is a program.

A width is a function of a type and a target description, never a property of either alone, and Landin.Types.Width is the only place one is formed. usize is [0160]'s pointer-width integer, so it is as wide as Landin.Targets.Pointer_Width says and no wider -- which is how a 32-bit target description stays 32-bit on a 64-bit host. A literal's value is held in Landin.Types.Magnitude, whose bound is written out as 2 ** 64 - 1 because that is a fact about u64; a host width leaking in would be Long_Long_Integer, whose range is a fact about the machine running the compiler. L0001 is retired -- the catalogue said it retires when the frontend is wired to the driver, and R1.40 is where that happened -- and its row stays so its number is never handed to another rule.

Both halves are held to the grammar from both sides. check.py compares Landin.Tokens' reserved words with the tour's own keyword production, derives every positive program in the corpus from the grammar and refuses every negative one the frontend rejects, and compares Landin.Syntax.Precedence with [1820]'s own levels, operators, fold and first sets. The harness lexes every program in the corpus and compares each token with what check.py's independent tokeniser produced, parses every one and requires the same verdict check.py reached, and parses every truncation of every one to prove that a half-written file yields a tree and not a crash.

Landin.Syntax is a flat table, not a pointer structure: a Node_Id is a dense integer, so R1.50's names, R1.60's types and R1.70's values each go in an array of their own rather than a map keyed on an access value. R1.50 is the first to take that up, and it does it once for the whole compilation: one array of Declaration_Id with one run per source and a first index, which is what Landin.Syntax already does with a node's children. A child's index is lower than its parent's and a child's extent lies inside it, both as postconditions. A construct the parser could not read becomes an error node of the band it needed -- one per band, so a case over a band still covers the hole -- and Is_Sound propagates upward so that one syntax mistake does not become a cascade of type errors about a hole. Trees live in the compilation's Landin.Syntax.Forest, one per source and none freed, which is R1.50's answer to where they live: a tree cannot be an element of a container, because it is limited with unknown discriminants, and an initialised allocator whose value is the parse is the one form Ada gives for building one where it will outlive the call.

A diagnostic code is written in exactly one place, Landin.Diagnostics.Catalogue, and check.py refuses a code literal anywhere else in src/. Each column of the catalogue is an exhaustive case over the code names, so a code with no row is a missing-case error rather than a warning. The catalogue holds no prose: L0003 is raised with two sentences, for a source that is missing and one that cannot be read, because one rule was violated and the difference between them is wording. What a code requires of every occurrence -- a source, a non-empty span, how many secondary labels, how many notes -- is in the row, and Landin.Diagnostics.Lexical checks the row against the diagnostic it just built.

Landin.Tokens knows two things the kernel grammar does not, both on purpose. A band of deferred lexemes -- 1.5, "text", +=, ! -- is read as one token each so that [1830] can refuse a construct by name instead of reporting a stray byte, and so that enabling one later cannot change how a file that never used it was read. Every deferred kind names the tour construct it belongs to, and check.py holds it to naming one that exists and to not being a lexeme the grammar already spells.

The stage seam is one interface and one pipeline, contract-tested with fake stages. Named per-stage packages arrive as each stage is written, which is what keeps the seam a seam rather than an empty directory tree with aspirational names in it.

Building

See TOOLCHAIN.md. From the repository root:

./scripts/build.sh
./scripts/test.sh