1use crate::ast::*;
2
3#[derive(Debug)]
6pub enum SemanticError {
7 TypeMismatch { expected: Type, found: Type },
8 UndefinedVariable(String),
9 UndefinedStruct(String),
10 UndefinedField { struct_name: String, field: String },
11 UndefinedFunction(String),
12 ReturnTypeMismatch { function: String, expected: Type, found: Type },
13 ArgCountMismatch { function: String, expected: usize, found: usize },
14 ArgTypeMismatch { function: String, param_index: usize, expected: Type, found: Type },
15 PropagateOnNonTrit { found: Type },
17 NonExhaustiveMatch(String),
18}
19
20impl std::fmt::Display for SemanticError {
21 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22 match self {
23 Self::TypeMismatch { expected, found } =>
24 write!(f, "[TYPE-001] Type mismatch: expected {expected:?}, found {found:?}. A trit is not an int. An int is not a trit. They don't coerce.\n → details: stdlib/errors/TYPE-001.tern | ternlang errors TYPE-001"),
25 Self::UndefinedVariable(n) =>
26 write!(f, "[SCOPE-001] '{n}' is undefined — hold state. Declare before use, or check for a typo.\n → details: stdlib/errors/SCOPE-001.tern | ternlang errors SCOPE-001"),
27 Self::UndefinedStruct(n) =>
28 write!(f, "[STRUCT-001] Struct '{n}' doesn't exist. A ghost type — the type system can't find it anywhere.\n → details: stdlib/errors/STRUCT-001.tern | ternlang errors STRUCT-001"),
29 Self::UndefinedField { struct_name, field } =>
30 write!(f, "[STRUCT-002] Struct '{struct_name}' has no field '{field}'. Check the definition — maybe it was renamed.\n → details: stdlib/errors/STRUCT-002.tern | ternlang errors STRUCT-002"),
31 Self::UndefinedFunction(n) =>
32 write!(f, "[FN-001] '{n}' was called but never defined. Declare it above the call site, or check for a typo.\n → details: stdlib/errors/FN-001.tern | ternlang errors FN-001"),
33 Self::ReturnTypeMismatch { function, expected, found } =>
34 write!(f, "[FN-002] '{function}' promised to return {expected:?} but returned {found:?}. Ternary contracts are strict — all paths must match.\n → details: stdlib/errors/FN-002.tern | ternlang errors FN-002"),
35 Self::ArgCountMismatch { function, expected, found } =>
36 write!(f, "[FN-003] '{function}' expects {expected} arg(s), got {found}. Arity is not optional — not even in hold state.\n → details: stdlib/errors/FN-003.tern | ternlang errors FN-003"),
37 Self::ArgTypeMismatch { function, param_index, expected, found } =>
38 write!(f, "[FN-004] '{function}' arg {param_index}: expected {expected:?}, found {found:?}. Types travel with their values — they don't change at the border.\n → details: stdlib/errors/FN-004.tern | ternlang errors FN-004"),
39 Self::PropagateOnNonTrit { found } =>
40 write!(f, "[PROP-001] '?' used on a {found:?} expression. Only trit-returning functions carry the three-valued signal. The third state requires a trit.\n → details: stdlib/errors/PROP-001.tern | ternlang errors PROP-001"),
41 Self::NonExhaustiveMatch(msg) =>
42 write!(f, "Non-exhaustive match: {msg}"),
43 }
44 }
45}
46
47#[derive(Debug, Clone)]
50pub struct FunctionSig {
51 pub params: Option<Vec<Type>>,
53 pub return_type: Type,
54}
55
56impl FunctionSig {
57 fn exact(params: Vec<Type>, return_type: Type) -> Self {
58 Self { params: Some(params), return_type }
59 }
60 fn variadic(return_type: Type) -> Self {
61 Self { params: None, return_type }
62 }
63}
64
65pub struct SemanticAnalyzer {
68 scopes: Vec<std::collections::HashMap<String, Type>>,
69 struct_defs: std::collections::HashMap<String, Vec<(String, Type)>>,
70 func_signatures: std::collections::HashMap<String, FunctionSig>,
71 current_fn_name: Option<String>,
73 current_fn_return: Option<Type>,
74}
75
76impl SemanticAnalyzer {
77 pub fn new() -> Self {
78 let mut sigs: std::collections::HashMap<String, FunctionSig> = std::collections::HashMap::new();
79
80 sigs.insert("consensus".into(), FunctionSig::exact(vec![Type::Trit, Type::Trit], Type::Trit));
82 sigs.insert("invert".into(), FunctionSig::exact(vec![Type::Trit], Type::Trit));
83 sigs.insert("length".into(), FunctionSig::variadic(Type::Int));
84 sigs.insert("truth".into(), FunctionSig::exact(vec![], Type::Trit));
85 sigs.insert("hold".into(), FunctionSig::exact(vec![], Type::Trit));
86 sigs.insert("conflict".into(), FunctionSig::exact(vec![], Type::Trit));
87 sigs.insert("mul".into(), FunctionSig::exact(vec![Type::Trit, Type::Trit], Type::Trit));
88
89 sigs.insert("matmul".into(), FunctionSig::variadic(Type::TritTensor { dims: vec![0, 0] }));
91 sigs.insert("sparsity".into(), FunctionSig::variadic(Type::Int));
92 sigs.insert("shape".into(), FunctionSig::variadic(Type::Int));
93 sigs.insert("zeros".into(), FunctionSig::variadic(Type::TritTensor { dims: vec![0, 0] }));
94
95 sigs.insert("print".into(), FunctionSig::variadic(Type::Trit));
97 sigs.insert("println".into(), FunctionSig::variadic(Type::Trit));
98
99 sigs.insert("abs".into(), FunctionSig::exact(vec![Type::Int], Type::Int));
101 sigs.insert("min".into(), FunctionSig::exact(vec![Type::Int, Type::Int], Type::Int));
102 sigs.insert("max".into(), FunctionSig::exact(vec![Type::Int, Type::Int], Type::Int));
103
104 sigs.insert("quantize".into(), FunctionSig::variadic(Type::TritTensor { dims: vec![0, 0] }));
106 sigs.insert("threshold".into(),FunctionSig::variadic(Type::Float));
107
108 sigs.insert("forward".into(), FunctionSig::variadic(Type::TritTensor { dims: vec![0, 0] }));
110 sigs.insert("argmax".into(), FunctionSig::variadic(Type::Int));
111
112 sigs.insert("cast".into(), FunctionSig::variadic(Type::Trit));
114
115 Self {
116 scopes: vec![std::collections::HashMap::new()],
117 struct_defs: std::collections::HashMap::new(),
118 func_signatures: sigs,
119 current_fn_name: None,
120 current_fn_return: None,
121 }
122 }
123
124 pub fn register_structs(&mut self, structs: &[StructDef]) {
127 for s in structs {
128 self.struct_defs.insert(s.name.clone(), s.fields.clone());
129 }
130 }
131
132 pub fn register_functions(&mut self, functions: &[Function]) {
133 for f in functions {
134 let params = f.params.iter().map(|(_, ty)| ty.clone()).collect();
135 self.func_signatures.insert(
136 f.name.clone(),
137 FunctionSig::exact(params, f.return_type.clone()),
138 );
139 }
140 }
141
142 pub fn register_agents(&mut self, agents: &[AgentDef]) {
143 for agent in agents {
144 for method in &agent.methods {
145 let params = method.params.iter().map(|(_, ty)| ty.clone()).collect();
146 let sig = FunctionSig::exact(params, method.return_type.clone());
147 self.func_signatures.insert(method.name.clone(), sig.clone());
148 self.func_signatures.insert(
149 format!("{}::{}", agent.name, method.name),
150 sig,
151 );
152 }
153 }
154 }
155
156 pub fn check_program(&mut self, program: &Program) -> Result<(), SemanticError> {
159 self.register_structs(&program.structs);
160 self.register_functions(&program.functions);
161 self.register_agents(&program.agents);
162 for agent in &program.agents {
163 for method in &agent.methods {
164 self.check_function(method)?;
165 }
166 }
167 for func in &program.functions {
168 self.check_function(func)?;
169 }
170 Ok(())
171 }
172
173 fn check_function(&mut self, func: &Function) -> Result<(), SemanticError> {
174 let prev_name = self.current_fn_name.take();
176 let prev_return = self.current_fn_return.take();
177 self.current_fn_name = Some(func.name.clone());
178 self.current_fn_return = Some(func.return_type.clone());
179
180 self.scopes.push(std::collections::HashMap::new());
181 for (name, ty) in &func.params {
182 self.scopes.last_mut().unwrap().insert(name.clone(), ty.clone());
183 }
184 for stmt in &func.body {
185 self.check_stmt(stmt)?;
186 }
187 self.scopes.pop();
188
189 self.current_fn_name = prev_name;
191 self.current_fn_return = prev_return;
192 Ok(())
193 }
194
195 pub fn check_stmt(&mut self, stmt: &Stmt) -> Result<(), SemanticError> {
198 match stmt {
199 Stmt::Let { name, ty, value } => {
200 let val_ty = self.infer_expr_type(value)?;
201 let type_ok = val_ty == *ty
202 || matches!(value, Expr::Cast { .. })
203 || matches!(value, Expr::StructLiteral { .. }) || (*ty == Type::Int && val_ty == Type::Trit)
205 || (*ty == Type::Trit && val_ty == Type::Int)
206 || (matches!(ty, Type::Named(_)) && val_ty == Type::Trit)
207 || (matches!(ty, Type::TritTensor { .. }) && matches!(val_ty, Type::TritTensor { .. }))
208 || (*ty == Type::AgentRef && val_ty == Type::AgentRef);
209 if !type_ok {
210 return Err(SemanticError::TypeMismatch { expected: ty.clone(), found: val_ty });
211 }
212 self.scopes.last_mut().unwrap().insert(name.clone(), ty.clone());
213 Ok(())
214 }
215
216 Stmt::Return(expr) => {
217 let found = self.infer_expr_type(expr)?;
218 if let (Some(fn_name), Some(expected)) = (&self.current_fn_name, &self.current_fn_return) {
219 let ok = found == *expected
221 || matches!(expr, Expr::Cast { .. })
222 || matches!(expr, Expr::StructLiteral { .. })
223 || (*expected == Type::Int && found == Type::Trit)
224 || (*expected == Type::Trit && found == Type::Int)
225 || (matches!(expected, Type::TritTensor { .. }) && matches!(found, Type::TritTensor { .. }))
226 || (matches!(expected, Type::Named(_)) && found == Type::Trit);
227 if !ok {
228 return Err(SemanticError::ReturnTypeMismatch {
229 function: fn_name.clone(),
230 expected: expected.clone(),
231 found,
232 });
233 }
234 }
235 Ok(())
236 }
237
238 Stmt::IfTernary { condition, on_pos, on_zero, on_neg } => {
239 let cond_ty = self.infer_expr_type(condition)?;
240 if cond_ty != Type::Trit {
241 return Err(SemanticError::TypeMismatch { expected: Type::Trit, found: cond_ty });
242 }
243 self.check_stmt(on_pos)?;
244 self.check_stmt(on_zero)?;
245 self.check_stmt(on_neg)?;
246 Ok(())
247 }
248
249 Stmt::Match { condition, arms } => {
250 let cond_ty = self.infer_expr_type(condition)?;
251 if cond_ty != Type::Trit && cond_ty != Type::Int && cond_ty != Type::Float {
252 return Err(SemanticError::TypeMismatch { expected: Type::Trit, found: cond_ty });
253 }
254
255 if cond_ty == Type::Trit {
256 let has_pos = arms.iter().any(|(p, _)| matches!(p, Pattern::Trit(1) | Pattern::Int(1)));
258 let has_wildcard = arms.iter().any(|(p, _)| matches!(p, Pattern::Wildcard));
259 if !has_wildcard {
260 let has_zero = arms.iter().any(|(p, _)| matches!(p, Pattern::Trit(0) | Pattern::Int(0)));
261 let has_neg = arms.iter().any(|(p, _)| matches!(p, Pattern::Trit(-1) | Pattern::Int(-1)));
262 if !has_pos || !has_zero || !has_neg {
263 return Err(SemanticError::NonExhaustiveMatch("Trit match must cover -1, 0, and 1 (or use _ wildcard)".into()));
264 }
265 }
266 for (pattern, _) in arms {
267 match pattern {
268 Pattern::Trit(v) => if *v < -1 || *v > 1 { return Err(SemanticError::TypeMismatch { expected: Type::Trit, found: Type::Int }); }
269 Pattern::Int(v) => if *v < -1 || *v > 1 { return Err(SemanticError::TypeMismatch { expected: Type::Trit, found: Type::Int }); }
270 Pattern::Float(_) => return Err(SemanticError::TypeMismatch { expected: Type::Trit, found: Type::Float }),
271 Pattern::Wildcard => {} }
273 }
274 }
275
276 for (_pattern, arm_stmt) in arms {
277 self.check_stmt(arm_stmt)?;
278 }
279 Ok(())
280 }
281
282 Stmt::Block(stmts) => {
283 self.scopes.push(std::collections::HashMap::new());
284 for s in stmts { self.check_stmt(s)?; }
285 self.scopes.pop();
286 Ok(())
287 }
288
289 Stmt::Decorated { stmt, .. } => self.check_stmt(stmt),
290
291 Stmt::Expr(expr) => { self.infer_expr_type(expr)?; Ok(()) }
292
293 Stmt::ForIn { var, iter, body } => {
294 self.infer_expr_type(iter)?;
295 self.scopes.push(std::collections::HashMap::new());
296 self.scopes.last_mut().unwrap().insert(var.clone(), Type::Trit);
297 self.check_stmt(body)?;
298 self.scopes.pop();
299 Ok(())
300 }
301
302 Stmt::WhileTernary { condition, on_pos, on_zero, on_neg } => {
303 let cond_ty = self.infer_expr_type(condition)?;
304 if cond_ty != Type::Trit {
305 return Err(SemanticError::TypeMismatch { expected: Type::Trit, found: cond_ty });
306 }
307 self.check_stmt(on_pos)?;
308 self.check_stmt(on_zero)?;
309 self.check_stmt(on_neg)?;
310 Ok(())
311 }
312
313 Stmt::Loop { body } => self.check_stmt(body),
314 Stmt::Break => Ok(()),
315 Stmt::Continue => Ok(()),
316 Stmt::Use { .. } => Ok(()),
317 Stmt::FromImport { .. } => Ok(()),
318
319 Stmt::Send { target, message } => {
320 self.infer_expr_type(target)?;
321 self.infer_expr_type(message)?;
322 Ok(())
323 }
324
325 Stmt::FieldSet { object, field, value } => {
326 let obj_ty = self.lookup_var(object)?;
327 if let Type::Named(struct_name) = obj_ty {
328 let field_ty = self.lookup_field(&struct_name, field)?;
329 let val_ty = self.infer_expr_type(value)?;
330 if val_ty != field_ty {
331 return Err(SemanticError::TypeMismatch { expected: field_ty, found: val_ty });
332 }
333 } else {
334 self.infer_expr_type(value)?;
335 }
336 Ok(())
337 }
338
339 Stmt::IndexSet { object, row, col, value } => {
340 self.lookup_var(object)?;
341 self.infer_expr_type(row)?;
342 self.infer_expr_type(col)?;
343 self.infer_expr_type(value)?;
344 Ok(())
345 }
346
347 Stmt::Set { name, value } => {
348 let var_ty = self.lookup_var(name)?;
349 let val_ty = self.infer_expr_type(value)?;
350 let ok = var_ty == val_ty
351 || matches!(value, Expr::Cast { .. })
352 || (var_ty == Type::Int && val_ty == Type::Trit)
353 || (var_ty == Type::Trit && val_ty == Type::Int);
354 if !ok {
355 return Err(SemanticError::TypeMismatch { expected: var_ty, found: val_ty });
356 }
357 Ok(())
358 }
359 }
360 }
361
362 fn infer_expr_type(&self, expr: &Expr) -> Result<Type, SemanticError> {
365 match expr {
366 Expr::TritLiteral(_) => Ok(Type::Trit),
367 Expr::IntLiteral(_) => Ok(Type::Int),
368 Expr::FloatLiteral(_) => Ok(Type::Float),
369 Expr::StringLiteral(_) => Ok(Type::String),
370 Expr::Ident(name) => self.lookup_var(name),
371
372 Expr::BinaryOp { op, lhs, rhs } => {
373 let l = self.infer_expr_type(lhs)?;
374 let r = self.infer_expr_type(rhs)?;
375 match op {
376 BinOp::Less | BinOp::Greater | BinOp::LessEqual | BinOp::GreaterEqual | BinOp::Equal | BinOp::NotEqual | BinOp::And | BinOp::Or => {
377 Ok(Type::Trit)
378 }
379
380 _ => {
381 let is_numeric = |t: &Type| matches!(t, Type::Int | Type::Trit | Type::Float);
383 if is_numeric(&l) && is_numeric(&r) {
384 if l == Type::Float || r == Type::Float { return Ok(Type::Float); }
385 if l == Type::Int || r == Type::Int { return Ok(Type::Int); }
386 return Ok(Type::Trit);
387 }
388
389 if l != r {
390 return Err(SemanticError::TypeMismatch { expected: l, found: r });
391 }
392 Ok(l)
393 }
394 }
395 }
396
397 Expr::UnaryOp { expr, .. } => self.infer_expr_type(expr),
398
399 Expr::Call { callee, args } => {
400 let sig = self.func_signatures.get(callee.as_str())
401 .ok_or_else(|| SemanticError::UndefinedFunction(callee.clone()))?
402 .clone();
403
404 if let Some(param_types) = &sig.params {
406 if args.len() != param_types.len() {
407 return Err(SemanticError::ArgCountMismatch {
408 function: callee.clone(),
409 expected: param_types.len(),
410 found: args.len(),
411 });
412 }
413 for (i, (arg, expected_ty)) in args.iter().zip(param_types.iter()).enumerate() {
414 let found_ty = self.infer_expr_type(arg)?;
415 let ok = found_ty == *expected_ty
417 || matches!(arg, Expr::Cast { .. })
418 || (expected_ty == &Type::Int && found_ty == Type::Trit)
419 || (expected_ty == &Type::Trit && found_ty == Type::Int)
420 || (matches!(expected_ty, Type::TritTensor { .. })
421 && matches!(found_ty, Type::TritTensor { .. }))
422 || (matches!(expected_ty, Type::Named(_)) && found_ty == Type::Trit);
423 if !ok {
424 return Err(SemanticError::ArgTypeMismatch {
425 function: callee.clone(),
426 param_index: i,
427 expected: expected_ty.clone(),
428 found: found_ty,
429 });
430 }
431 }
432 } else {
433 for arg in args { self.infer_expr_type(arg)?; }
435 }
436
437 Ok(sig.return_type)
438 }
439
440 Expr::Cast { ty, .. } => Ok(ty.clone()),
441 Expr::Spawn { .. } => Ok(Type::AgentRef),
442 Expr::Await { .. } => Ok(Type::Trit),
443 Expr::NodeId => Ok(Type::String),
444
445 Expr::Propagate { expr } => {
446 let inner = self.infer_expr_type(expr)?;
447 if inner != Type::Trit {
448 return Err(SemanticError::PropagateOnNonTrit { found: inner });
449 }
450 Ok(Type::Trit)
451 }
452
453 Expr::TritTensorLiteral(vals) => {
454 Ok(Type::TritTensor { dims: vec![vals.len()] })
455 }
456
457 Expr::StructLiteral { name, fields } => {
458 let def = self.struct_defs.get(name)
460 .ok_or_else(|| SemanticError::UndefinedStruct(name.clone()))?;
461
462 if fields.len() != def.len() {
463 return Err(SemanticError::ArgCountMismatch {
464 function: name.clone(),
465 expected: def.len(),
466 found: fields.len()
467 });
468 }
469
470 for (f_name, f_val) in fields {
471 let expected_f_ty = def.iter()
472 .find(|(n, _)| n == f_name)
473 .ok_or_else(|| SemanticError::UndefinedField {
474 struct_name: name.clone(),
475 field: f_name.clone()
476 })?
477 .1.clone();
478 let found_f_ty = self.infer_expr_type(f_val)?;
479 if found_f_ty != expected_f_ty {
480 return Err(SemanticError::TypeMismatch {
481 expected: expected_f_ty,
482 found: found_f_ty
483 });
484 }
485 }
486 Ok(Type::Named(name.clone()))
487 }
488
489 Expr::FieldAccess { object, field } => {
490 let obj_ty = self.infer_expr_type(object)?;
491 if let Type::Named(struct_name) = obj_ty {
492 self.lookup_field(&struct_name, field)
493 } else {
494 Ok(Type::Trit)
495 }
496 }
497
498 Expr::Index { object, row, col } => {
499 self.infer_expr_type(object)?;
500 self.infer_expr_type(row)?;
501 self.infer_expr_type(col)?;
502 Ok(Type::Trit)
503 }
504
505 Expr::Slice { object, start, end, stride } => {
506 self.infer_expr_type(object)?;
507 self.infer_expr_type(start)?;
508 self.infer_expr_type(end)?;
509 self.infer_expr_type(stride)?;
510 Ok(Type::TritTensor { dims: vec![0] })
512 }
513 }
514 }
515
516 fn lookup_var(&self, name: &str) -> Result<Type, SemanticError> {
519 for scope in self.scopes.iter().rev() {
520 if let Some(ty) = scope.get(name) { return Ok(ty.clone()); }
521 }
522 Err(SemanticError::UndefinedVariable(name.to_string()))
523 }
524
525 fn lookup_field(&self, struct_name: &str, field: &str) -> Result<Type, SemanticError> {
526 let fields = self.struct_defs.get(struct_name)
527 .ok_or_else(|| SemanticError::UndefinedStruct(struct_name.to_string()))?;
528 fields.iter()
529 .find(|(f, _)| f == field)
530 .map(|(_, ty)| ty.clone())
531 .ok_or_else(|| SemanticError::UndefinedField {
532 struct_name: struct_name.to_string(),
533 field: field.to_string(),
534 })
535 }
536}
537
538#[cfg(test)]
541mod tests {
542 use super::*;
543 use crate::parser::Parser;
544
545 fn check(src: &str) -> Result<(), SemanticError> {
546 let mut parser = Parser::new(src);
547 let prog = parser.parse_program().expect("parse failed");
548 let mut analyzer = SemanticAnalyzer::new();
549 analyzer.check_program(&prog)
550 }
551
552 fn check_ok(src: &str) {
553 assert!(check(src).is_ok(), "expected ok, got: {:?}", check(src));
554 }
555
556 fn check_err(src: &str) {
557 assert!(check(src).is_err(), "expected error but check passed");
558 }
559
560 #[test]
563 fn test_return_correct_type() {
564 check_ok("fn f() -> trit { return 1; }");
565 }
566
567 #[test]
568 fn test_return_int_in_trit_fn() {
569 check_ok("fn f() -> trit { let x: int = 42; return x; }");
571 }
572
573 #[test]
574 fn test_return_trit_in_trit_fn() {
575 check_ok("fn decide(a: trit, b: trit) -> trit { return consensus(a, b); }");
576 }
577
578 #[test]
581 fn test_call_correct_arity() {
582 check_ok("fn f() -> trit { return consensus(1, -1); }");
583 }
584
585 #[test]
586 fn test_call_too_few_args_caught() {
587 check_err("fn f() -> trit { return consensus(1); }");
588 }
589
590 #[test]
591 fn test_call_too_many_args_caught() {
592 check_err("fn f() -> trit { return invert(1, 1); }");
593 }
594
595 #[test]
598 fn test_call_int_arg_in_trit_fn() {
599 check_ok("fn f(a: trit) -> trit { return invert(a); } fn main() -> trit { let x: int = 42; return f(x); }");
601 }
602
603 #[test]
604 fn test_call_correct_arg_type() {
605 check_ok("fn f(a: trit) -> trit { return invert(a); }");
606 }
607
608 #[test]
611 fn test_undefined_function_caught() {
612 check_err("fn f() -> trit { return doesnt_exist(1); }");
613 }
614
615 #[test]
618 fn test_user_fn_return_type_registered() {
619 check_ok("fn helper(a: trit) -> trit { return invert(a); } fn main() -> trit { return helper(1); }");
620 }
621
622 #[test]
623 fn test_user_fn_int_return_ok() {
624 check_ok("fn helper(a: trit) -> trit { let x: int = 1; return x; }");
626 }
627
628 #[test]
631 fn test_undefined_variable_caught() {
632 check_err("fn f() -> trit { return ghost_var; }");
633 }
634
635 #[test]
636 fn test_defined_variable_ok() {
637 check_ok("fn f() -> trit { let x: trit = 1; return x; }");
638 }
639
640 #[test]
643 fn test_struct_field_access_ok() {
644 check_ok("struct S { val: trit } fn f(s: S) -> trit { return s.val; }");
645 }
646}