use lex_ast::canonicalize_program;
use lex_bytecode::{compile_program, vm::Vm, Value};
use lex_runtime::{DefaultHandler, Policy};
use lex_syntax::parse_source;
use std::collections::BTreeSet;
use std::sync::Arc;
fn policy_with(effects: &[&str]) -> Policy {
let mut p = Policy::pure();
p.allow_effects = effects
.iter()
.map(|s| s.to_string())
.collect::<BTreeSet<_>>();
p
}
fn run(src: &str, fn_name: &str, args: Vec<Value>, policy: Policy) -> Value {
let prog = parse_source(src).expect("parse");
let stages = canonicalize_program(&prog);
if let Err(errs) = lex_types::check_program(&stages) {
panic!("type errors:\n{errs:#?}");
}
let bc = Arc::new(compile_program(&stages));
let handler = DefaultHandler::new(policy).with_program(Arc::clone(&bc));
let mut vm = Vm::with_handler(&bc, Box::new(handler));
vm.call(fn_name, args).unwrap_or_else(|e| panic!("call {fn_name}: {e}"))
}
fn bytes(v: Value) -> Vec<u8> {
match v { Value::Bytes(b) => b, other => panic!("expected Bytes, got {other:?}") }
}
fn s(v: Value) -> String {
match v { Value::Str(x) => x.to_string(), other => panic!("expected Str, got {other:?}") }
}
fn b(v: Value) -> bool {
match v { Value::Bool(x) => x, other => panic!("expected Bool, got {other:?}") }
}
fn ok_b(v: Value) -> Vec<u8> {
match v {
Value::Variant { name, args } if name == "Ok" && args.len() == 1 => bytes(args.into_iter().next().unwrap()),
other => panic!("expected Ok(Bytes), got {other:?}"),
}
}
const SRC: &str = r#"
import "std.crypto" as crypto
import "std.bytes" as bytes
fn blake2b_of(s :: Str) -> Bytes { crypto.blake2b(bytes.from_str(s)) }
fn sha256_str_of(s :: Str) -> Str { crypto.sha256_str(s) }
fn sha512_str_of(s :: Str) -> Str { crypto.sha512_str(s) }
fn b64url_round(s :: Str) -> Result[Bytes, Str] {
crypto.base64url_decode(crypto.base64url_encode(bytes.from_str(s)))
}
fn b64url_encode(s :: Str) -> Str { crypto.base64url_encode(bytes.from_str(s)) }
fn eq_self(s :: Str) -> Bool {
let bs := bytes.from_str(s)
crypto.eq(bs, bs)
}
fn eq_diff() -> Bool {
crypto.eq(bytes.from_str("alpha"), bytes.from_str("beta"))
}
fn eq_str_same(a :: Str, c :: Str) -> Bool { crypto.eq_str(a, c) }
fn eq_str_len_mismatch() -> Bool { crypto.eq_str("foo", "fooo") }
fn rand_hex(n :: Int) -> [random] Str { crypto.random_str_hex(n) }
"#;
#[test]
fn blake2b_returns_64_byte_digest() {
let v = run(SRC, "blake2b_of", vec![Value::Str("hello".into())], Policy::pure());
let digest = bytes(v);
assert_eq!(digest.len(), 64, "BLAKE2b-512 must return 64 bytes; got {} bytes", digest.len());
}
#[test]
fn blake2b_is_deterministic() {
let a = bytes(run(SRC, "blake2b_of", vec![Value::Str("hello".into())], Policy::pure()));
let b = bytes(run(SRC, "blake2b_of", vec![Value::Str("hello".into())], Policy::pure()));
assert_eq!(a, b, "BLAKE2b must be deterministic across calls");
}
#[test]
fn blake2b_distinguishes_inputs() {
let a = bytes(run(SRC, "blake2b_of", vec![Value::Str("hello".into())], Policy::pure()));
let b = bytes(run(SRC, "blake2b_of", vec![Value::Str("world".into())], Policy::pure()));
assert_ne!(a, b, "BLAKE2b of different inputs must produce different digests");
}
#[test]
fn sha256_str_known_vector() {
let h = s(run(SRC, "sha256_str_of", vec![Value::Str("".into())], Policy::pure()));
assert_eq!(
h,
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"SHA-256 of empty string must match the standard test vector"
);
}
#[test]
fn sha512_str_length_is_128_hex_chars() {
let h = s(run(SRC, "sha512_str_of", vec![Value::Str("hello".into())], Policy::pure()));
assert_eq!(h.len(), 128, "SHA-512 hex must be 128 chars; got {}", h.len());
assert!(h.chars().all(|c| c.is_ascii_hexdigit()), "all chars must be hex: {h}");
}
#[test]
fn base64url_uses_urlsafe_alphabet_and_no_padding() {
let encoded = s(run(SRC, "b64url_encode", vec![Value::Str("hi".into())], Policy::pure()));
assert!(
!encoded.contains('+') && !encoded.contains('/') && !encoded.contains('='),
"URL-safe base64 must not contain +, /, or = padding: {encoded:?}"
);
}
#[test]
fn base64url_round_trips() {
let v = run(SRC, "b64url_round", vec![Value::Str("any plaintext payload here".into())], Policy::pure());
let decoded = ok_b(v);
assert_eq!(decoded, b"any plaintext payload here".to_vec());
}
#[test]
fn eq_returns_true_for_identical_bytes() {
assert!(b(run(SRC, "eq_self", vec![Value::Str("anything".into())], Policy::pure())));
}
#[test]
fn eq_returns_false_for_distinct_bytes() {
assert!(!b(run(SRC, "eq_diff", vec![], Policy::pure())));
}
#[test]
fn eq_str_returns_true_when_equal() {
let v = run(
SRC,
"eq_str_same",
vec![Value::Str("token".into()), Value::Str("token".into())],
Policy::pure(),
);
assert!(b(v));
}
#[test]
fn eq_str_returns_false_when_different() {
let v = run(
SRC,
"eq_str_same",
vec![Value::Str("token".into()), Value::Str("toxen".into())],
Policy::pure(),
);
assert!(!b(v));
}
#[test]
fn eq_str_returns_false_for_length_mismatch() {
assert!(!b(run(SRC, "eq_str_len_mismatch", vec![], Policy::pure())));
}
#[test]
fn random_str_hex_returns_2n_hex_chars() {
let v = run(SRC, "rand_hex", vec![Value::Int(16)], policy_with(&["random"]));
let hex = s(v);
assert_eq!(hex.len(), 32, "16 random bytes must render as 32 hex chars; got {} chars", hex.len());
assert!(hex.chars().all(|c| c.is_ascii_hexdigit()), "all chars must be hex: {hex}");
}
#[test]
fn random_str_hex_zero_returns_empty_string() {
let v = run(SRC, "rand_hex", vec![Value::Int(0)], policy_with(&["random"]));
assert_eq!(s(v), "");
}
#[test]
fn random_str_hex_produces_distinct_outputs_across_calls() {
let a = s(run(SRC, "rand_hex", vec![Value::Int(32)], policy_with(&["random"])));
let b = s(run(SRC, "rand_hex", vec![Value::Int(32)], policy_with(&["random"])));
assert_ne!(a, b, "two 32-byte random hex tokens must not collide");
}