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 TOML_SRC: &str = r#"
import "std.toml" as toml
fn parse_proj(s :: Str) -> Result[Map[Str, Json], Str] {
toml.parse_strict(s, ["project.name", "project.license"])
}
"#;
#[test]
fn nested_dotted_path_passes_when_all_present() {
let toml_src = r#"
[project]
name = "foo"
license = "MIT"
"#;
let v = run(TOML_SRC, "parse_proj", vec![Value::Str(toml_src.into())]).unwrap();
assert!(matches!(&v, Value::Variant { name, .. } if name == "Ok"),
"expected Ok, got: {v:?}");
}
#[test]
fn nested_dotted_path_reports_missing_inner_field() {
let toml_src = r#"
[project]
name = "foo"
"#;
let v = run(TOML_SRC, "parse_proj", vec![Value::Str(toml_src.into())]).unwrap();
let err = match v {
Value::Variant { name, args } if name == "Err" => args.into_iter().next().unwrap(),
other => panic!("expected Err, got: {other:?}"),
};
let msg = format!("{err:?}");
assert!(msg.contains("project.license"),
"error should name the missing dotted path; got: {msg}");
assert!(!msg.contains("project.name"),
"present dotted path should not appear in error; got: {msg}");
}
#[test]
fn missing_intermediate_object_reports_full_path() {
let v = run(TOML_SRC, "parse_proj", vec![Value::Str("name = \"top\"\n".into())]).unwrap();
let err = match v {
Value::Variant { name, args } if name == "Err" => args.into_iter().next().unwrap(),
other => panic!("expected Err, got: {other:?}"),
};
let msg = format!("{err:?}");
assert!(msg.contains("project.name") && msg.contains("project.license"),
"both missing dotted paths should be listed; got: {msg}");
}
#[test]
fn intermediate_non_object_is_treated_as_missing() {
let toml_src = r#"
project = "MIT"
"#;
let v = run(TOML_SRC, "parse_proj", vec![Value::Str(toml_src.into())]).unwrap();
let err = match v {
Value::Variant { name, args } if name == "Err" => args.into_iter().next().unwrap(),
other => panic!("expected Err on string-where-object, got: {other:?}"),
};
let msg = format!("{err:?}");
assert!(msg.contains("project.name") || msg.contains("project.license"),
"should report a dotted-path miss; got: {msg}");
}
#[test]
fn deep_three_level_path_works() {
let src = r#"
import "std.json" as json
fn parse_deep(s :: Str) -> Result[Map[Str, Json], Str] {
json.parse_strict(s, ["a.b.c"])
}
"#;
let json = r#"{ "a": { "b": { "c": 1 } } }"#;
let v = run(src, "parse_deep", vec![Value::Str(json.into())]).unwrap();
assert!(matches!(&v, Value::Variant { name, .. } if name == "Ok"));
let json = r#"{ "a": { "b": {} } }"#;
let v = run(src, "parse_deep", vec![Value::Str(json.into())]).unwrap();
let err = match v {
Value::Variant { name, args } if name == "Err" => args.into_iter().next().unwrap(),
other => panic!("expected Err, got: {other:?}"),
};
assert!(format!("{err:?}").contains("a.b.c"));
}
#[test]
fn top_level_field_still_works() {
let src = r#"
import "std.json" as json
fn check_name(s :: Str) -> Result[Map[Str, Json], Str] {
json.parse_strict(s, ["name"])
}
"#;
let v = run(src, "check_name", vec![Value::Str(r#"{"name": "x"}"#.into())]).unwrap();
assert!(matches!(&v, Value::Variant { name, .. } if name == "Ok"));
let v = run(src, "check_name", vec![Value::Str(r#"{"version": "1"}"#.into())]).unwrap();
let err = match v {
Value::Variant { name, args } if name == "Err" => args.into_iter().next().unwrap(),
_ => panic!("expected Err"),
};
assert!(format!("{err:?}").contains("name"));
}
#[test]
fn literal_dot_in_field_name_via_escape() {
let src = r#"
import "std.json" as json
fn check(s :: Str) -> Result[Map[Str, Json], Str] {
json.parse_strict(s, ["package\\.json"])
}
"#;
let v = run(src, "check", vec![
Value::Str(r#"{ "package.json": "{}" }"#.into()),
]).unwrap();
let is_ok = matches!(&v, Value::Variant { name, .. } if name == "Ok");
assert!(is_ok, "literal-dot key with `\\.` escape should pass; got: {v:?}");
}
#[test]
fn yaml_nested_required_works_too() {
let src = r#"
import "std.yaml" as yaml
fn check(s :: Str) -> Result[Map[Str, Json], Str] {
yaml.parse_strict(s, ["jobs.test"])
}
"#;
let yaml = "jobs:\n test:\n runs-on: ubuntu-latest\n";
let v = run(src, "check", vec![Value::Str(yaml.into())]).unwrap();
assert!(matches!(&v, Value::Variant { name, .. } if name == "Ok"));
}