use rustyfi_backend::{FontKey, FontMetrics, Length};
use rustyfi_lang::CompileError;
use rustyfi_loader::{LoadedCst, LoadedFile};
use rustyfi_syntax::RustyfiVersion;
use rustyfi_syntax::{parse_file, parse_file_v1};
struct Mono;
impl FontMetrics for Mono {
fn advance(&self, _f: FontKey, c: char, size: Length) -> Option<Length> {
if c.is_ascii() {
Some(size * 0.5)
} else {
None
}
}
fn ascender(&self, _f: FontKey, size: Length) -> Length {
size * 0.75
}
fn descender(&self, _f: FontKey, size: Length) -> Length {
size * 0.25
}
}
fn run(lib_src: &str, doc_src: &str) -> Result<(), CompileError> {
let files = vec![
LoadedFile {
path: std::path::PathBuf::from("lib.satyh"),
cst: LoadedCst::V0_1(
parse_file_v1(lib_src).unwrap_or_else(|e| panic!("lib parse failed: {e}")),
),
origin: Default::default(),
version: RustyfiVersion::V0_1,
},
LoadedFile {
path: std::path::PathBuf::from("doc.saty"),
cst: LoadedCst::V0_1(
parse_file_v1(doc_src).unwrap_or_else(|e| panic!("doc parse failed: {e}")),
),
origin: Default::default(),
version: RustyfiVersion::V0_1,
},
];
let mono = Mono;
rustyfi_lang::compile_document_v1(&files, &mono).map(|_| ())
}
fn assert_accepts(lib_src: &str, doc_src: &str) {
match run(lib_src, doc_src) {
Ok(()) | Err(CompileError::NotADocument(_)) => {}
Err(other) => panic!("expected acceptance, got: {other}"),
}
}
fn assert_type_error(lib_src: &str, doc_src: &str) -> String {
match run(lib_src, doc_src) {
Err(CompileError::Type(e)) => e.to_string(),
Err(other) => panic!("expected a Type error, got: {other}"),
Ok(()) => panic!("expected type-checking to reject, but compilation succeeded"),
}
}
const T1_LIB: &str = "\
module M = struct
val inline ctx \\mathstub m = read-inline ctx {}
val inline ctx \\emphwith ?(color = c) inner =
let cv = match c with None -> 0 | Some v -> v end in
read-inline ctx inner
end
";
#[test]
fn t1_inline_param_bundle_unbundled_call_defaults_and_evaluates() {
let doc = "\
let ctx = get-initial-context 400pt (command \\M.mathstub) in
read-inline ctx {\\M.emphwith{hello}}";
assert_accepts(T1_LIB, doc);
}
const T2_LIB: &str = "\
module M = struct
val inline ctx \\mathstub m = read-inline ctx {}
val block ctx +sec ?(label = l, outline-title = o) title inner =
let lv = match l with None -> 0 | Some v -> v end in
let ov = match o with None -> 0 | Some v -> v end in
read-block ctx inner
end
";
#[test]
fn t2_block_command_two_labels_unbundled_call_defaults_and_evaluates() {
let doc = "\
let ctx = get-initial-context 400pt (command \\M.mathstub) in
read-block ctx '< +M.sec{Title}< > >";
assert_accepts(T2_LIB, doc);
}
const T3_LIB: &str = "\
module M :> sig
val +sec : block [?(outline-title:string, label:string) inline-text, block-text]
end = struct
val block ctx +sec ?(label = l, outline-title = o) title inner =
read-block ctx inner
end
";
#[test]
fn t3_sealed_command_opt_labels_round_trip() {
assert_accepts(T3_LIB, "1");
}
#[test]
fn t4_sealed_command_opt_label_set_mismatch_rejected() {
let lib_sig_subset = "\
module M :> sig
val +sec : block [?(label:string) inline-text, block-text]
end = struct
val block ctx +sec ?(label = l, outline-title = o) title inner =
read-block ctx inner
end
";
let msg = assert_type_error(lib_sig_subset, "1");
assert!(!msg.is_empty(), "expected a non-empty type-error message");
let lib_sig_superset = "\
module M :> sig
val +sec : block [?(label:string, outline-title:string) inline-text, block-text]
end = struct
val block ctx +sec ?(label = l) title inner =
read-block ctx inner
end
";
let msg2 = assert_type_error(lib_sig_superset, "1");
assert!(!msg2.is_empty(), "expected a non-empty type-error message");
}
const T5_LIB: &str = "\
module M :> sig
val \\href : inline [?(border:length * color) string, inline-text]
end = struct
val inline ctx \\href ?(border = b) uri inner =
read-inline ctx inner
end
";
#[test]
fn t5_sealed_href_shape_compound_label_type() {
assert_accepts(T5_LIB, "1");
}
#[test]
fn t6_v006_command_param_bundle_version_gate() {
let file = parse_file("let-inline ctx \\c ?(a = x) t = t in 0")
.unwrap_or_else(|e| panic!("0.0.6 parse of the command bundle failed: {e}"));
let env = rustyfi_lang::primitives::base_env();
let store = rustyfi_lang::symbol::SymbolStore::new();
let scope = rustyfi_lang::elaborate::Scope::new(&store, env.names());
let err = rustyfi_lang::elaborate::elaborate_program(&file, &scope)
.expect_err("a 0.0.6 command binding with a `?(l=x)` bundle must be rejected");
assert!(
err.to_string().contains("SATySFi 0.1 syntax"),
"expected a version-gate message, got: {err}"
);
}
#[test]
fn t9_empty_param_bundle_on_command_is_lower_error() {
let file = parse_file_v1(
"module M = struct\n\
val inline ctx \\c ?() t = t\n\
end",
)
.unwrap_or_else(|e| panic!("lib parse failed: {e}"));
let err = rustyfi_lang::v1::lower::lower_file_v1(&file)
.expect_err("an empty `?()` command-parameter bundle must be a lower error");
assert!(
err.to_string().contains("optional-parameter bundle")
|| err.to_string().contains("optional"),
"got: {err}"
);
}
#[test]
fn t9_empty_type_row_bundle_is_lower_error() {
let lib = "\
module M :> sig
val \\c : inline [?() int]
end = struct
val inline ctx \\c n = read-inline ctx {}
end
";
let msg = assert_type_error(lib, "1");
assert!(
msg.contains("optional-label bundle") || msg.contains("optional"),
"got: {msg}"
);
}
#[test]
fn t9_math_command_type_head_with_labeled_row_parses() {
let src = "module M :> sig\n\
val \\derive : math [?(name:math-text) list math-text, math-text]\n\
end = struct val x = 1 end";
assert!(
parse_file_v1(src).is_ok(),
"a `math [...]` command-type head (with a `?(...)` labeled row) must now parse"
);
}
#[test]
fn t_m1_seal_bare_math_rows() {
let lib = "\
module M :> sig
val \\frac : math [math-text, math-text]
val \\alpha : math []
end = struct
val math ctx \\frac a b =
let _ = read-math ctx a in
let _ = read-math ctx b in
math-char ctx MathOrd `x`
val math ctx \\alpha = math-char ctx MathOrd `alpha`
end
";
assert_accepts(lib, "1");
}
#[test]
fn t_m1_mismatch_arity() {
let lib = "\
module M :> sig
val \\alpha : math [math-text]
end = struct
val math ctx \\alpha a b =
let _ = a in
let _ = b in
read-math ctx a
end
";
let msg = assert_type_error(lib, "1");
assert!(!msg.is_empty(), "expected a non-empty type-error message");
}
#[test]
fn t_m1_mismatch_inline_sig_for_math_impl() {
let lib = "\
module M :> sig
val \\alpha : inline [math-text]
end = struct
val math ctx \\alpha m = read-math ctx m
end
";
let msg = assert_type_error(lib, "1");
assert!(!msg.is_empty(), "expected a non-empty type-error message");
}
#[test]
fn t_m1_mismatch_math_sig_for_inline_impl() {
let lib = "\
module M :> sig
val \\greet : math [inline-text]
end = struct
val inline ctx \\greet it = read-inline ctx it
end
";
let msg = assert_type_error(lib, "1");
assert!(!msg.is_empty(), "expected a non-empty type-error message");
}
#[test]
fn t_m1_scripts_trio_not_surfaced_as_slots() {
let lib = "\
module M :> sig
val \\lim : math []
end = struct
val math ctx \\lim with sub sup =
let _ = sub in
let _ = sup in
math-char ctx MathOp `lim`
end
";
assert_accepts(lib, "1");
}
#[test]
fn inc3b_alpha_math_param_bundle_seals() {
let lib = "\
module M :> sig
val \\sq : math [?(deco:int) math-text]
end = struct
val math ctx \\sq ?(deco = d) base =
let _ = match d with None -> 0 | Some v -> v end in
read-math ctx base
end
";
assert_accepts(lib, "1");
}
#[test]
fn inc3b_alpha_math_param_bundle_unbundled_call_evaluates() {
let lib = "\
module M = struct
val inline ctx \\mathstub m = read-inline ctx {}
val math ctx \\sq ?(deco = d) base =
let _ = match d with None -> 0 | Some v -> v end in
read-math ctx base
end
";
let doc = "\
let ctx = get-initial-context 400pt (command \\M.mathstub) in
read-math ctx ${\\M.sq{a}}";
assert_accepts(lib, doc);
}
#[test]
fn inc3b_alpha_math_param_bundle_label_set_mismatch_rejected() {
let impl_has_extra = "\
module M :> sig
val \\sq : math [math-text]
end = struct
val math ctx \\sq ?(deco = d) base =
let _ = match d with None -> 0 | Some v -> v end in
read-math ctx base
end
";
assert!(!assert_type_error(impl_has_extra, "1").is_empty());
let sig_has_extra = "\
module M :> sig
val \\sq : math [?(deco:int) math-text]
end = struct
val math ctx \\sq base = read-math ctx base
end
";
assert!(!assert_type_error(sig_has_extra, "1").is_empty());
}
#[test]
fn inc3b_alpha_math_bundle_and_scripts_trio_coexist() {
let lib = "\
module M :> sig
val \\lim : math [?(k:int) math-text]
end = struct
val math ctx \\lim ?(k = kopt) base with sub sup =
let _ = match kopt with None -> 0 | Some v -> v end in
let _ = sub in
let _ = sup in
read-math ctx base
end
";
assert_accepts(lib, "1");
}
const INC3B_BETA_LIB: &str = "\
module M = struct
val inline ctx \\mathstub m = read-inline ctx {}
val inline ctx \\emphwith ?(color = c) inner =
let _ = match c with None -> 0 | Some v -> v end in
read-inline ctx inner
val block ctx +sec ?(label = l) title inner =
let _ = match l with None -> 0 | Some v -> v end in
read-block ctx inner
end
";
#[test]
fn inc3b_beta_inline_app_bundle_supplied_flows_to_eval() {
let doc = "\
let ctx = get-initial-context 400pt (command \\M.mathstub) in
read-inline ctx {\\M.emphwith ?(color = 3){hi}}";
assert_accepts(INC3B_BETA_LIB, doc);
}
#[test]
fn inc3b_beta_inline_app_bundle_omitted_defaults_none() {
let doc = "\
let ctx = get-initial-context 400pt (command \\M.mathstub) in
read-inline ctx {\\M.emphwith{hi}}";
assert_accepts(INC3B_BETA_LIB, doc);
}
#[test]
fn inc3b_beta_inline_app_unknown_label_rejected() {
let doc = "\
let ctx = get-initial-context 400pt (command \\M.mathstub) in
read-inline ctx {\\M.emphwith ?(bogus = 3){hi}}";
let msg = assert_type_error(INC3B_BETA_LIB, doc);
assert!(
msg.contains("bogus") || msg.contains("optional label"),
"expected an unexpected-optional-label message, got: {msg}"
);
}
#[test]
fn inc3b_beta_inline_app_bundle_wrong_value_type_rejected() {
let doc = "\
let ctx = get-initial-context 400pt (command \\M.mathstub) in
read-inline ctx {\\M.emphwith ?(color = `x`){hi}}";
let msg = assert_type_error(INC3B_BETA_LIB, doc);
assert!(!msg.is_empty(), "expected a non-empty type-error message");
}
#[test]
fn inc3b_beta_block_app_bundle_supplied_flows_to_eval() {
let doc = "\
let ctx = get-initial-context 400pt (command \\M.mathstub) in
read-block ctx '< +M.sec ?(label = 7){Title}< > >";
assert_accepts(INC3B_BETA_LIB, doc);
}
const INC3B_BETA_OBS_LIB: &str = "\
module M = struct
val inline ctx \\mathstub m = read-inline ctx {}
val inline ctx \\needcolor ?(color = c) inner =
match c with
| Some v -> let _ = v in read-inline ctx inner
| None -> abort-with-message `no color supplied`
end
end
";
#[test]
fn inc3b_beta_app_bundle_value_observably_reaches_eval() {
let doc_supplied = "\
let ctx = get-initial-context 400pt (command \\M.mathstub) in
read-inline ctx {\\M.needcolor ?(color = 3){x}}";
assert_accepts(INC3B_BETA_OBS_LIB, doc_supplied);
let doc_omitted = "\
let ctx = get-initial-context 400pt (command \\M.mathstub) in
read-inline ctx {\\M.needcolor{x}}";
match run(INC3B_BETA_OBS_LIB, doc_omitted) {
Err(CompileError::Eval(_)) => {}
other => {
panic!("expected a run-time abort (Eval error) on the None branch, got: {other:?}")
}
}
}