use serde_json::json;
use vize_l0::dump::value::DumpValue;
use vize_l0::dump::{Dump, Error as DumpError, Mode as DumpMode};
use vize_l0::{Allocator, Span, String, cstr};
use vize_l3::op::{OpId, Phase, Program, RegionId};
use vize_l3::operand::{Operand, OperandRole, OperandValue, ValueKind};
use vize_l3::values_dump::{Operand as DumpOperand, OperandRow, Page as ValuesPage};
fn parse_row(json: &str, line: usize) -> Result<DumpOperand, DumpError> {
DumpOperand::parse_value(&cstr!("operand={json}"), line)
}
#[test]
fn every_role_and_value_kind_round_trips_without_losing_escaped_text() {
let arena = Allocator::default();
let mut program = Program::new(&arena, Phase::Built);
for role in OperandRole::ALL {
for kind in ValueKind::ALL
.into_iter()
.filter(|kind| *kind != ValueKind::NativeHandler)
{
let attribute = matches!(role, OperandRole::Attribute | OperandRole::ModelAttribute);
program.operands.push(Operand {
op: OpId::new(0),
role,
target: (!role.accepts_target(false)).then_some(OpId::new(1)),
region: (role == OperandRole::Condition).then_some(RegionId::new(1)),
name: attribute.then_some("data-\"key\\\n\u{03bb}"),
value: OperandValue {
native_handler: None,
kind,
text: if kind == ValueKind::Absent {
""
} else {
"\"\\\n\r\t\u{0}\u{03bb}\u{1f600}"
},
qualifier: match kind {
ValueKind::Opaque => "parse-failure",
ValueKind::Foreign => "moonbit",
_ => "",
},
span: Span::new(4, 19),
},
});
}
}
let folio = ValuesPage::of(&program);
assert_eq!(folio.operands.len(), 108);
let printed = folio.print_to_string(DumpMode::Full);
let reparsed = ValuesPage::parse(printed.as_str()).unwrap();
assert_eq!(reparsed, folio);
assert_eq!(reparsed.print_to_string(DumpMode::Full), printed);
for operand in folio.operands {
let mut text = String::default();
operand.print_value(&mut text).unwrap();
let row: OperandRow = operand.0;
assert_eq!(row.0, 0);
assert_eq!(
text,
cstr!("operand={}", serde_json::to_string(&row).unwrap())
);
assert_eq!(text.lines().count(), 1);
}
}
#[test]
fn malformed_rows_fail_on_the_authored_line() {
let valid = json!([0, "text", null, null, null, "literal", "hello", "", 0, 5]);
for (index, value) in [
(0, json!(-1)),
(1, json!("unknown")),
(3, json!(1)),
(4, json!("unexpected-name")),
(5, json!("unknown")),
(5, json!("absent")),
(5, json!("opaque")),
(5, json!("foreign")),
(7, json!("unexpected-qualifier")),
(8, json!(6)),
] {
let mut row = valid.clone();
row[index] = value;
let error = parse_row(&serde_json::to_string(&row).unwrap(), 17).unwrap_err();
assert_eq!(error.line, 17);
}
for text in [
"[]",
"[0]",
"not json",
"[0,\"text\",null,null,null,\"literal\",\"x\",\"\",0,1,2]",
] {
assert_eq!(parse_row(text, 23).unwrap_err().line, 23);
}
for row in [
json!([0, "attribute", null, null, null, "literal", "", "", 0, 0]),
json!([0, "condition", null, null, null, "literal", "", "", 0, 0]),
json!([0, "condition", 1, 2, null, "literal", "", "", 0, 0]),
] {
assert!(parse_row(&serde_json::to_string(&row).unwrap(), 1).is_err());
}
}
#[test]
fn absent_and_empty_literal_values_remain_distinct() {
let absent = parse_row("[0,\"value\",1,null,null,\"absent\",\"\",\"\",0,0]", 1).unwrap();
let literal = parse_row("[0,\"value\",1,null,null,\"literal\",\"\",\"\",0,0]", 1).unwrap();
assert_ne!(absent, literal);
}
#[test]
fn value_rows_reject_missing_binding_targets_and_unexpected_structural_targets() {
for role in [
"binding-kind",
"value",
"modifier",
"model-read",
"model-write",
"params",
] {
let row = json!([0, role, null, null, null, "literal", "x", "", 0, 1]);
assert!(parse_row(&serde_json::to_string(&row).unwrap(), 1).is_err());
}
let attribute = json!([
0,
"model-attribute",
null,
null,
"trim",
"absent",
"",
"",
0,
0
]);
assert!(parse_row(&serde_json::to_string(&attribute).unwrap(), 1).is_err());
let text = json!([0, "text", 1, null, null, "literal", "x", "", 0, 1]);
assert!(parse_row(&serde_json::to_string(&text).unwrap(), 1).is_err());
}