1use ternlang_core::{
24 Parser, SemanticAnalyzer, BytecodeEmitter, StdlibLoader, BetVm,
25 vm::Value,
26 trit::Trit,
27};
28
29#[derive(Debug, Clone)]
33pub enum TernExpected {
34 Trit(i8),
36 ParseError,
38 SemanticError,
40}
41
42pub struct TernTestCase {
44 pub name: &'static str,
45 pub source: &'static str,
47 pub expected: TernExpected,
48}
49
50#[derive(Debug)]
54pub struct TernTestResult {
55 pub name: &'static str,
56 pub passed: bool,
57 pub actual_trit: Option<i8>,
58 pub message: String,
59}
60
61impl TernTestResult {
62 fn pass(name: &'static str, trit: Option<i8>) -> Self {
63 Self { name, passed: true, actual_trit: trit, message: "ok".into() }
64 }
65
66 fn fail(name: &'static str, msg: impl Into<String>) -> Self {
67 Self { name, passed: false, actual_trit: None, message: msg.into() }
68 }
69
70 fn fail_trit(name: &'static str, actual: i8, msg: impl Into<String>) -> Self {
71 Self { name, passed: false, actual_trit: Some(actual), message: msg.into() }
72 }
73}
74
75pub fn run_tern_test(case: &TernTestCase) -> TernTestResult {
80 let mut parser = Parser::new(case.source);
82 let prog = match parser.parse_program() {
83 Ok(p) => p,
84 Err(e) => {
85 let msg = format!("{}", e);
86 let passed = matches!(case.expected, TernExpected::ParseError);
87 return if passed {
88 TernTestResult::pass(case.name, None)
89 } else {
90 TernTestResult::fail(case.name, format!("Parse error (unexpected): {msg}"))
91 };
92 }
93 };
94 if matches!(case.expected, TernExpected::ParseError) {
95 return TernTestResult::fail(case.name, "expected a parse error but program parsed successfully");
96 }
97
98 let mut prog = prog;
100 StdlibLoader::resolve(&mut prog);
101
102 let mut checker = SemanticAnalyzer::new();
104 if let Err(e) = checker.check_program(&prog) {
105 let msg = format!("{}", e);
106 let passed = matches!(case.expected, TernExpected::SemanticError);
107 return if passed {
108 TernTestResult::pass(case.name, None)
109 } else {
110 TernTestResult::fail(case.name, format!("Semantic error (unexpected): {msg}"))
111 };
112 }
113 if matches!(case.expected, TernExpected::SemanticError) {
114 return TernTestResult::fail(case.name, "expected a semantic error but program passed analysis");
115 }
116
117 let mut emitter = BytecodeEmitter::new();
119 emitter.emit_program(&prog);
120 emitter.emit_entry_call("main");
122 let code = emitter.finalize();
123
124 let mut vm = BetVm::new(code);
126 match vm.run() {
127 Err(e) => TernTestResult::fail(case.name, format!("VM error: {e}")),
128 Ok(()) => {
129 let trit_val: i8 = match vm.peek_stack() {
131 Some(Value::Trit(Trit::Affirm)) => 1,
132 Some(Value::Trit(Trit::Tend)) => 0,
133 Some(Value::Trit(Trit::Reject)) => -1,
134 Some(other) => {
135 return TernTestResult::fail(
136 case.name,
137 format!("VM returned non-trit value: {other:?}"),
138 );
139 }
140 None => {
141 return TernTestResult::fail(case.name, "VM stack is empty after execution");
142 }
143 };
144
145 match &case.expected {
146 TernExpected::Trit(expected) => {
147 if trit_val == *expected {
148 TernTestResult::pass(case.name, Some(trit_val))
149 } else {
150 TernTestResult::fail_trit(
151 case.name,
152 trit_val,
153 format!("expected trit={expected}, got trit={trit_val}"),
154 )
155 }
156 }
157 TernExpected::ParseError | TernExpected::SemanticError => unreachable!(),
159 }
160 }
161 }
162}
163
164#[macro_export]
179macro_rules! assert_tern {
180 ($case:expr) => {{
181 let result = $crate::run_tern_test(&$case);
182 assert!(
183 result.passed,
184 "\n[TERN-TEST] '{}' failed\n → {}\n",
185 result.name,
186 result.message,
187 );
188 result
189 }};
190}
191
192#[cfg(test)]
195mod tests {
196 use super::*;
197
198 fn run(name: &'static str, source: &'static str, expected: TernExpected) -> TernTestResult {
199 run_tern_test(&TernTestCase { name, source, expected })
200 }
201
202 #[test]
205 fn trit_pos_literal() {
206 let r = run("trit +1", "fn main() -> trit { return 1; }", TernExpected::Trit(1));
207 assert!(r.passed, "{}", r.message);
208 }
209
210 #[test]
211 fn trit_zero_literal() {
212 let r = run("trit 0", "fn main() -> trit { return 0; }", TernExpected::Trit(0));
213 assert!(r.passed, "{}", r.message);
214 }
215
216 #[test]
217 fn trit_neg_literal() {
218 let r = run("trit -1", "fn main() -> trit { return -1; }", TernExpected::Trit(-1));
219 assert!(r.passed, "{}", r.message);
220 }
221
222 #[test]
225 fn consensus_pos_and_zero() {
226 let r = run(
228 "consensus(+1, 0)=+1",
229 "fn main() -> trit { return consensus(1, 0); }",
230 TernExpected::Trit(1),
231 );
232 assert!(r.passed, "{}", r.message);
233 }
234
235 #[test]
236 fn consensus_conflict_holds() {
237 let r = run(
239 "consensus(+1,-1)=0",
240 "fn main() -> trit { return consensus(1, -1); }",
241 TernExpected::Trit(0),
242 );
243 assert!(r.passed, "{}", r.message);
244 }
245
246 #[test]
249 fn propagate_passes_through_pos() {
250 let r = run(
251 "propagate pass-through on +1",
252 r#"
253fn check() -> trit { return 1; }
254fn main() -> trit { return check()?; }
255"#,
256 TernExpected::Trit(1),
257 );
258 assert!(r.passed, "{}", r.message);
259 }
260
261 #[test]
262 fn propagate_early_returns_on_neg() {
263 let r = run(
265 "propagate early return on -1",
266 r#"
267fn check() -> trit { return -1; }
268fn main() -> trit {
269 let x: trit = check()?;
270 return 1;
271}
272"#,
273 TernExpected::Trit(-1),
274 );
275 assert!(r.passed, "{}", r.message);
276 }
277
278 #[test]
281 fn stdlib_trit_resolves() {
282 let r = run(
283 "std::trit resolves",
284 r#"
285fn main() -> trit {
286 use std::trit;
287 return abs(-1);
288}
289"#,
290 TernExpected::Trit(1),
291 );
292 assert!(r.passed, "{}", r.message);
293 }
294
295 #[test]
298 fn non_exhaustive_match_is_parse_error() {
299 let r = run(
300 "non-exhaustive match",
301 r#"
302fn main() -> trit {
303 let x: trit = 1;
304 match x { 1 => { return 1; } 0 => { return 0; } }
305}
306"#,
307 TernExpected::ParseError,
308 );
309 assert!(r.passed, "{}", r.message);
310 }
311
312 #[test]
313 fn undefined_variable_is_semantic_error() {
314 let r = run(
315 "undefined variable",
316 "fn main() -> trit { return ghost; }",
317 TernExpected::SemanticError,
318 );
319 assert!(r.passed, "{}", r.message);
320 }
321}