//! Foundation crate of the AILang toolchain. //! //! `core` owns the data model that every other crate consumes: //! //! - **AST** — see [`ast`]. The source of an AILang translation unit is an //! [`ast::Module`] expressed as JSON; the types here mirror that schema //! one-to-one via serde. //! - **Canonical JSON** — see [`canonical`]. A deterministic byte form //! (no whitespace, lexicographically sorted object keys) used as the //! pre-image for hashing. //! - **Content-addressed hashes** — see [`hash`] and [`def_hash`]. Each //! definition gets a stable 16-hex-char BLAKE3 prefix derived from its //! canonical bytes, which is how the rest of the pipeline refers to it. //! - **Pretty-printing** — see [`pretty`]. AST → human-readable text for //! diff/review. The JSON form remains the source of truth. //! - **Workspace loading** — see [`workspace`] and [`load_workspace`]. //! Resolves transitive imports of an entry module and returns a //! [`Workspace`]. //! - **AST → AST desugaring** — see [`desugar`]. Pure rewrite that runs //! *after* `load_module` and before the typechecker / codegen //! consume a module. Iter 16a flattens nested constructor patterns //! in `match` so downstream stages always see single-level patterns; //! canonical bytes / hashes are unaffected because the pass runs in //! memory only. //! //! ## Place in the pipeline //! //! `core` → `check` (typecheck) → `codegen` (LLVM emission) → `ail` CLI. //! `core` has no upstream dependencies inside the workspace; everything //! else depends on it. //! //! ## Central invariant //! //! Canonical JSON is **deterministic**: the byte output of //! [`canonical::to_bytes`] for a given value never changes across runs, //! versions of `serde_json`, or platforms. Hashes are //! **content-addressed**: equal canonical bytes ⇒ equal hash. Schema //! identifier is `ailang/v0`; a [`Module`] whose `schema` field disagrees //! is rejected at load time. Schema extensions added in later iterations //! (e.g. parameterised ADTs in Iter 13a) are written so that all //! pre-existing on-disk hashes remain bit-identical — see //! [`ast::TypeDef::vars`] and the `args` field of [`ast::Type::Con`]. //! //! ## Entry points //! //! - Read a single module from disk: [`load_module`]. //! - Read an entry module plus all imports: [`load_workspace`]. //! - Hash a [`Def`]: [`def_hash`]. Hash a whole [`Module`]: //! [`workspace::module_hash`]. //! - Form-(A) text projection of a [`Module`]: see //! `ailang-surface::print` (round-trippable; the inverse of //! `ailang-surface::parse`). //! - Diagnostic stringification (manifest summary, type/pattern //! for error messages): [`pretty::manifest`], //! [`pretty::type_to_string`], [`pretty::pattern_to_string`]. pub mod ast; pub mod canonical; pub mod desugar; pub mod hash; pub mod pretty; pub mod primitives; pub mod workspace; pub use ast::{ def_kind, def_name, ConstDef, Def, FnDef, Import, Literal, Module, Term, Type, }; pub use hash::def_hash; pub use workspace::{load_workspace, module_hash, Workspace, WorkspaceLoadError}; /// Errors raised while loading a single module file. /// /// Workspace-level loading uses a richer enum that wraps this one; see /// [`WorkspaceLoadError`]. #[derive(Debug, thiserror::Error)] pub enum Error { /// Module file parses as JSON but its `schema` field does not match /// [`SCHEMA`]. #[error("schema mismatch: expected {expected:?}, got {got:?}")] SchemaMismatch { expected: String, got: String }, /// Module file failed to parse as JSON or did not match the AST /// schema (missing/unexpected fields). #[error("json: {0}")] Json(#[from] serde_json::Error), /// File could not be read. #[error("io: {0}")] Io(#[from] std::io::Error), } /// Result alias scoped to the crate-level [`Error`]. pub type Result = std::result::Result; /// Schema identifier this version of `core` accepts. Every loaded /// [`Module`] must carry exactly this string in its `schema` field, or /// loading fails with [`Error::SchemaMismatch`]. pub const SCHEMA: &str = "ailang/v0"; /// Iter 20f: complete LLM-targeted specification of Form-A — the /// canonical authoring surface (Decision 6). Embedded verbatim into /// any prompt that asks an LLM to produce or edit AILang code; in /// particular, `ail merge-prose` includes it in the round-trip /// prompt template. The string is the raw bytes of /// `specs/form_a.md`, co-located with this crate so the spec sits /// next to the AST it describes; a drift test /// (`tests/spec_drift.rs`) walks every AST enum variant and asserts /// its serde tag appears in this string. Adding a new variant /// without updating the spec fails the test suite. pub const FORM_A_SPEC: &str = include_str!("../specs/form_a.md"); /// Load a single module file, validate its schema, and deserialize it /// into a [`Module`]. /// /// This is the lowest-level entry point and does **not** resolve imports /// — for the transitive closure use [`load_workspace`]. Errors are /// returned as [`Error::Io`] / [`Error::Json`] / [`Error::SchemaMismatch`]. pub fn load_module(path: &std::path::Path) -> Result { let bytes = std::fs::read(path)?; let module: Module = serde_json::from_slice(&bytes)?; if module.schema != SCHEMA { return Err(Error::SchemaMismatch { expected: SCHEMA.to_string(), got: module.schema, }); } Ok(module) }