1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
//! End-to-end coverage for parenthesized-operator NAMES (`( ‹op› )`) — the
//! gap that blocked porting `itemize.satyh`/`progsynt.satyh`/`proof.satyh`
//! (upstream's `let (+++>) = ..`/`val (-->) : ty` binding forms and the bare
//! `(+++)` atomic-expression reference). Real source text run through
//! `parse_file` -> `elaborate::elaborate_program` -> `typecheck::typecheck`
//! -> `eval::Interp`, mirroring `tests/type_synonym.rs`'s harness.
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 eval_str(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 int(src: &str) -> i64 {
match eval_str(src) {
Ok(Value::Int(n)) => n,
Ok(other) => panic!("{src:?} evaluated to {other:?}, not an int"),
Err(e) => panic!("{src:?} failed to parse/typecheck/evaluate: {e}"),
}
}
fn boolean(src: &str) -> bool {
match eval_str(src) {
Ok(Value::Bool(b)) => b,
Ok(other) => panic!("{src:?} evaluated to {other:?}, not a bool"),
Err(e) => panic!("{src:?} failed to parse/typecheck/evaluate: {e}"),
}
}
#[test]
fn let_paren_op_binding_with_no_params() {
assert_eq!(int("let (+++>) = 1 in 0"), 0);
}
#[test]
fn let_paren_op_binding_curried_infix_use() {
// The `OpChain` fold (elaborate.rs's `op_chain`) already resolves any
// user-bound operator name generically, so this needs no elaborator
// change beyond the `(op)` NAME grammar itself.
assert_eq!(int("let (<+>) a b = a + b in 3 <+> 4"), 7);
}
#[test]
fn let_paren_op_binding_prefix_use() {
// Prefix application, how `progsynt.satyh`-style packages mostly use
// their custom operators.
assert_eq!(int("let (-->) a b = a * b in (-->) 3 4"), 12);
}
#[test]
fn bare_paren_op_reference_to_a_builtin_primitive() {
// `(+)` referencing the registered `"+"` primitive as a first-class
// value (`ast::Atomic::OpRef`).
assert_eq!(int("let f = (+) in f 3 4"), 7);
}
#[test]
fn bare_paren_op_reference_applied_directly() {
assert_eq!(int("(*) 6 7"), 42);
}
#[test]
fn module_sig_val_paren_op_matches_struct_let_paren_op() {
// Module signatures are parsed but not enforced against the struct
// (elaborate.rs's `TopBinding::Module` doc comment), so this exercises
// parsing + elaboration + evaluation end-to-end via `open`.
let src = "\
module M : sig val (-->) : int -> int -> int end = struct \
let (-->) a b = a + b \
end in open M in (-->) 3 4";
assert_eq!(int(src), 7);
}
#[test]
fn module_sig_val_paren_op_parses_with_unresolved_type_name() {
// Undeclared type `t`: `sig .. end` is parsed but never consulted by the
// (untyped) elaborator or the typechecker, so this only needs to PARSE.
let src = "module M : sig val (-->) : t -> t -> t end = struct \
let (-->) a b = a end";
rustyfi_syntax::parse_file(src)
.expect("the parenthesized-operator sig/struct form should parse");
}
#[test]
fn not_binds_looser_than_application() {
// Upstream `not f x` is `not (f x)`, NOT `(not f) x` (which would apply
// `not` to a function value and fail to typecheck). The blocker case was
// `satysfi-xpath`'s `util.satyh`: `not float-zero-or-nan (a +. 1.)`.
let src = "let id = fun b -> b in not id true";
assert!(!boolean(src), "`not id true` must fold as `not (id true)`");
}
#[test]
fn not_still_first_class_in_argument_position() {
// The looser-binding rule only fires when `not` is the HEAD of an
// application; a `not` sitting in ARGUMENT position stays the ordinary
// `not` primitive value, so it can still be passed to a higher-order
// function. `apply not true` = `not true` = `false`.
let src = "let apply = fun f x -> f x in apply not true";
assert!(
!boolean(src),
"`not` in argument position must stay first-class"
);
}
// ---- nested-module operator via local-open (real-world compat round 4) ----
#[test]
fn nested_module_operator_local_open() {
// `satysfi-fss`'s `fss/font/selection.satyg`: a nested `module Inner`
// whose members (including an operator `(<)`) are bound under the fully-
// qualified `Outer.Inner.<`, referenced from a sibling via a local-open
// `Inner.(z < z)`. `open_module` must resolve the relative alias
// `Inner.<` to its actual qualified binding key, or the overlaid `<`
// reaches the typechecker unbound.
let src = "module Outer : sig val run : int end = struct \
module Inner : sig val (<) : int -> int -> bool val z : int end = struct \
let (<) a b = true \
let z = 5 \
end \
let run = if Inner.(z < z) then 1 else 2 \
end in Outer.run";
assert_eq!(int(src), 1);
}