hara-native 0.1.13

HAL-free native host runtime and package launcher for Hara
Documentation
use hara_native::{kernel::Form, Runtime};
use sha2::{Digest, Sha256};
use std::{
    collections::HashSet,
    env, fs,
    path::{Path, PathBuf},
    process,
};

const MAGIC: &[u8; 4] = b"HLC1";
const SPEC_PATH: &str = "specs/language-v1.edn";
const LOWERING_PATH: &str = "specs/lowering-v1.edn";
const ASSET_PATH: &str = "assets/language-conformance.hlc";
const ERROR_EXPECTATION_PREFIX: &str = "!error:";

#[derive(Debug, Clone, PartialEq, Eq)]
enum Expectation {
    Display(String),
    Error(String),
}

impl Expectation {
    fn encoded(&self) -> String {
        match self {
            Self::Display(value) => value.clone(),
            Self::Error(value) => format!("{ERROR_EXPECTATION_PREFIX}{value}"),
        }
    }
}

#[derive(Debug, Clone)]
struct LanguageCase {
    id: String,
    layer: String,
    source: String,
    expectation: Expectation,
    browser_safe: bool,
}

fn main() {
    if let Err(error) = run() {
        eprintln!("hara-native-language-conformance-artifact: {error}");
        process::exit(1);
    }
}

fn run() -> Result<(), String> {
    let command = env::args().nth(1).unwrap_or_else(|| "check".into());
    let root = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
    let specification =
        fs::read_to_string(root.join(SPEC_PATH)).map_err(|error| error.to_string())?;
    let compiled = compile_specification(&root, &specification)?;
    let asset = root.join(ASSET_PATH);
    match command.as_str() {
        "generate" => {
            fs::create_dir_all(asset.parent().expect("artifact has a parent"))
                .map_err(|error| error.to_string())?;
            fs::write(&asset, &compiled).map_err(|error| error.to_string())?;
            println!("wrote {} bytes to {}", compiled.len(), asset.display());
            Ok(())
        }
        "check" => {
            let tracked = fs::read(&asset).map_err(|error| error.to_string())?;
            if tracked != compiled {
                return Err(format!("{} is stale; run with generate", asset.display()));
            }
            println!("{} is current ({} bytes)", asset.display(), tracked.len());
            Ok(())
        }
        _ => Err("usage: hara-native-language-conformance-artifact [generate|check]".into()),
    }
}

fn compile_specification(root: &Path, source: &str) -> Result<Vec<u8>, String> {
    let forms = hara_native::kernel::parse_forms(source)?;
    let [Form::Map(manifest)] = forms.as_slice() else {
        return Err("language conformance specification must contain one map".into());
    };
    expect_keyword(
        manifest,
        "format",
        "specification",
        "hara-native/language-conformance",
    )?;
    match entry(manifest, "version") {
        Some(Form::Number(1)) => {}
        _ => return Err("specification :version must be 1".into()),
    }
    let Form::Vector(cases) = required(manifest, "cases", "specification")? else {
        return Err("specification :cases must be a vector".into());
    };
    if cases.is_empty() {
        return Err("specification :cases must not be empty".into());
    }

    let registry_source = fs::read_to_string(root.join("specs/language/registry/bytecode-vm.edn"))
        .map_err(|error| error.to_string())?;
    let registry = single_map(&registry_source, "imported bytecode VM registry fixture")?;
    let lowering_source =
        fs::read_to_string(root.join(LOWERING_PATH)).map_err(|error| error.to_string())?;
    let lowering = single_map(&lowering_source, "lowering ledger")?;
    let runtime = Runtime::new();
    let mut payload = Vec::new();
    let mut identifiers = HashSet::new();
    put_u32(&mut payload, cases.len())?;
    for form in cases {
        let language_case = parse_case(form, &registry, &lowering)?;
        if !identifiers.insert(language_case.id.clone()) {
            return Err(format!(
                "specification has a duplicate case id :{}",
                language_case.id
            ));
        }
        let artifact = runtime
            .compile_bytecode_artifact(&language_case.source)
            .map_err(|error| format!(":{} failed to compile: {error}", language_case.id))?;
        put_bytes(&mut payload, language_case.id.as_bytes())?;
        put_bytes(&mut payload, language_case.layer.as_bytes())?;
        put_bytes(&mut payload, language_case.expectation.encoded().as_bytes())?;
        put_u32(&mut payload, usize::from(language_case.browser_safe))?;
        put_bytes(&mut payload, language_case.source.as_bytes())?;
        put_bytes(&mut payload, &artifact)?;
    }
    let mut output = MAGIC.to_vec();
    output.extend_from_slice(&Sha256::digest(&payload));
    output.extend_from_slice(&payload);
    Ok(output)
}

fn parse_case(
    form: &Form,
    registry: &[(Form, Form)],
    lowering: &[(Form, Form)],
) -> Result<LanguageCase, String> {
    let Form::Map(case) = form else {
        return Err("every language conformance case must be a map".into());
    };
    let id = required_keyword(case, "id", "case")?;
    let layer = required_keyword(case, "layer", &id)?;
    if !matches!(layer.as_str(), "parser" | "evaluator" | "native-abi") {
        return Err(format!(":{id} has unsupported layer :{layer}"));
    }
    let program = required(case, "program", &id)?;
    let source = program.to_string();
    if source.contains("std.foundation") || source.contains("std.lib.") {
        return Err(format!(":{id} must not reference a source library"));
    }
    if layer == "native-abi" && !source.contains("std.native.") && !source.contains("std.protocol.")
    {
        return Err(format!(
            ":{id} native ABI case must call std.native or std.protocol"
        ));
    }
    let expectation = expectation(case, &id)?;
    let browser_safe = optional_bool(case, "browser-safe", false)?;
    validate_origin(case, &id, program, &expectation, registry, lowering)?;
    Ok(LanguageCase {
        id,
        layer,
        source,
        expectation,
        browser_safe,
    })
}

fn validate_origin(
    case: &[(Form, Form)],
    id: &str,
    program: &Form,
    case_expectation: &Expectation,
    registry: &[(Form, Form)],
    lowering: &[(Form, Form)],
) -> Result<(), String> {
    let Form::Map(origin) = required(case, "origin", id)? else {
        return Err(format!(":{id} :origin must be a map"));
    };
    let kind = required_keyword(origin, "kind", &format!(":{id} :origin"))?;
    let source_case = required_keyword(origin, "case", &format!(":{id} :origin"))?;
    let document = match kind.as_str() {
        "registry" => {
            let expected_document = "language/registry/bytecode-vm.edn";
            if required_string(origin, "document", &format!(":{id} :origin"))? != expected_document
            {
                return Err(format!(
                    ":{id} registry origin must reference {expected_document}"
                ));
            }
            registry
        }
        "lowering" => lowering,
        _ => return Err(format!(":{id} has unsupported origin kind :{kind}")),
    };
    let Form::Vector(cases) = required(document, "cases", "origin document")? else {
        return Err("origin document :cases must be a vector".into());
    };
    let matching = cases
        .iter()
        .filter_map(|candidate| match candidate {
            Form::Map(entries)
                if matches!(entry(entries, "id"), Some(Form::Keyword(candidate_id)) if candidate_id == &source_case) =>
            {
                Some(entries)
            }
            _ => None,
        })
        .collect::<Vec<_>>();
    let [source] = matching.as_slice() else {
        return Err(format!(
            ":{id} origin :{source_case} must exist exactly once"
        ));
    };
    let source_program = match kind.as_str() {
        "registry" => {
            let Form::String(source) = required(source, "source", &source_case)? else {
                return Err(format!(":{source_case} :source must be a string"));
            };
            let forms = hara_native::kernel::parse_forms(source)
                .map_err(|error| format!(":{source_case} source failed to parse: {error}"))?;
            let [form] = forms.as_slice() else {
                return Err(format!(":{source_case} must contain one source form"));
            };
            form.clone()
        }
        "lowering" => required(source, "program", &source_case)?.clone(),
        _ => unreachable!("origin kind was checked above"),
    };
    if program != &source_program {
        return Err(format!(
            ":{id} program differs from its :{kind} origin :{source_case}"
        ));
    }
    let expected = expectation(source, &source_case)?;
    if case_expectation != &expected {
        return Err(format!(
            ":{id} expectation differs from its :{kind} origin :{source_case}"
        ));
    }
    Ok(())
}

fn expectation(case: &[(Form, Form)], context: &str) -> Result<Expectation, String> {
    let Form::Map(expect) = required(case, "expect", context)? else {
        return Err(format!(":{context} :expect must be a map"));
    };
    match (
        entry(expect, "display"),
        entry(expect, "error"),
        entry(expect, "error-category"),
    ) {
        (Some(Form::String(value)), None, None) => Ok(Expectation::Display(value.clone())),
        (None, Some(Form::Keyword(value)), None) => Ok(Expectation::Error(value.replace('-', " "))),
        (None, Some(Form::String(value)), None) => Ok(Expectation::Error(value.clone())),
        (None, None, Some(Form::String(value))) => Ok(Expectation::Error(value.clone())),
        _ => Err(format!(
            ":{context} :expect must contain exactly one display or error expectation"
        )),
    }
}

fn single_map(source: &str, context: &str) -> Result<Vec<(Form, Form)>, String> {
    let forms = hara_native::kernel::parse_forms(source)?;
    let [Form::Map(document)] = forms.as_slice() else {
        return Err(format!("{context} must contain one map"));
    };
    Ok(document.clone())
}

fn entry<'a>(entries: &'a [(Form, Form)], key: &str) -> Option<&'a Form> {
    entries
        .iter()
        .find_map(|(candidate, value)| match candidate {
            Form::Keyword(name) if name == key => Some(value),
            _ => None,
        })
}

fn required<'a>(entries: &'a [(Form, Form)], key: &str, context: &str) -> Result<&'a Form, String> {
    entry(entries, key).ok_or_else(|| format!("{context} is missing :{key}"))
}

fn required_keyword(entries: &[(Form, Form)], key: &str, context: &str) -> Result<String, String> {
    match required(entries, key, context)? {
        Form::Keyword(value) if !value.is_empty() => Ok(value.clone()),
        _ => Err(format!("{context} :{key} must be a keyword")),
    }
}

fn expect_keyword(
    entries: &[(Form, Form)],
    key: &str,
    context: &str,
    expected: &str,
) -> Result<(), String> {
    match required(entries, key, context)? {
        Form::Keyword(value) if value == expected => Ok(()),
        _ => Err(format!("{context} :{key} must be :{expected}")),
    }
}

fn required_string(entries: &[(Form, Form)], key: &str, context: &str) -> Result<String, String> {
    match required(entries, key, context)? {
        Form::String(value) if !value.is_empty() => Ok(value.clone()),
        _ => Err(format!("{context} :{key} must be a non-empty string")),
    }
}

fn optional_bool(entries: &[(Form, Form)], key: &str, default: bool) -> Result<bool, String> {
    match entry(entries, key) {
        None => Ok(default),
        Some(Form::Bool(value)) => Ok(*value),
        _ => Err(format!(":{key} must be a boolean")),
    }
}

fn put_u32(output: &mut Vec<u8>, value: usize) -> Result<(), String> {
    output.extend_from_slice(
        &u32::try_from(value)
            .map_err(|_| "language conformance artifact exceeds u32 limits")?
            .to_le_bytes(),
    );
    Ok(())
}

fn put_bytes(output: &mut Vec<u8>, value: &[u8]) -> Result<(), String> {
    put_u32(output, value.len())?;
    output.extend_from_slice(value);
    Ok(())
}

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

    #[test]
    fn checked_in_specification_compiles_to_a_checksummed_hlc1_artifact() {
        let root = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
        let source = fs::read_to_string(root.join(SPEC_PATH)).unwrap();
        let compiled = compile_specification(&root, &source).unwrap();
        assert!(compiled.starts_with(MAGIC));
        assert!(compiled.len() > 36);
        let digest: [u8; 32] = Sha256::digest(&compiled[36..]).into();
        assert_eq!(&compiled[4..36], &digest);
    }

    #[test]
    fn provenance_rejects_a_program_that_drifted_from_the_lowering_ledger() {
        let root = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
        let source = fs::read_to_string(root.join(SPEC_PATH)).unwrap();
        let drifted = source.replacen(
            "(std.protocol.icount.ICount/count (std.native.Base/vector 1 2 3))",
            "(std.protocol.icount.ICount/count (std.native.Base/vector 1 2))",
            1,
        );
        assert!(compile_specification(&root, &drifted)
            .unwrap_err()
            .contains("differs from its :lowering origin"));
    }
}