hara-native 0.1.8

HAL-free native host runtime and package launcher for Hara
Documentation
use sha2::{Digest, Sha256};

const HNC_MAGIC: &[u8; 4] = b"HNC1";
const HNC_ARTIFACT: &[u8] = include_bytes!("../../../assets/native-protocol-conformance.hnc");
const ERROR_EXPECTATION_PREFIX: &str = "!error:";

struct HncCase<'a> {
    id: &'a str,
    expected: HncExpectation<'a>,
    artifact: &'a [u8],
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum HncExpectation<'a> {
    Display(&'a str),
    Error(&'a str),
}

impl<'a> HncExpectation<'a> {
    fn parse(value: &'a str) -> Self {
        value
            .strip_prefix(ERROR_EXPECTATION_PREFIX)
            .map(Self::Error)
            .unwrap_or(Self::Display(value))
    }
}

struct HncSuite<'a> {
    id: &'a str,
    setup: &'a [u8],
    cases: Vec<HncCase<'a>>,
}

struct HncReader<'a> {
    bytes: &'a [u8],
    offset: usize,
}

impl<'a> HncReader<'a> {
    fn new(bytes: &'a [u8]) -> Self {
        Self { bytes, offset: 0 }
    }

    fn u32(&mut self) -> Result<usize, String> {
        Ok(u32::from_le_bytes(self.take(4)?.try_into().expect("four bytes")) as usize)
    }

    fn bytes(&mut self) -> Result<&'a [u8], String> {
        let length = self.u32()?;
        self.take(length)
    }

    fn take(&mut self, length: usize) -> Result<&'a [u8], String> {
        let end = self
            .offset
            .checked_add(length)
            .ok_or_else(|| "HNC1 field length overflows the artifact".to_owned())?;
        let value = self
            .bytes
            .get(self.offset..end)
            .ok_or_else(|| "HNC1 field exceeds the artifact".to_owned())?;
        self.offset = end;
        Ok(value)
    }

    fn finish(&self) -> Result<(), String> {
        if self.offset == self.bytes.len() {
            Ok(())
        } else {
            Err("HNC1 artifact has trailing bytes".into())
        }
    }
}

fn parse_hnc(bytes: &[u8]) -> Result<Vec<HncSuite<'_>>, String> {
    if !bytes.starts_with(HNC_MAGIC) || bytes.len() < 36 {
        return Err("HNC1 artifact has an invalid or truncated header".into());
    }
    let (digest, payload) = bytes[4..].split_at(32);
    let expected: [u8; 32] = digest.try_into().expect("HNC1 digest has 32 bytes");
    let actual: [u8; 32] = Sha256::digest(payload).into();
    if actual != expected {
        return Err("HNC1 artifact checksum mismatch".into());
    }
    let mut reader = HncReader::new(payload);
    let count = reader.u32()?;
    if count != 2 {
        return Err(format!(
            "HNC1 must contain native and protocol suites, found {count}"
        ));
    }
    let mut suites = Vec::with_capacity(count);
    for _ in 0..count {
        let id = std::str::from_utf8(reader.bytes()?).map_err(|_| "HNC1 suite id is not UTF-8")?;
        let setup = reader.bytes()?;
        let case_count = reader.u32()?;
        if case_count == 0 || case_count > reader.bytes.len() / 12 {
            return Err(format!("HNC1 suite {id} has an invalid case count"));
        }
        let mut cases = Vec::with_capacity(case_count);
        for _ in 0..case_count {
            let case_id =
                std::str::from_utf8(reader.bytes()?).map_err(|_| "HNC1 case id is not UTF-8")?;
            let expected = std::str::from_utf8(reader.bytes()?)
                .map_err(|_| "HNC1 expected display is not UTF-8")?;
            cases.push(HncCase {
                id: case_id,
                expected: HncExpectation::parse(expected),
                artifact: reader.bytes()?,
            });
        }
        suites.push(HncSuite { id, setup, cases });
    }
    reader.finish()?;
    if suites[0].id != "native" || suites[1].id != "protocol" {
        return Err("HNC1 suites must be ordered native then protocol".into());
    }
    Ok(suites)
}

fn requires_foundation(program: &crate::vm::Program) -> bool {
    program.constants.iter().any(|value| {
        let text = value.display();
        text.starts_with("std.foundation/") || text.starts_with("std.foundation.")
    })
}

fn normalized_error_category(error: &str) -> Option<&'static str> {
    let error = error.trim_start_matches("error: ");
    if error.starts_with("protocol/arity:") {
        Some("protocol/arity")
    } else if error.starts_with("protocol/unsupported-receiver:") {
        Some("protocol/unsupported-receiver")
    } else if error.contains("expects") {
        let arity = [
            "expects one ",
            "expects two ",
            "expects three ",
            "expects four ",
            "expects at least ",
            "expects no ",
        ]
        .iter()
        .any(|marker| error.contains(marker));
        if arity {
            Some("native/arity")
        } else if error.contains("number")
            || error.contains("numeric")
            || error.contains("integer")
            || error.contains("string")
        {
            Some("native/type")
        } else {
            Some("native/arity")
        }
    } else {
        None
    }
}

fn assert_outcome(case: &HncCase<'_>, actual: Result<String, String>) -> Result<(), String> {
    match (case.expected, actual) {
        (HncExpectation::Display(expected), Ok(actual)) if actual == expected => Ok(()),
        (HncExpectation::Display(expected), Ok(actual)) => Err(format!(
            "{} expected display {expected:?}, observed {actual:?}",
            case.id
        )),
        (HncExpectation::Display(expected), Err(error)) => Err(format!(
            "{} expected display {expected:?}, raised {error}",
            case.id
        )),
        (HncExpectation::Error(expected), Err(error))
            if normalized_error_category(&error) == Some(expected) =>
        {
            Ok(())
        }
        (HncExpectation::Error(expected), Err(error)) => Err(format!(
            "{} expected error {expected:?}, observed {error:?} ({:?})",
            case.id,
            normalized_error_category(&error)
        )),
        (HncExpectation::Error(expected), Ok(actual)) => Err(format!(
            "{} expected error {expected:?}, returned {actual:?}",
            case.id
        )),
    }
}

#[test]
fn core_runtime_executes_the_native_protocol_artifact_serially() {
    let suites = parse_hnc(HNC_ARTIFACT).expect("embedded HNC1 corpus is valid");
    let expected = suites.iter().map(|suite| suite.cases.len()).sum::<usize>();
    let mut runtime = crate::Runtime::core();
    let mut executed = 0;
    for suite in &suites {
        let setup = crate::vm::decode_program(suite.setup)
            .unwrap_or_else(|error| panic!("{} setup has invalid HBC0: {error}", suite.id));
        assert!(
            !requires_foundation(&setup),
            "{} setup must not require Foundation",
            suite.id
        );
        runtime
            .eval_bytecode_artifact(suite.setup)
            .unwrap_or_else(|error| panic!("{} setup failed: {error}", suite.id));
        for case in &suite.cases {
            let program = crate::vm::decode_program(case.artifact)
                .unwrap_or_else(|error| panic!("{} has invalid HBC0: {error}", case.id));
            assert!(
                !requires_foundation(&program),
                "{} must not require Foundation",
                case.id
            );
            let actual = runtime.eval_bytecode_artifact(case.artifact);
            assert_outcome(case, actual).unwrap_or_else(|error| panic!("{error}"));
            executed += 1;
        }
    }
    assert_eq!(executed, expected, "all declared native/protocol cases ran");
}

#[test]
fn hnc_expected_outcomes_reject_wrong_values_and_error_categories() {
    let value_case = HncCase {
        id: "fixture/value",
        expected: HncExpectation::Display("42"),
        artifact: &[],
    };
    assert!(assert_outcome(&value_case, Ok("41".into())).is_err());
    let error_case = HncCase {
        id: "fixture/error",
        expected: HncExpectation::Error("protocol/arity"),
        artifact: &[],
    };
    assert!(assert_outcome(
        &error_case,
        Err("protocol/unsupported-receiver: missing".into())
    )
    .is_err());
    assert_eq!(
        normalized_error_category("abs expects one numeric value"),
        Some("native/arity")
    );
    assert_eq!(
        normalized_error_category("abs expects a numeric value"),
        Some("native/type")
    );
}