1be2c1f145
Eighteen markdown list items in module- and item-doc comments had continuation lines that were not indented to align under the item text, so rustdoc rendered them as separate paragraphs and broke the list. Corrected the indentation (and, at two sites where a general wrap-up sentence followed a sublist, separated it with a blank doc line so it does not mis-attach to the last item). Doc whitespace only; no prose reworded, no code changed. Workspace clippy is now warning-clean.
141 lines
6.2 KiB
Rust
141 lines
6.2 KiB
Rust
//! 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`],
|
|
//! [`workspace::load_modules_with`], and [`workspace::build_workspace`].
|
|
//! Core exposes the two-phase composition: DFS-only loader returning a
|
|
//! modules-map, then a separate validation + registry-construction
|
|
//! step that takes a caller-supplied `implicit_imports` slice. The
|
|
//! public single-call entry point lives in `ailang-surface` (see
|
|
//! [`ailang_surface::load_workspace`](https://docs.rs/ailang-surface)),
|
|
//! which composes the two halves with a prelude-inject step in
|
|
//! between. pd.2 retired the pd.1 `load_workspace_with` shim and
|
|
//! moved the prelude embed into surface.
|
|
//! - **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`].
|
|
//! - Assemble a workspace from an entry module plus all imports:
|
|
//! `ailang_surface::load_workspace` (which composes
|
|
//! [`workspace::load_modules_with`] + a prelude inject +
|
|
//! [`workspace::build_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::{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<T> = std::result::Result<T, Error>;
|
|
|
|
/// 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";
|
|
|
|
/// Complete LLM-targeted specification of Form-A — the canonical
|
|
/// authoring surface (see `design/contracts/0001-authoring-surface.md`).
|
|
/// 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 `ailang_surface::load_workspace`
|
|
/// (which composes [`workspace::load_modules_with`] + a prelude inject
|
|
/// + [`workspace::build_workspace`]). Errors are returned as
|
|
/// [`Error::Io`] / [`Error::Json`] / [`Error::SchemaMismatch`].
|
|
pub fn load_module(path: &std::path::Path) -> Result<Module> {
|
|
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)
|
|
}
|