use lex_ast::canonicalize_program;
use lex_bytecode::{compile_program, vm::Vm, Value};
use lex_runtime::{DefaultHandler, Policy};
use lex_syntax::parse_source;
fn run(src: &str, func: &str, args: Vec<Value>) -> Result<Value, String> {
let prog = parse_source(src).expect("parse");
let stages = canonicalize_program(&prog);
if let Err(errs) = lex_types::check_program(&stages) {
return Err(format!("type errors: {errs:#?}"));
}
let bc = compile_program(&stages);
let handler = DefaultHandler::new(Policy::permissive());
let mut vm = Vm::with_handler(&bc, Box::new(handler));
vm.call(func, args).map_err(|e| format!("{e}"))
}
const SRC: &str = r#"
import "std.str" as str
fn slice(s :: Str, lo :: Int, hi :: Int) -> Str { str.slice(s, lo, hi) }
"#;
#[test]
fn in_range_slice_returns_substring() {
let v = run(SRC, "slice", vec![
Value::Str("hello".into()), Value::Int(1), Value::Int(4),
]).unwrap();
assert_eq!(v, Value::Str("ell".into()));
}
#[test]
fn hi_past_end_clamps_to_len() {
let v = run(SRC, "slice", vec![
Value::Str("MIT License (32 char-ish here)".into()),
Value::Int(0), Value::Int(64),
]).unwrap();
assert_eq!(v, Value::Str("MIT License (32 char-ish here)".into()));
}
#[test]
fn lo_past_end_yields_empty_string() {
let v = run(SRC, "slice", vec![
Value::Str("hello".into()),
Value::Int(100), Value::Int(200),
]).unwrap();
assert_eq!(v, Value::Str("".into()));
}
#[test]
fn negative_lo_clamps_to_zero() {
let v = run(SRC, "slice", vec![
Value::Str("hello".into()),
Value::Int(-5), Value::Int(3),
]).unwrap();
assert_eq!(v, Value::Str("hel".into()));
}
#[test]
fn negative_hi_clamps_to_zero_yielding_empty() {
let v = run(SRC, "slice", vec![
Value::Str("hello".into()),
Value::Int(0), Value::Int(-1),
]).unwrap();
assert_eq!(v, Value::Str("".into()));
}
#[test]
fn lo_greater_than_hi_after_clamping_errors() {
let err = run(SRC, "slice", vec![
Value::Str("hello".into()),
Value::Int(4), Value::Int(2),
]).unwrap_err();
assert!(err.to_lowercase().contains("reversed"),
"expected reversed-range error, got: {err}");
}
#[test]
fn mid_codepoint_lo_still_errors() {
let err = run(SRC, "slice", vec![
Value::Str("héllo".into()),
Value::Int(2), Value::Int(5),
]).unwrap_err();
assert!(err.contains("char boundaries"),
"expected char-boundary error, got: {err}");
}
#[test]
fn empty_string_with_any_range_is_empty() {
let v = run(SRC, "slice", vec![
Value::Str("".into()), Value::Int(0), Value::Int(10),
]).unwrap();
assert_eq!(v, Value::Str("".into()));
}