use rustyfi_backend::{FontKey, FontMetrics, Length};
use rustyfi_lang::CompileError;
use rustyfi_loader::{LoadedCst, LoadedFile};
use rustyfi_syntax::parse_file_v1;
use rustyfi_syntax::RustyfiVersion;
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 compile+eval to succeed, 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"),
}
}
#[test]
fn i_fn1_include_application_end_to_end() {
let lib = "\
module Lib = struct
module Int = struct
type t = int
val compare x y = x - y
end
module Make = fun (Key : sig type t :: o val compare : t -> t -> int end) -> struct
type t = Wrap of Key.t
val wrap x = Wrap x
val cmp a b =
match a with
| Wrap x ->
(match b with
| Wrap y -> Key.compare x y
end)
end
end
module Test = struct
include Make Int
end
end
";
assert_accepts(lib, "Lib.Test.cmp (Lib.Test.wrap 3) (Lib.Test.wrap 5)");
}
#[test]
fn i_fn2_module_bind_application_and_qualified_use() {
let lib = "\
module Lib = struct
signature Settings = sig
val label : int
end
module DefaultSettings = struct
val label = 42
end
module ConsoleSettings = struct
val label = 7
end
module Make = fun (X : Settings) -> struct
val get-label ctx = X.label + ctx
end
module Default = Make DefaultSettings
module Console = Make ConsoleSettings
end
";
assert_accepts(lib, "Lib.Default.get-label 0");
assert_accepts(lib, "Lib.Console.get-label 0");
}
#[test]
fn i_fn3_param_sig_mismatch_rejected_end_to_end() {
let lib = "\
module Lib = struct
module BadInt = struct
type t = int
end
module Make = fun (Key : sig type t :: o val compare : t -> t -> int end) -> struct
type t = Wrap of Key.t
val wrap x = Wrap x
end
module Test = struct
include Make BadInt
end
end
";
let msg = assert_type_error(lib, "1");
assert!(msg.contains("does not match functor"), "{msg}");
assert!(msg.contains("compare"), "{msg}");
}
#[test]
fn functor_definition_alone_is_erased() {
let lib = "\
module Lib = struct
module Make = fun (Key : sig val v : int end) -> struct
val get () = Key.v
end
val untouched = 1
end
";
assert_accepts(lib, "Lib.untouched + 1");
}
#[test]
fn f_set1_functor_body_applies_another_functor_to_its_own_parameter() {
let lib = "\
module Lib = struct
module IntOrd = struct
type t = int
val compare x y = x - y
end
module Map = struct
module Make = fun (Key : sig type t :: o val compare : t -> t -> int end) -> struct
type t = Wrap of Key.t
val wrap x = Wrap x
val cmp a b =
match a with
| Wrap x ->
(match b with
| Wrap y -> Key.compare x y
end)
end
end
end
module Set = struct
module Make = fun (Elem : sig type t :: o val compare : t -> t -> int end) -> struct
module Impl = Map.Make Elem
type t = Impl.t
val wrap x = Impl.wrap x
val cmp a b = Impl.cmp a b
end
end
module S = Set.Make IntOrd
end
";
assert_accepts(lib, "Lib.S.cmp (Lib.S.wrap 3) (Lib.S.wrap 5)");
}
#[test]
fn f_set1_neg_inner_application_param_mismatch_rejected() {
let lib = "\
module Lib = struct
module BadElem = struct
type t = int
end
module Map = struct
module Make = fun (Key : sig type t :: o val compare : t -> t -> int end) -> struct
type t = Wrap of Key.t
val wrap x = Wrap x
end
end
module Set = struct
module Make = fun (Elem : sig type t :: o val compare : t -> t -> int end) -> struct
module Impl = Map.Make Elem
type t = Impl.t
val wrap x = Impl.wrap x
end
end
module S = Set.Make BadElem
end
";
let msg = assert_type_error(lib, "1");
assert!(msg.contains("does not match functor"), "{msg}");
assert!(msg.contains("compare"), "{msg}");
}
#[test]
fn f_abs2_plain_module_relative_sibling_reference() {
let lib = "\
module Lib = struct
module Inner = struct
val x = 42
end
val y = Inner.x + 1
end
";
assert_accepts(lib, "Lib.y");
}
#[test]
fn f_abs3_relative_sibling_command_invocation() {
let lib = "\
module Lib = struct
val inline ctx \\mathstub m = read-inline ctx {}
module Inner = struct
val block ctx +greet = read-block ctx '< >
end
val block ctx +hello = read-block ctx '< +Inner.greet; >
end
";
let doc = "\
let ctx = get-initial-context 400pt (command \\Lib.mathstub) in
read-block ctx '< +Lib.hello; >";
assert_accepts(lib, doc);
}