keleusma 0.2.2

Total Functional Stream Processor with definitive WCET and WCMU verification, targeting no_std + alloc embedded scripting
Documentation
#![cfg(all(feature = "compile", feature = "verify"))]
//! B37 residuals: unsignatured-native returns of text-bearing composites across
//! the composite kinds, complementing the tuple case in `flat_ref_tuple.rs`.
//!
//! A native builds its result with no arena, so a text field is a `StaticStr`
//! and the composite body is boxed. The compiler bakes flat construction and
//! flat access for the declared return type (text is a flat `(ptr, len)` field
//! at the host word width), so the native-result canonicalisation must promote
//! the `StaticStr` field to an arena `KStr` and pack the body flat, or the
//! access mismatches the body. These tests pin that behaviour for struct,
//! array, enum, and `Option<Text>` returns.

extern crate alloc;

use alloc::string::String;
use alloc::vec;
use keleusma::bytecode::EnumBody;
use keleusma::compiler::compile;
use keleusma::lexer::tokenize;
use keleusma::parser::parse;
use keleusma::vm::{DEFAULT_ARENA_CAPACITY, Vm, VmState};
use keleusma::{Arena, KeleusmaType, Value};

// A host enum mirroring `enum Msg { Note(Word, Word), Code(Word) }` for the
// signatured-native test below. `Note` is the larger, first variant; `Code` is
// the smaller, second variant that an unsignatured native cannot return
// correctly (B37).
#[derive(KeleusmaType, Debug, Clone, PartialEq)]
enum Msg {
    Note(i64, i64),
    Code(i64),
}

#[test]
fn native_struct_with_text_field_flattens_and_reads() {
    // A native returns a `Sensor { name: Text, id: Word }`. The body is boxed
    // with a `StaticStr` name; the compiler bakes flat struct field access.
    // The canonicalisation promotes `name` to a `KStr` and packs the struct
    // flat, so `s.id` and `s.name` read back. id 10 plus len("abcd") 4 is 14.
    let src = "use sensor() -> Sensor\n\
               use tlen(Text) -> Word\n\
               struct Sensor { name: Text, id: Word }\n\
               fn main() -> Word { let s = sensor(); s.id + tlen(s.name) }";
    let module = compile(&parse(&tokenize(src).expect("lex")).expect("parse")).expect("compile");
    let arena = Arena::with_capacity(DEFAULT_ARENA_CAPACITY);
    let mut vm = Vm::new(module, &arena).expect("verify");
    vm.register_native("sensor", |_| {
        Ok(Value::struct_value(
            String::from("Sensor"),
            vec![
                (String::from("name"), Value::StaticStr(String::from("abcd"))),
                (String::from("id"), Value::Int(10)),
            ],
        ))
    });
    vm.register_fn("tlen", |s: String| -> i64 { s.len() as i64 });
    match vm.call(&[]).expect("call") {
        VmState::Finished(v) => assert_eq!(v, Value::Int(14)),
        other => panic!("expected finished, got {:?}", other),
    }
}

#[test]
fn native_array_of_text_flattens_and_indexes() {
    // A native returns `[Text; 2]` of `StaticStr`. Each element promotes to a
    // `KStr` and the array packs flat. len("ab") 2 plus len("cde") 3 is 5.
    let src = "use lines() -> [Text; 2]\n\
               use tlen(Text) -> Word\n\
               fn main() -> Word { let a = lines(); tlen(a[0]) + tlen(a[1]) }";
    let module = compile(&parse(&tokenize(src).expect("lex")).expect("parse")).expect("compile");
    let arena = Arena::with_capacity(DEFAULT_ARENA_CAPACITY);
    let mut vm = Vm::new(module, &arena).expect("verify");
    vm.register_native("lines", |_| {
        Ok(Value::array(vec![
            Value::StaticStr(String::from("ab")),
            Value::StaticStr(String::from("cde")),
        ]))
    });
    vm.register_fn("tlen", |s: String| -> i64 { s.len() as i64 });
    match vm.call(&[]).expect("call") {
        VmState::Finished(v) => assert_eq!(v, Value::Int(5)),
        other => panic!("expected finished, got {:?}", other),
    }
}

#[test]
fn native_enum_largest_text_variant_flattens_and_matches() {
    // A native returns the `Note(Text)` variant of `Msg { Note(Text),
    // Code(Word) }`. `Note` carries the largest payload (a two-word Text),
    // so the body the canonicalisation packs with `min_payload == 0` happens
    // to equal the compiler's `word + payload_max` size and the match reads
    // back. len("hey") is 3. A smaller variant of the same enum is the
    // documented residual below.
    let src = "use note() -> Msg\n\
               use tlen(Text) -> Word\n\
               enum Msg { Note(Text), Code(Word) }\n\
               fn main() -> Word { let m = note(); match m { Msg::Note(s) => tlen(s), Msg::Code(n) => n } }";
    let module = compile(&parse(&tokenize(src).expect("lex")).expect("parse")).expect("compile");
    let arena = Arena::with_capacity(DEFAULT_ARENA_CAPACITY);
    let mut vm = Vm::new(module, &arena).expect("verify");
    vm.register_native("note", |_| {
        Ok(Value::Enum(EnumBody::boxed(
            String::from("Msg"),
            String::from("Note"),
            vec![Value::StaticStr(String::from("hey"))],
        )))
    });
    vm.register_fn("tlen", |s: String| -> i64 { s.len() as i64 });
    match vm.call(&[]).expect("call") {
        VmState::Finished(v) => assert_eq!(v, Value::Int(3)),
        other => panic!("expected finished, got {:?}", other),
    }
}

#[test]
fn native_enum_smaller_later_variant_reads_correctly() {
    // B37 / audit finding 25 follow-up, now fixed. `Code(Word)` is the second
    // variant (discriminant 1) with a smaller payload than the first variant
    // `Note(Text)`. The arena-less `EnumBody::boxed` constructor records
    // discriminant 0 and no largest-variant padding, but the VM now corrects
    // those hints from the module's `enum_layouts` before flattening (the
    // discriminant is derived from the enum type, the way Rust lays out an
    // enum, rather than asserted by the caller). So an unsignatured native that
    // returns `Code(7)` through the bare `boxed` constructor now reads back as
    // `Code(7)`, matching the compiler's baked flat access.
    let src = "use code() -> Msg\n\
               enum Msg { Note(Text), Code(Word) }\n\
               fn main() -> Word { let m = code(); match m { Msg::Note(_) => 0, Msg::Code(n) => n } }";
    let module = compile(&parse(&tokenize(src).expect("lex")).expect("parse")).expect("compile");
    let arena = Arena::with_capacity(DEFAULT_ARENA_CAPACITY);
    let mut vm = Vm::new(module, &arena).expect("verify");
    vm.register_native("code", |_| {
        Ok(Value::Enum(EnumBody::boxed(
            String::from("Msg"),
            String::from("Code"),
            vec![Value::Int(7)],
        )))
    });
    match vm.call(&[]).expect("call") {
        VmState::Finished(v) => assert_eq!(v, Value::Int(7)),
        other => panic!("expected finished, got {:?}", other),
    }
}

#[test]
fn signatured_native_returns_smaller_enum_variant_correctly() {
    // B37 signatured-native direction. The unsignatured native above silently
    // misreads a non-first, non-largest enum variant, because the arena-less
    // `EnumBody::boxed` loses the discriminant and the largest-variant padding.
    // A SIGNATURED native registered through `register_fn` marshals its result
    // through the declared type's `into_value_ctx` (B28 P3), which supplies the
    // correct discriminant and padding from the type, so the smaller variant
    // reads back. This is the strategic fix the value-driven flatten cannot
    // reach, and it needs no further marshalling work: composite returns are
    // already supported by the marshalling family.
    let src = "use code() -> Msg\n\
               enum Msg { Note(Word, Word), Code(Word) }\n\
               fn main() -> Word { let m = code(); match m { Msg::Note(a, b) => a + b, Msg::Code(n) => n } }";
    let module = compile(&parse(&tokenize(src).expect("lex")).expect("parse")).expect("compile");
    let arena = Arena::with_capacity(DEFAULT_ARENA_CAPACITY);
    let mut vm = Vm::new(module, &arena).expect("verify");
    vm.register_fn("code", || -> Msg { Msg::Code(7) });
    match vm.call(&[]).expect("call") {
        VmState::Finished(v) => assert_eq!(v, Value::Int(7)),
        other => panic!("expected finished, got {:?}", other),
    }
}

#[test]
fn native_option_text_return_matches() {
    // A native returns `Option<Text>`. `Option` is kept boxed by both the
    // compiler (baked boxed access) and the canonicalisation (the `Option`
    // arm), so the two agree without flattening. This pins that the boxed
    // path destructures correctly. len("hello") is 5.
    let src = "use getopt() -> Option<Text>\n\
               use tlen(Text) -> Word\n\
               fn main() -> Word { match getopt() { Option::Some(s) => tlen(s), Option::None => 0 } }";
    let module = compile(&parse(&tokenize(src).expect("lex")).expect("parse")).expect("compile");
    let arena = Arena::with_capacity(DEFAULT_ARENA_CAPACITY);
    let mut vm = Vm::new(module, &arena).expect("verify");
    vm.register_native("getopt", |_| {
        Ok(Value::Enum(EnumBody::boxed(
            String::from("Option"),
            String::from("Some"),
            vec![Value::StaticStr(String::from("hello"))],
        )))
    });
    vm.register_fn("tlen", |s: String| -> i64 { s.len() as i64 });
    match vm.call(&[]).expect("call") {
        VmState::Finished(v) => assert_eq!(v, Value::Int(5)),
        other => panic!("expected finished, got {:?}", other),
    }
}