use rustyfi_backend::{FontKey, FontMetrics, Length};
use rustyfi_lang::value::Value;
use rustyfi_lang::{elaborate, eval, primitives, typecheck, CompileError};
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(src: &str) -> Result<Value, CompileError> {
let file = rustyfi_syntax::parse_file(src)?;
let env = primitives::base_env();
let store = rustyfi_lang::symbol::SymbolStore::new();
let scope = elaborate::Scope::new(&store, env.names());
let program = elaborate::elaborate_program(&file, &scope)?;
typecheck::typecheck(&program)?;
let mono = Mono;
let mut interp = eval::Interp::new(&mono);
Ok(interp.eval(&env, &rustyfi_lang::ast::debrand(&program.body, &store))?)
}
fn as_length(v: Value) -> Length {
match v {
Value::Length(l) => l,
other => panic!("expected a length, got {other:?}"),
}
}
fn natural_width(v: Value) -> Length {
match v {
Value::Tuple(vs) if vs.len() == 3 => as_length(vs.into_iter().next().unwrap()),
other => panic!("expected a (length * length * length) tuple, got {other:?}"),
}
}
const DUMMY_CTX_CMD: &str = "let-inline ctx \\dummy m = inline-nil\n";
#[test]
fn let_math_tuple_pattern_param() {
let base = |cmd: &str| {
let src = format!(
"let-math \\fst (m, _) = m\n\
{DUMMY_CTX_CMD}\
in
let ctx = get-initial-context 200pt (command \\dummy) in
get-natural-metrics (embed-math ctx {cmd})"
);
natural_width(run(&src).unwrap())
};
let w_fst = base(r"${\fst!(${xx}, ${y})}");
let w_xx = base(r"${xx}");
assert_eq!(
w_fst, w_xx,
"`\\fst!(${{xx}}, ${{y}})` should bind `m` to `${{xx}}`'s math value \
via the (m, _) tuple pattern, discarding ${{y}}"
);
}
#[test]
fn let_math_wildcard_and_literal_patterns() {
let src = format!(
"let-math \\snd (_, m) = m\n\
let-math \\pick0 (0, m) = m\n\
{DUMMY_CTX_CMD}\
in
let ctx = get-initial-context 200pt (command \\dummy) in
let (w-snd, _, _) = get-natural-metrics (embed-math ctx ${{\\snd!(${{xx}}, ${{y}})}}) in
let (w-pick0, _, _) = get-natural-metrics (embed-math ctx ${{\\pick0!(0, ${{y}})}}) in
let (w-y, _, _) = get-natural-metrics (embed-math ctx ${{y}}) in
(w-snd, w-pick0, w-y)"
);
match run(&src).unwrap() {
Value::Tuple(vs) if vs.len() == 3 => {
let mut it = vs.into_iter();
let w_snd = as_length(it.next().unwrap());
let w_pick0 = as_length(it.next().unwrap());
let w_y = as_length(it.next().unwrap());
assert_eq!(
w_snd, w_y,
"wildcard-first tuple pattern should bind `m` to ${{y}}"
);
assert_eq!(
w_pick0, w_y,
"literal-`0`-first tuple pattern should match and bind `m` to ${{y}}"
);
}
other => panic!("expected a (length * length * length) tuple, got {other:?}"),
}
}
#[test]
fn let_math_optional_marker_composes_with_pattern_param() {
let src = format!(
"let-math \\foo ?:o (a, b) = (match o with None -> a | Some(n) -> n)\n\
{DUMMY_CTX_CMD}\
in
let ctx = get-initial-context 200pt (command \\dummy) in
let (w-bare, _, _) = get-natural-metrics (embed-math ctx ${{\\foo!(${{xx}}, ${{y}})}}) in
let (w-supplied, _, _) = get-natural-metrics (embed-math ctx ${{\\foo?:{{www}}!(${{xx}}, ${{y}})}}) in
let (w-xx, _, _) = get-natural-metrics (embed-math ctx ${{xx}}) in
(w-bare, w-supplied, w-xx)"
);
match run(&src).unwrap() {
Value::Tuple(vs) if vs.len() == 3 => {
let mut it = vs.into_iter();
let w_bare = as_length(it.next().unwrap());
let w_supplied = as_length(it.next().unwrap());
let w_xx = as_length(it.next().unwrap());
assert_eq!(
w_bare, w_xx,
"a marker-less bare call must auto-pad `None` for the leading \
`?:`-marked slot and fall back to `a` (${{xx}})"
);
assert!(
w_supplied > w_bare,
"an explicitly `?:`-supplied override (`${{www}}`, 3 chars) \
should be wider than the auto-padded fallback (`${{xx}}`, 2 chars)"
);
}
other => panic!("expected a (length * length * length) tuple, got {other:?}"),
}
}
#[test]
fn let_inline_tuple_pattern_param_both_forms() {
const PICK_CMD: &str =
"let-inline ctx \\pick-ctx (a, b) = read-inline ctx (if a then { AAAA } else { B })
let-inline \\pick-light (a, b) = if a then { AAAA } else { B }
let-inline ctx \\math m = inline-nil
";
let base = |cmd: &str| {
let src = format!(
"{PICK_CMD}in
let base = get-initial-context 200pt (command \\math) in
let (w, _, _) = get-natural-metrics (read-inline base {{ {cmd} }}) in
w"
);
as_length(run(&src).unwrap())
};
let w_ctx_true = base(r"\pick-ctx(true, 1);");
let w_ctx_false = base(r"\pick-ctx(false, 1);");
let w_light_true = base(r"\pick-light(true, 1);");
let w_light_false = base(r"\pick-light(false, 1);");
assert!(
w_ctx_true > w_ctx_false,
"the `ctx`-headed form's `(a, b)` pattern param should bind `a` to \
the supplied bool and select the 4-char branch"
);
assert!(
w_light_true > w_light_false,
"the lightweight (implicit-`%context`) form's `(a, b)` pattern param \
should bind `a` the same way, inside the `read-inline %context ..` \
wrapping"
);
}
#[test]
fn let_math_refutable_pattern_param_fails_at_apply_time() {
let src = format!(
"let-math \\lit (0, m) = m\n\
{DUMMY_CTX_CMD}\
in
let ctx = get-initial-context 200pt (command \\dummy) in
let (w, _, _) = get-natural-metrics (embed-math ctx ${{\\lit!(1, ${{xx}})}}) in
w"
);
match run(&src) {
Err(_) => {}
Ok(v) => panic!("expected a runtime match failure, got {v:?}"),
}
}
#[test]
fn expression_level_let_math_in() {
let src = "let-inline ctx \\math m2 = inline-nil
in
let-math \\g m = ${#m#m} in
let ctx = get-initial-context 200pt (command \\math) in
let (w-g, _, _) = get-natural-metrics (embed-math ctx ${\\g{x}}) in
let (w-xx, _, _) = get-natural-metrics (embed-math ctx ${xx}) in
(w-g, w-xx)";
match run(src).unwrap() {
Value::Tuple(vs) if vs.len() == 2 => {
let mut it = vs.into_iter();
let w_g = as_length(it.next().unwrap());
let w_xx = as_length(it.next().unwrap());
assert_eq!(
w_g, w_xx,
"\\g{{x}} (double-splicing a 1-char math value) should be as \
wide as ${{xx}} (a literal 2-char run)"
);
}
other => panic!("expected a (length * length) tuple, got {other:?}"),
}
}
#[test]
fn expression_level_let_math_in_nested_under_a_plain_let() {
let src = "let-inline ctx \\math m = inline-nil in
let result =
let-math \\g m = ${#m#m} in
let ctx = get-initial-context 200pt (command \\math) in
get-natural-metrics (embed-math ctx ${\\g{x}})
in
let (w-g, _, _) = result in
let (w-xx, _, _) =
let ctx = get-initial-context 200pt (command \\math) in
get-natural-metrics (embed-math ctx ${xx})
in
(w-g, w-xx)";
match run(src).unwrap() {
Value::Tuple(vs) if vs.len() == 2 => {
let mut it = vs.into_iter();
let w_g = as_length(it.next().unwrap());
let w_xx = as_length(it.next().unwrap());
assert_eq!(
w_g, w_xx,
"let-math .. in nested under a plain let .. in should still work"
);
}
other => panic!("expected a (length * length) tuple, got {other:?}"),
}
}
#[test]
fn expression_level_let_math_in_non_math_value_fails_typecheck() {
let src = "let-math \\f = 3 in 0";
match run(src) {
Err(_) => {}
Ok(v) => {
panic!("expected a typecheck error (a math command's value must be `math`), got {v:?}")
}
}
}