CME is a statically typed, embeddable scripting language implemented in Rust. The full whitepaper language surface parses, type-checks, and runs end to end on the tree-walking interpreter, the §8 megaprogramming system (grammar-driven mega macros with a packrat pattern engine and compile-time function evaluation) is implemented at the source-text level with its own cme expand command, multi-file mods (§10) load, link, and run from a mod.toml + src/ tree, and the §9 schema system gates host/script contracts with compile-time-verified bindings for Rust and C hosts. The bytecode VM and AOT compiler remain future work.
cme-coredefines the spanned AST for the full language surface: primitive/inferred/void types, named and generic types, arrays and maps, literals, identifiers, variable declarations, functions, calls with positional and named arguments, structs and enums with type parameters, enum variant construction, impl blocks with function members, qualified and dotted-path calls, field and index access,match(statement and expression forms with patterns),for-in,if/else,while,return, the?operator, interpolated strings, and lvalue assignment targets.cme-compilerprovides the lexer (including block comments and$"..."interpolation tokens), parser, diagnostics, validator, and one-callparse_sourcefor the full surface. The type checker resolves struct and enum declarations with generics, the built-inoption<T>/result<T, E>, arrays and maps; it enforces §2.6–§2.16, §10.4 (impl targets, member unions and duplicates, member bodies), §11, and Appendix A, including match exhaustiveness,?error-type agreement, named-argument coverage, and §2.16 crystallization.cme-interpruns the full language surface end to end: plain Rust values with structural equality for structs/enums/arrays/maps, overflow-checked arithmetic, truncating division, short-circuit logic, §A.6 stringification, CMON-style display, value-semantics cloning, impl member and path calls (§10.4), named arguments bound by name, and a fixed 1024 call-depth limit. Hosts invoke interface members throughInterpreter::invoke_member.cme-runtimeis a placeholder.- The
cmeCLI has workinglex,ast,check, andruncommands with rendered diagnostics;runrefuses to execute any program that produced a diagnostic. - Megaprogramming (§8) is implemented in
cme-compiler::mega:grammarandmegadeclarations, invocation regions balanced under composed lexical profiles (islands and heredoc regions included), a packrat pattern engine with furthest-failure diagnostics, indentation-aware blocks, fragment validators, parse-integrated$expr/$type/$block/$raw/$templateextents,@-function compile-time evaluation (§8.5), fixpoint expansion with a depth cap,grammar ts extends jsinheritance, and profile static checks.mega.cmat the repository root is the living fixture: HTML, CSS, JS, Python, JSON, YAML, TOML, SQL, and RE megaprograms, all expanding and running. syntax.cmat the repository root is the full-language fixture: it exercises every whitepaper syntax feature — including §10.4 impl blocks on structs, enums, and host-style dotted path targets — and returns 0 when all of its internal checks pass.basic.cmremains the zero-diagnostics pin for the front end andboom.cmthe recovery stress fixture.
The language specification is maintained in WHITEPAPER.md.
The user-facing documentation — getting-started guides, the full language
reference, mods, megaprogramming, the schema system, "coming from
Go/C#/Rust/Python/TypeScript" guides, and detailed Rust and C embedding
guides — lives in docs/ as an mdBook. It
builds and deploys to GitHub Pages from this same repository via
.github/workflows/docs.yml on every
push that touches docs/.
Build it locally:
cargo install mdbook # once
mdbook build docs # output in docs/book; `mdbook serve docs` to browseThe docs track the implementation: per AGENTS.md, any change to the
language, toolchain, or host APIs must check docs/ for drift in the
same change.
Checkmate ships its own editor tooling in-tree, next to the compiler it must stay in sync with:
| Directory | What it is | Who consumes it |
|---|---|---|
grammars/tree-sitter-checkmate |
Tree-sitter grammar + corpus tests + external heredoc scanner | Zed extension, Neovim/Helix/Emacs |
editors/zed |
Zed extension: highlighting, brackets, outline, indents, text objects | Zed (extensions: Install Dev Extension → editors/zed) |
editors/textmate |
TextMate grammar (source.checkmate) + installable VS Code wrapper |
VS Code, Sublime, GitHub |
All three cover the full implemented surface — the core language, §8
megaprogramming (grammar/mega declarations, the pattern language,
expansion templates, brace-balanced invocation regions, heredocs), and §9
schema files. The tree-sitter grammar is validated against every fixture in
this repository: syntax.cm, mega.cm, the schema files, and the mod trees
all parse without error nodes (the deliberately damaged recovery fixtures
boom.cm, broken_syntax.cm, tests/fixtures/recovery/* are expected to
produce them).
Build and test the grammar:
cd grammars/tree-sitter-checkmate
tree-sitter generate && tree-sitter testSee editors/zed/README.md and editors/textmate/README.md for
per-editor install instructions.
Files that declare or invoke megaprograms (mega name(...) { template } definitions, name! { region } invocations) are expanded automatically before check/run (the source behaves exactly like its expansion). To see the generated program itself:
cargo run --features cli -- expand mega.cmThis writes mega_expanded.cm side by side with the original — pure Checkmate, every megaprogram invocation replaced by its generated code — and then parses and checks that file. Add --provenance to annotate each root mega site with a // @ name! src:line:col comment; without the flag the output is byte-deterministic. Expansion runs per file inside a mod too: each module's megaprograms expand before the mod links into one program.
The developer toolchain ships as one binary, and the language server rides in it:
cargo run --features cli -- lspcme lsp speaks LSP over stdio (WHITEPAPER §14). Diagnostics stream on open and edit — lexer, parser, validator, type checker, §8 megaprogram expansion, and §9 schema pipelines all report into the editor. Hover shows resolved signatures and crystallized infer types, completion covers struct fields and enum variants after ., named arguments inside calls, import segments, and scope names, and go-to-definition, find-references, document symbols, and semantic tokens are shadowing-aware. The analysis is incremental through salsa: every document is a query input, and an edit recomputes only what it invalidated. The Zed extension launches cme lsp automatically.
The server also understands where your files live, with no configuration:
- Mods and schemas are auto-detected. From any opened file the server walks up to the nearest
mod.toml(§10.1), collects the.cmschema files around the mod root — including aschemas/directory next to the mod, the layout of this repository's host apps — and grants them through the manifest's[schemas]table exactly likecme check <mod> --schemadoes (§9.5, §10.2). Scripts in a mod are checked as one assembled program (§10.3/§10.4): cross-module calls link,import self.a.bresolves against the real module tree,implblocks union across files, and every diagnostic is re-anchored to the module it came from. - Schema-aware authoring. §9.3 boundary types (
Sprite,Event) complete and hover like local types;game.window.completes the capability's members with their signatures; capability calls fill named arguments from the schema; import completion offers schema namespaces and capabilities; and insideimpl game.gamemode { … }the interface's missing members complete with their exact signature. - Schema files are first-class documents. Inside a
.cmschema file the §9 keywords, member shapes, and types complete (optionalonly in interfaces,voidonly in return position), every declaration hovers with its signature andsince/optionalmetadata, and the outline mirrors the file. Editing a schema buffer updates the contract the editor's scripts see immediately — even before saving — and a broken schema edit falls back to its last clean parse instead of darkening every script. - Megaprogramming completion.
megaandgrammarcomplete anywhere; inside amega name(pattern) { template }declaration the pattern position offers the §8.3 fragments, combinators, and editor annotations, the template position the §8.4 constructs, and grammar bodies the §8.2 profile declarations switching to the pattern language insiderulebodies. Invocation regions (name! { … }) stay silent — they are foreign text. Files that mention megaprograms still surface expansion diagnostics (anchored in the original text) plus thecme/expandcustom request returning the expansion preview; their parse/check spans live in expanded-text coordinates, so they are not published into such buffers.
Checkmate embeds through the WHITEPAPER §13 host APIs over the shipped front end and tree-walking interpreter. Programs load from a source text, a .cm file, or a whole §10 mod tree; every load applies the same gate the CLI applies (megaprogram expansion when present, parse, standalone-import check, type check), so a program that produced a diagnostic is never invocable. Contexts carry the §5.5 execution limits — fuel, wall-clock deadline, call depth — per invocation, and hosts invoke top-level functions or §10.4 impl members (engine.gamemode.OnTick-style) — with the §9 schema system active, loads gate on the registered contract and capability calls dispatch to host providers. Invocations also enforce the §5.7 reentrancy prohibition: a capability dispatched mid-invocation cannot invoke back into the same context (the nested call fails with ErrorKind::Reentrant; the C ABI reports it as CM_ERROR_INVALID_ARG), while different contexts and other threads stay free.
The Rust shape (behind the facade's api feature):
use cme::{Engine, ExecutionLimits};
let engine = Engine::new();
let program = engine.load_source("int main() {\nreturn 40 + 2\n}\n")?;
let context = engine.create_context(&program, ExecutionLimits {
fuel: Some(1_000_000),
deadline_ms: Some(50),
max_call_depth: 64,
});
let answer = context.invoke("main", &[])?; // Value::Int(42)
# Ok::<(), Box<dyn std::error::Error>>(())The C shape (crates/cme-ffi/include/cme.h, built as libcme_ffi.a / .so):
cm_context_t* ctx = cm_engine_create_context(engine, program, &limits);
cm_future_t* future = cm_invoke(ctx, "engine.gamemode", "OnTick", NULL, 0);
cm_poll_result_t result;
while ((result = cm_future_poll(future, NULL)) == CM_PENDING) {
/* drive host IO; unreachable over the synchronous interpreter */
}
if (result == CM_ERROR) {
cm_error_t err = cm_future_get_error(future);
printf("Script execution failed: %s\n", err.message);
cm_error_free(&err);
}
cm_future_destroy(future);Hosts declare their contract in .cm schema files — one namespace root per file (§9.2), capabilities the host provides, interfaces scripts implement with impl blocks, since/optional versioning (§9.5), and requires edges (§9.4):
// schemas/engine.cm
schema engine 1.4.0
struct TextureHandle {
int id
}
since 1.0.0 capability graphics {
TextureHandle LoadTexture(str path)
}
since 1.0.0 interface gamemode {
GameState InitGame(GameConfig config)
}
Register the schema (with the engine or the CLI's --schema flag) and every load enforces the contract at compile time: capability calls type-check against the schema signatures and require the import, import engine.assets itself demands the capability's prerequisite (the §9.4 "cannot import or call"), impl engine.gamemode blocks must implement every required visible member with the exact signature, an impl naming an ungranted or unknown namespace is an error, members introduced after the program's target version are hidden (§9.5), and requires edges gate both directions. Schema-authoring defects are rejected too: an optional capability member (§9.5 defines the flag for interfaces) and a block tagged since beyond the schema's own version both fail validation.
Rust hosts get compile-time-verified bindings (§9.6) through a procedural macro that runs the real schema parser at host build time:
cme_schema_bindings!("schemas/engine.cm"); // generates the `engine` module
struct HostGraphics;
impl engine::EngineGraphicsCapability for HostGraphics {
fn load_texture(&self, path: String) -> engine::TextureHandle { /* ... */ }
// missing members or wrong signatures = host COMPILE error
}
let mut engine = cme::Engine::new();
engine.register_schema(engine::schema())?; // same file, no re-parse
engine::register_engine_graphics(&mut engine, std::sync::Arc::new(HostGraphics))?;The macro also generates typed interface proxies for host → script calls and native Rust structs/enums for the §9.3 boundary types, packing and unpacking script values by name. Proxies construct through engine::EngineGamemodeProxy::new(&context) or the WHITEPAPER §13.1 shape, context.get_interface::<EngineGamemodeProxy>()? — construction fails when the loaded program never implemented the interface. Enable the facade's schema-macro feature.
C hosts get a generated header: cme codegen-c schemas/engine.cm emits function-pointer typedefs per capability member, a vtable, and a registration macro whose _Static_asserts (via _Generic) verify every host implementation's signature AT COMPILE TIME — a missing member fails the preprocessor, a wrong signature fails the assert. Interface members become exact-arity invocation helpers, and schema structs/enums gain pack/unpack helpers over the cm_value ABI (primitives, owned char* strings, nested schema types by address; container shapes ride the generic accessors). One header per namespace; several headers coexist in one translation unit. The header is not just a demo: cme-ffi's build generates it from apps/c_host/engine.cm and the C host application compiles against it, so cargo test exercises the full flow.
A program that calls a capability with no registered provider fails the LOAD — wiring mistakes are deterministic before any invocation, and capability calls at runtime dispatch to the provider through an engine-agnostic boundary the tree walker uses today and the bytecode VM will use unchanged.
apps/rust_host is a working Rust consumer (cme-rust-host <file.cm | mod_dir> <entry> [args...] --fuel N --deadline-ms N --depth N --schema <schema.cm>, plus --schema-demo for the full §9.6 generated-bindings walkthrough over its own schemas/game.cm — the host implements the macro-generated capability trait, so its signatures are verified at the app's compile time), and apps/c_host is a working C consumer with a Makefile — the same C file also runs inside cargo test via cme-ffi's build script, which generates the schema header the consumer includes, so the workspace test run exercises the real C client AND its compile-time-verified bindings end to end.
The repository is a Cargo workspace with focused crates:
| Crate | Purpose | Status |
|---|---|---|
cme-core |
Shared AST and language data models | Full language surface |
cme-compiler |
Lexer, parser, diagnostics, validator, type checker, parse_source, the mega megaprogram subsystem, and the mods multi-file mod loader |
Working front-end for the full surface plus §8 megaprogramming and §10 mods |
cme-interp |
Interpreter | Working tree-walking interpreter (full surface) |
cme-api |
Rust host embedding API (§13.1): Engine, CompiledProgram, Context, ExecutionLimits, schema registration, capability providers |
Working over source files and mod trees, schema-gated |
cme-ffi |
Stable C host API (§13.2): cme.h, cm_* ABI, staticlib + cdylib, schema + provider surface |
Working; the C host app runs in cargo test |
cme-schema-macro |
cme_schema_bindings! — compile-time-verified Rust host bindings from .cm schema files (§9.6) |
Working; capability traits, proxies, descriptors |
cme-lsp |
Language server (cme lsp): salsa incremental analysis, auto-detected mods + schemas (§10/§9), schema-aware diagnostics and completion, schema-file authoring features, §8 megaprogramming completion, hover, definition, references, document symbols, semantic tokens, megaprogram expansion preview |
Working over single files, mod trees, and §9 schema files; serves stdio from the unified cme binary |
cme-runtime |
Runtime services and built-ins | Placeholder |
cme |
Facade package and optional CLI | Working lex/ast/check/run/expand/schema/codegen-c toolchain; check/ast/run accept mod directories and --schema contracts |
apps/rust_host |
Rust host application (§13.1 consumer) | Loads file/mod, limits flags, --schema registration, and --schema-demo: the full generated-bindings flow (compile-time-verified provider, typed proxies) |
apps/c_host |
C host application (§13.2 consumer) | 246 self-checks incl. the schema flow, the §5.7 reentrancy rejection, AND the generated schema header consumed for real, run by cargo test and standalone |
The root cme package exposes workspace crates through optional core, compiler, interp, runtime, api, schema-macro, and lsp features. Enabling cli enables the toolchain crates plus the language server; api enables the Rust host API (flat re-exports Engine, ExecutionLimits, CompiledProgram, Context, Value); schema-macro enables cme::cme_schema_bindings. The default build intentionally exposes no root APIs.
Build the toolchain:
cargo build --features cliRun tests:
cargo test --workspaceRun tests with the complete facade enabled:
cargo test --workspace --features cliCheck formatting and lints:
cargo fmt
cargo clippy --workspace --all-targets