aura-lang 0.1.0

Aura configuration language: deterministic, capability-secured, schema-validated configs — embeddable library + the `aura` CLI
Documentation
//! The language, rendered for a coding assistant: `aura docs --agent`.
//!
//! An assistant asked to write a manifest in a language it has not memorised will
//! guess, and guesses come out looking like YAML or Python. Aura is small enough
//! that the alternative is practical — the whole language fits in a few thousand
//! tokens, which is not true of the larger configuration languages.
//!
//! Everything here is assembled from what already exists: the keyword list the
//! lexer reserves, the stdlib manifest the language server builds completion
//! from, and the diagnostic catalogue a test keeps in step with the compiler. The
//! prose that cannot be derived — the `:` against `=` distinction, the capability
//! model, the mistakes people actually make — lives in `agent-preamble.md`.
//!
//! Assembling rather than writing is the whole point. A hand-maintained copy of
//! the method list would be a second source of truth, and this repository has
//! spent enough time discovering what those turn into.

/// Prose that no generator could produce, kept as Markdown so it is readable on
/// its own.
const PREAMBLE: &str = include_str!("../agent-preamble.md");

/// The diagnostic catalogue, which lives in this crate rather than in the book:
/// it describes the compiler, and `include_str!` cannot reach outside a package —
/// pointing it at the book made `cargo publish` fail to build the tarball. The
/// book now includes this file instead, so there is still one copy.
///
/// `tests/diagnostic_catalogue.rs` asserts it lists exactly the codes the
/// compiler can emit, so quoting it here cannot introduce a claim the compiler
/// does not honour.
const DIAGNOSTICS: &str = include_str!("../diagnostics.md");

/// Render the complete reference.
pub fn render() -> String {
    let mut out = String::with_capacity(32 * 1024);

    out.push_str("# Aura ");
    out.push_str(env!("CARGO_PKG_VERSION"));
    out.push_str(" — language reference for coding assistants\n\n");
    out.push_str(
        "Generated by `aura docs --agent` from the compiler's own definitions, so it \
         describes the binary that produced it. Regenerate rather than edit.\n\n",
    );

    out.push_str(PREAMBLE.trim_end());
    out.push_str("\n\n## Keywords\n\nAll ");
    out.push_str(&crate::lexer::token::KEYWORDS.len().to_string());
    out.push_str(" reserved words:\n\n");
    out.push_str(&crate::lexer::token::KEYWORDS.join(", "));
    out.push_str(
        ".\n\nPlus `text`, which is *not* reserved: it opens a block string only \
         where a value is expected, and is an ordinary identifier everywhere \
         else.\n",
    );

    out.push_str("\n## Standard library\n\n");
    out.push_str(
        "Methods are called on a value (`\"a\".upper()`, `xs.map(f)`); builtins are \
         global functions.\n\n",
    );
    out.push_str(&stdlib_section());

    out.push_str("\n## Diagnostics\n\n");
    out.push_str(
        "Every code is stable. When one is reported, the fix is in this table rather \
         than in guesswork.\n\n",
    );
    out.push_str(strip_front_matter(DIAGNOSTICS).trim_end());
    out.push('\n');

    out
}

/// Evaluate the stdlib manifest and render it as tables grouped by receiver.
///
/// The manifest is Aura, evaluated by Aura — the same dogfooding the language
/// server does. If it ever fails to evaluate, that is a broken manifest and the
/// test suite says so; here it degrades to a note rather than an empty section
/// pretending the standard library is empty.
fn stdlib_section() -> String {
    let Ok(json) = eval_manifest(crate::STDLIB_MANIFEST) else {
        return "_The stdlib manifest failed to evaluate; see crates/aura-lang/stdlib.aura._\n"
            .to_string();
    };
    let Some(groups) = json.as_object() else {
        return String::new();
    };

    let mut out = String::new();
    for (receiver, methods) in groups {
        let Some(methods) = methods.as_object() else {
            continue;
        };
        out.push_str("### ");
        out.push_str(if receiver == "builtins" {
            "Builtins"
        } else {
            receiver
        });
        out.push_str("\n\n| Signature | |\n| --- | --- |\n");
        for (name, spec) in methods {
            let params = spec
                .get("params")
                .and_then(|p| p.as_array())
                .map(|xs| {
                    xs.iter()
                        .filter_map(|v| v.as_str())
                        .collect::<Vec<_>>()
                        .join(", ")
                })
                .unwrap_or_default();
            let returns = spec.get("returns").and_then(|v| v.as_str()).unwrap_or("?");
            let doc = spec.get("doc").and_then(|v| v.as_str()).unwrap_or("");
            out.push_str(&format!("| `{name}({params}) -> {returns}` | {doc} |\n"));
        }
        out.push('\n');
    }
    out
}

fn eval_manifest(src: &str) -> Result<serde_json::Value, ()> {
    let tokens = crate::lexer::Lexer::new(src, 0)
        .tokenize()
        .map_err(|_| ())?;
    let module = crate::parser::Parser::new(tokens)
        .parse_module()
        .map_err(|_| ())?;
    let mut interp = crate::eval::Interpreter::new(crate::eval::Options {
        strict: false,
        dry_run: false,
    });
    let value = interp.eval_module(&module).map_err(|_| ())?;
    crate::serialize::to_json(&value).map_err(|_| ())
}

/// Drop the catalogue's own title and preamble: this document supplies its own,
/// and the heading levels have to nest under `## Diagnostics`.
fn strip_front_matter(md: &str) -> String {
    let body = md
        .split_once("\n## ")
        .map_or(md, |(_, rest)| rest)
        .to_string();
    // The remaining `## Lexing (E01xx)` headings become `###` one level down.
    let mut out = String::from("### ");
    out.push_str(&body.replace("\n## ", "\n### "));
    out
}

#[cfg(test)]
mod tests {
    use super::*;

    /// The generated reference has to actually contain the things it promises,
    /// or it is a confidently wrong document — worse for an assistant than no
    /// document at all.
    #[test]
    fn the_reference_carries_every_section_it_claims() {
        let doc = render();
        for needle in [
            "## Keywords",
            "## Standard library",
            "## Diagnostics",
            "### Builtins",
            "`:` exports, `=` does not",
        ] {
            assert!(doc.contains(needle), "missing from the reference: {needle}");
        }
    }

    /// Specifically: the stdlib section must be built from the manifest, not
    /// silently degraded. A generator that quietly emits nothing would still pass
    /// a "contains the heading" check.
    #[test]
    fn the_stdlib_section_is_populated_from_the_manifest() {
        let doc = render();
        assert!(
            !doc.contains("failed to evaluate"),
            "the stdlib manifest did not evaluate"
        );
        for needle in ["`upper() -> String`", "`map(fn) -> List`", "### String"] {
            assert!(
                doc.contains(needle),
                "expected {needle} in the stdlib tables"
            );
        }
    }

    /// Every keyword the lexer reserves is listed. An assistant that has never
    /// seen `shadow` will not invent it.
    #[test]
    fn every_reserved_word_is_listed() {
        let doc = render();
        for kw in crate::lexer::token::KEYWORDS {
            assert!(doc.contains(kw), "keyword absent from the reference: {kw}");
        }
    }
}