Almide is a ~72,000-line pure-Rust compiler organized as a workspace of 9 crates + a CLI binary. Dependencies: serde + serde_json (AST serialization), toml (template loading), clap (CLI), lasso (string interning).
Build time (cargo build)
┌──────────────────────────────────────────┐
│ grammar/*.toml ──┐ │
│ runtime/rs/src/ ─┤ │
│ stdlib/ ─┼─→ build.rs ─→ generated/ │
│ │ arg_transforms.rs │
│ │ stdlib_sigs.rs │
│ │ rust_runtime.rs │
│ │ token_table.rs │
└──────────────────────────────────────────┘
Run time (almide run)
┌─────────────────────────────────────────────────────────────────┐
│ │
│ .almd source │
│ │ │
│ ▼ │
│ ┌─────────┐ ┌──────────┐ ┌─────────┐ ┌──────────────┐ │
│ │ Lexer │──▶│ Parser │──▶│ AST │──▶│ Type Checker │ │
│ └─────────┘ └──────────┘ └─────────┘ └──────┬───────┘ │
│ │ │
│ expr_types + env │
│ │ │
│ ▼ │
│ ┌───────────────────────────────┐ │
│ │ Lowering (AST → Typed IR) │ │
│ └──────────────┬────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────┐ │
│ │ Nanopass Pipeline │ │
│ │ (target-specific rewrites) │ │
│ └──────────────┬────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────┐ │
│ │ Rust target WASM target │ │
│ │ Template Renderer Direct Emit │ │
│ │ (TOML-driven) (linear memory) │ │
│ └──────────────┬─────────────────────────┘ │
│ │ │
│ ▼ │
│ .rs / .wasm │
└─────────────────────────────────────────────────────────────────┘
almide/ Workspace root
├── Cargo.toml Workspace manifest
├── src/ CLI binary (almide)
│ ├── main.rs CLI entry: subcommands, orchestration
│ ├── lib.rs Public API (used by playground WASM crate)
│ ├── resolve.rs Module resolution (filesystem + git deps)
│ ├── project.rs almide.toml parsing, PkgId
│ ├── project_fetch.rs Git dependency fetching
│ ├── diagnostic_render.rs Diagnostic pretty-printing
│ └── cli/
│ ├── mod.rs CLI module exports
│ ├── run.rs almide run: compile → rustc → execute
│ ├── build.rs almide build: compile → binary / WASM
│ ├── check.rs almide check: type check only
│ ├── emit.rs almide emit: output generated source
│ ├── commands.rs almide test: find + run test blocks
│ └── selfupdate.rs almide self-update: binary update from GitHub
│
├── crates/
│ ├── almide-base/ Shared primitives
│ │ ├── diagnostic.rs Error/warning types with file:line + hint
│ │ ├── intern.rs String interning (lasso)
│ │ └── span.rs Source span types
│ │
│ ├── almide-syntax/ Parsing
│ │ ├── ast.rs AST node types (serde-serializable)
│ │ ├── lexer.rs Tokenizer (42 keywords, string interpolation, heredocs)
│ │ └── parser/ Recursive descent parser with error recovery
│ │ ├── entry.rs Top-level: program, imports, declarations
│ │ ├── declarations.rs fn, type, trait, impl, test, top-let
│ │ ├── expressions.rs Binary, unary, pipe, match, if/then/else
│ │ ├── primary.rs Literals, identifiers, lambdas, blocks
│ │ ├── statements.rs let, var, guard, assignment
│ │ ├── patterns.rs Match arm patterns (variant, record, tuple)
│ │ ├── types.rs Type expressions (generics, records, functions)
│ │ ├── collections.rs List, map, record, tuple literals
│ │ ├── compounds.rs for-in, while, fan blocks
│ │ └── hints/ Smart error hints (typos, keywords, delimiters)
│ │
│ ├── almide-types/ Type system
│ │ ├── types/ Ty enum (Int, String, List, Record, Fn, Variant, ...)
│ │ └── stdlib_info.rs UFCS candidate tables, auto-import module lists
│ │
│ ├── almide-frontend/ Type checking & lowering
│ │ ├── check/ Constraint-based type inference + UFCS resolution
│ │ ├── lower/ AST + Types → IR lowering, VarId assignment
│ │ ├── canonicalize/ Import canonicalization
│ │ ├── type_env.rs Scoped variables, functions, types, modules
│ │ ├── import_table.rs Import resolution table
│ │ └── stdlib.rs Stdlib signature registration
│ │
│ ├── almide-ir/ Intermediate representation
│ │ ├── lib.rs IrProgram, IrExpr, IrStmt, IrPattern, CallTarget, VarTable
│ │ ├── fold.rs IR tree walker/transformer
│ │ ├── visit.rs Read-only IR visitor
│ │ ├── use_count.rs Variable use-count analysis (move vs clone)
│ │ ├── result.rs Result expression detection
│ │ ├── effect.rs Effect inference helpers
│ │ └── wasm_repr.rs WASM type representation
│ │
│ ├── almide-optimize/ Optimization
│ │ ├── mono/ Monomorphization (generic instantiation, VarId alpha-renaming)
│ │ └── optimize/ DCE, constant propagation, LICM, peephole, stream fusion
│ │
│ ├── almide-codegen/ Code generation
│ │ ├── pass.rs NanoPass trait, Pipeline, Target enum
│ │ ├── target.rs Target config: pipeline + templates per target
│ │ ├── template.rs TOML template engine ({var} substitution)
│ │ ├── walker/ IR → Rust source renderer (target-agnostic)
│ │ ├── emit_wasm/ Direct WASM binary emitter (linear memory, WASI)
│ │ ├── pass_*.rs Nanopass implementations (20+ passes)
│ │ └── generated/ Auto-generated by build.rs (DO NOT EDIT)
│ │ ├── arg_transforms.rs Per-function argument decoration rules
│ │ └── rust_runtime.rs Embedded Rust runtime (include_str)
│ │
│ ├── almide-tools/ Tooling
│ │ ├── fmt.rs Source code formatter (almide fmt)
│ │ ├── interface.rs Module interface extraction (almide compile)
│ │ └── almdi.rs ALMDI metadata format
│ │
│ └── almide-lang/ Language metadata (version, feature flags)
│
├── grammar/ Grammar definitions
│ ├── tokens.toml Keyword → TokenType mapping
│ ├── almide.toml Grammar rules
│ └── precedence.toml Operator precedence table
│
├── codegen/templates/
│ └── rust.toml Rust syntax templates (~330 rules)
│
├── stdlib/defs/ Stdlib TOML definitions (23 modules, 430+ functions)
│
└── runtime/rs/src/ Rust runtime: 22 modules (string, list, map, json, fs, ...)
All semantic decisions are made in the IR before any text is emitted. The walker sees only typed IR nodes — it never checks what target it's rendering for.
Each pass receives &mut IrProgram and rewrites it structurally. Passes are composable and target-specific:
Rust pipeline:
TypeConcretization → BorrowInsertion → CloneInsertion
→ StdlibLowering → ResultPropagation → BuiltinLowering → FanLowering
WASM pipeline:
Direct binary emission — no template layer. The emit_wasm/ module walks the IR and emits WASM bytecode directly, managing linear memory layout, stack frames, and WASI syscalls.
| Pass | Target | What it does |
|---|---|---|
| StdlibLowering | Rust | Module { "list", "map" } → Named { "almide_rt_list_map" } + arg decoration |
| ResultPropagation | Rust | Insert Try { expr } (Rust ?) on fallible calls in effect fn |
| CloneInsertion | Rust | Insert Clone nodes based on use-count analysis |
| BoxDeref | Rust | Insert Deref for recursive type access through Box |
| BuiltinLowering | Rust | assert_eq → RustMacro, println → RustMacro |
| FanLowering | Rust | Strip auto-try from fan spawn closures |
| TailCallMark | WASM | Mark tail-recursive calls for return_call emission |
| ClosureConversion | WASM | Lambda capture → explicit env struct passing |
TOML files define syntax patterns. The walker calls templates.render_with("if_expr", ...) and gets back Rust syntax:
# rust.toml
[if_expr]
template = "if ({cond}) {{ {then} }} else {{ {else} }}"~330 template rules. All string rendering is done here — passes never produce text.
walker/ walks the IR tree and renders each node by calling the template engine. It is fully target-agnostic — zero if target == Rust checks. Target differences are handled entirely by passes (Layer 1) and templates (Layer 2).
Key rendering functions:
render_expr()— expressions (recursively renders sub-expressions)render_stmt()— statements (let, var, guard, assign)render_type()— type annotations (named records, generics, tuples)render_pattern()— match patterns (variants, records, tuples)render_function()— function declarations with params, return type, body
The WASM target (emit_wasm/) bypasses templates entirely and emits binary WASM directly:
- Linear memory: Stack allocator on memory 0, scratch buffer on memory 1 (multi-memory)
- Tail calls: Native
return_call/return_call_indirect(WASM 3.0) - Strings: UTF-8 in linear memory, length-prefixed
- Closures: Explicit environment structs, function table indirect calls
- WASI: File I/O, args, env vars via WASI preview1 imports
build.rs generates code at compile time:
- Scans
runtime/rs/src/*.rs→ extracts function signatures → generatesarg_transforms.rs(per-function argument decoration: BorrowStr, BorrowRef, ToVec, LambdaClone, Direct) - Reads stdlib definitions → generates
stdlib_sigs.rs(function signatures for type checking) - Reads grammar files → generates
token_table.rs(keyword → TokenType mapping)
The Rust runtime is embedded in the compiler binary via include_str! and prepended to generated .rs files.
Constraint-based inference with eager unification:
- Infer — Walk AST, assign fresh type variables to unknowns, collect constraints
- Solve — Unify constraints, propagate solutions
- Resolve — Replace inference variables with concrete types in
expr_types
Key types: Ty::Int, Ty::String, Ty::List(Box<Ty>), Ty::Record { fields }, Ty::Variant { cases }, Ty::Fn { params, ret }, Ty::Option(Box<Ty>), Ty::Result(Box<Ty>, Box<Ty>).
UFCS resolution: xs.map(fn) → checker finds builtin_module_for_type(List) = "list" → dispatches to list.map(xs, fn).
- Resolve (
resolve.rs) — Walksimportdeclarations, finds.almdfiles (local, git deps, stdlib skip) - Register (
check/mod.rs) —register_module(name, program)adds prefixed function/type signatures to TypeEnv - Check — Main program type-checked with all modules registered
- Lower — Each module lowered to
IrModule(separate function/type namespace) - Codegen — Module functions emitted with
almide_rt_{module}_{func}prefix
Stdlib modules (TOML-defined) are never loaded from disk — their signatures come from build.rs-generated code, and their runtime is embedded.
Every diagnostic includes:
- Error code (E001–E010) for programmatic consumption
- File:line:col location
- Source context with underline
- Actionable hint pointing to a specific fix
error[E005]: argument 'xs' expects List[Int] but got String
at line 5
in call to list.sort()
hint: Fix the argument type
|
5 | let sorted = list.sort("hello")
| ^^^^^^^
Supported output: human-readable (default) or --json for tool integration.