1use crate::lcnf::*;
6use std::fmt::Write as FmtWrite;
7
8use super::types::{
9 CSharpBackend, CSharpClass, CSharpEnum, CSharpExpr, CSharpInterface, CSharpInterpolationPart,
10 CSharpLit, CSharpMethod, CSharpModule, CSharpProperty, CSharpRecord, CSharpStmt,
11 CSharpSwitchArm, CSharpType,
12};
13
14pub(super) fn lcnf_type_to_csharp(ty: &LcnfType) -> CSharpType {
16 match ty {
17 LcnfType::Nat => CSharpType::Long,
18 LcnfType::LcnfString => CSharpType::String,
19 LcnfType::Unit => CSharpType::Void,
20 LcnfType::Erased | LcnfType::Irrelevant => CSharpType::Object,
21 LcnfType::Object => CSharpType::Object,
22 LcnfType::Var(name) => CSharpType::Custom(name.clone()),
23 LcnfType::Fun(params, ret) => {
24 let cs_params: Vec<CSharpType> = params.iter().map(lcnf_type_to_csharp).collect();
25 let cs_ret = lcnf_type_to_csharp(ret);
26 CSharpType::Func(cs_params, Box::new(cs_ret))
27 }
28 LcnfType::Ctor(name, _args) => CSharpType::Custom(name.clone()),
29 }
30}
31pub(super) fn emit_stmts(stmts: &[CSharpStmt], indent: &str, out: &mut std::string::String) {
33 for stmt in stmts {
34 emit_stmt(stmt, indent, out);
35 }
36}
37pub(super) fn emit_stmt(stmt: &CSharpStmt, indent: &str, out: &mut std::string::String) {
39 let inner = format!("{} ", indent);
40 match stmt {
41 CSharpStmt::Expr(expr) => {
42 let _ = writeln!(out, "{}{};", indent, expr);
43 }
44 CSharpStmt::Assign { target, value } => {
45 let _ = writeln!(out, "{}{} = {};", indent, target, value);
46 }
47 CSharpStmt::LocalVar {
48 name,
49 ty,
50 init,
51 is_const,
52 } => {
53 let kw = if *is_const { "const" } else { "var" };
54 match (ty, init) {
55 (Some(t), Some(v)) => {
56 let _ = writeln!(out, "{}{} {} {} = {};", indent, kw, t, name, v);
57 }
58 (Some(t), None) => {
59 let _ = writeln!(out, "{}{} {};", indent, t, name);
60 }
61 (None, Some(v)) => {
62 let _ = writeln!(out, "{}{} {} = {};", indent, kw, name, v);
63 }
64 (None, None) => {
65 let _ = writeln!(out, "{}{} {};", indent, kw, name);
66 }
67 }
68 }
69 CSharpStmt::Return(None) => {
70 let _ = writeln!(out, "{}return;", indent);
71 }
72 CSharpStmt::Return(Some(expr)) => {
73 let _ = writeln!(out, "{}return {};", indent, expr);
74 }
75 CSharpStmt::Break => {
76 let _ = writeln!(out, "{}break;", indent);
77 }
78 CSharpStmt::Continue => {
79 let _ = writeln!(out, "{}continue;", indent);
80 }
81 CSharpStmt::YieldBreak => {
82 let _ = writeln!(out, "{}yield break;", indent);
83 }
84 CSharpStmt::YieldReturn(expr) => {
85 let _ = writeln!(out, "{}yield return {};", indent, expr);
86 }
87 CSharpStmt::Throw(expr) => {
88 let _ = writeln!(out, "{}throw {};", indent, expr);
89 }
90 CSharpStmt::If {
91 cond,
92 then_stmts,
93 else_stmts,
94 } => {
95 let _ = writeln!(out, "{}if ({})", indent, cond);
96 let _ = writeln!(out, "{}{{", indent);
97 emit_stmts(then_stmts, &inner, out);
98 let _ = writeln!(out, "{}}}", indent);
99 if !else_stmts.is_empty() {
100 let _ = writeln!(out, "{}else", indent);
101 let _ = writeln!(out, "{}{{", indent);
102 emit_stmts(else_stmts, &inner, out);
103 let _ = writeln!(out, "{}}}", indent);
104 }
105 }
106 CSharpStmt::Switch {
107 expr,
108 cases,
109 default,
110 } => {
111 let _ = writeln!(out, "{}switch ({})", indent, expr);
112 let _ = writeln!(out, "{}{{", indent);
113 for case in cases {
114 let _ = writeln!(out, "{} case {}:", indent, case.label);
115 emit_stmts(&case.stmts, &format!("{} ", indent), out);
116 }
117 if !default.is_empty() {
118 let _ = writeln!(out, "{} default:", indent);
119 emit_stmts(default, &format!("{} ", indent), out);
120 }
121 let _ = writeln!(out, "{}}}", indent);
122 }
123 CSharpStmt::While { cond, body } => {
124 let _ = writeln!(out, "{}while ({})", indent, cond);
125 let _ = writeln!(out, "{}{{", indent);
126 emit_stmts(body, &inner, out);
127 let _ = writeln!(out, "{}}}", indent);
128 }
129 CSharpStmt::For {
130 init,
131 cond,
132 step,
133 body,
134 } => {
135 let init_str = match init {
136 None => std::string::String::new(),
137 Some(s) => stmt_to_inline_str(s),
138 };
139 let cond_str = cond.as_ref().map(|c| format!("{}", c)).unwrap_or_default();
140 let step_str = step.as_ref().map(|s| format!("{}", s)).unwrap_or_default();
141 let _ = writeln!(
142 out,
143 "{}for ({}; {}; {})",
144 indent, init_str, cond_str, step_str
145 );
146 let _ = writeln!(out, "{}{{", indent);
147 emit_stmts(body, &inner, out);
148 let _ = writeln!(out, "{}}}", indent);
149 }
150 CSharpStmt::ForEach {
151 var_name,
152 var_ty,
153 collection,
154 body,
155 } => {
156 let ty_str = var_ty
157 .as_ref()
158 .map(|t| format!("{} ", t))
159 .unwrap_or_else(|| "var ".to_string());
160 let _ = writeln!(
161 out,
162 "{}foreach ({}{} in {})",
163 indent, ty_str, var_name, collection
164 );
165 let _ = writeln!(out, "{}{{", indent);
166 emit_stmts(body, &inner, out);
167 let _ = writeln!(out, "{}}}", indent);
168 }
169 CSharpStmt::TryCatch {
170 try_stmts,
171 catches,
172 finally_stmts,
173 } => {
174 let _ = writeln!(out, "{}try", indent);
175 let _ = writeln!(out, "{}{{", indent);
176 emit_stmts(try_stmts, &inner, out);
177 let _ = writeln!(out, "{}}}", indent);
178 for catch in catches {
179 let _ = writeln!(
180 out,
181 "{}catch ({} {})",
182 indent, catch.exception_type, catch.var_name
183 );
184 let _ = writeln!(out, "{}{{", indent);
185 emit_stmts(&catch.stmts, &inner, out);
186 let _ = writeln!(out, "{}}}", indent);
187 }
188 if !finally_stmts.is_empty() {
189 let _ = writeln!(out, "{}finally", indent);
190 let _ = writeln!(out, "{}{{", indent);
191 emit_stmts(finally_stmts, &inner, out);
192 let _ = writeln!(out, "{}}}", indent);
193 }
194 }
195 CSharpStmt::Using {
196 resource,
197 var_name,
198 body,
199 } => {
200 if body.is_empty() {
201 if let Some(name) = var_name {
202 let _ = writeln!(out, "{}using var {} = {};", indent, name, resource);
203 } else {
204 let _ = writeln!(out, "{}using ({});", indent, resource);
205 }
206 } else {
207 let _ = writeln!(out, "{}using ({})", indent, resource);
208 let _ = writeln!(out, "{}{{", indent);
209 emit_stmts(body, &inner, out);
210 let _ = writeln!(out, "{}}}", indent);
211 }
212 }
213 CSharpStmt::Lock { obj, body } => {
214 let _ = writeln!(out, "{}lock ({})", indent, obj);
215 let _ = writeln!(out, "{}{{", indent);
216 emit_stmts(body, &inner, out);
217 let _ = writeln!(out, "{}}}", indent);
218 }
219 }
220}
221pub(super) fn stmt_to_inline_str(stmt: &CSharpStmt) -> std::string::String {
223 match stmt {
224 CSharpStmt::LocalVar {
225 name,
226 ty,
227 init,
228 is_const,
229 } => {
230 let kw = if *is_const { "const" } else { "var" };
231 if let (Some(t), Some(v)) = (ty, init) {
232 format!("{} {} {} = {}", kw, t, name, v)
233 } else if let (None, Some(v)) = (ty, init) {
234 format!("{} {} = {}", kw, name, v)
235 } else {
236 format!("{} {}", kw, name)
237 }
238 }
239 CSharpStmt::Assign { target, value } => format!("{} = {}", target, value),
240 _ => std::string::String::new(),
241 }
242}
243pub const CSHARP_KEYWORDS: &[&str] = &[
245 "abstract",
246 "add",
247 "alias",
248 "as",
249 "ascending",
250 "async",
251 "await",
252 "base",
253 "bool",
254 "break",
255 "by",
256 "byte",
257 "case",
258 "catch",
259 "char",
260 "checked",
261 "class",
262 "const",
263 "continue",
264 "decimal",
265 "default",
266 "delegate",
267 "descending",
268 "do",
269 "double",
270 "dynamic",
271 "else",
272 "enum",
273 "equals",
274 "event",
275 "explicit",
276 "extern",
277 "false",
278 "finally",
279 "fixed",
280 "float",
281 "for",
282 "foreach",
283 "from",
284 "get",
285 "global",
286 "goto",
287 "group",
288 "if",
289 "implicit",
290 "in",
291 "init",
292 "int",
293 "interface",
294 "internal",
295 "into",
296 "is",
297 "join",
298 "let",
299 "lock",
300 "long",
301 "managed",
302 "nameof",
303 "namespace",
304 "new",
305 "notnull",
306 "null",
307 "object",
308 "on",
309 "operator",
310 "orderby",
311 "out",
312 "override",
313 "params",
314 "partial",
315 "private",
316 "protected",
317 "public",
318 "readonly",
319 "record",
320 "ref",
321 "remove",
322 "required",
323 "return",
324 "sbyte",
325 "sealed",
326 "select",
327 "set",
328 "short",
329 "sizeof",
330 "stackalloc",
331 "static",
332 "string",
333 "struct",
334 "switch",
335 "this",
336 "throw",
337 "true",
338 "try",
339 "typeof",
340 "uint",
341 "ulong",
342 "unchecked",
343 "unmanaged",
344 "unsafe",
345 "ushort",
346 "using",
347 "value",
348 "var",
349 "virtual",
350 "void",
351 "volatile",
352 "when",
353 "where",
354 "while",
355 "with",
356 "yield",
357];
358pub fn is_csharp_keyword(s: &str) -> bool {
360 CSHARP_KEYWORDS.contains(&s)
361}
362pub const CSHARP_RUNTIME: &str = r#"
364/// <summary>OxiLean C# Runtime helpers.</summary>
365internal static class OxiLeanRt
366{
367 /// <summary>Called when pattern matching reaches an unreachable branch.</summary>
368 public static T Unreachable<T>() =>
369 throw new InvalidOperationException("OxiLean: unreachable code reached");
370
371 /// <summary>Natural number addition (long arithmetic).</summary>
372 public static long NatAdd(long a, long b) => a + b;
373
374 /// <summary>Natural number subtraction (saturating at 0).</summary>
375 public static long NatSub(long a, long b) => Math.Max(0L, a - b);
376
377 /// <summary>Natural number multiplication.</summary>
378 public static long NatMul(long a, long b) => a * b;
379
380 /// <summary>Natural number division (truncating, 0 if divisor is 0).</summary>
381 public static long NatDiv(long a, long b) => b == 0L ? 0L : a / b;
382
383 /// <summary>Natural number modulo.</summary>
384 public static long NatMod(long a, long b) => b == 0L ? a : a % b;
385
386 /// <summary>Boolean to nat (decidable equality).</summary>
387 public static long Decide(bool b) => b ? 1L : 0L;
388
389 /// <summary>String representation of a natural number.</summary>
390 public static string NatToString(long n) => n.ToString();
391
392 /// <summary>String append.</summary>
393 public static string StrAppend(string a, string b) => a + b;
394
395 /// <summary>List.cons — prepend element to list.</summary>
396 public static List<A> Cons<A>(A head, List<A> tail)
397 {
398 var result = new List<A> { head };
399 result.AddRange(tail);
400 return result;
401 }
402
403 /// <summary>List.nil — empty list.</summary>
404 public static List<A> Nil<A>() => new List<A>();
405
406 /// <summary>Option.some.</summary>
407 public static A? Some<A>(A value) where A : class => value;
408
409 /// <summary>Option.none.</summary>
410 public static A? None<A>() where A : class => null;
411}
412"#;
413#[cfg(test)]
414mod tests {
415 use super::*;
416 #[test]
417 pub(super) fn test_type_primitives() {
418 assert_eq!(CSharpType::Int.to_string(), "int");
419 assert_eq!(CSharpType::Long.to_string(), "long");
420 assert_eq!(CSharpType::Double.to_string(), "double");
421 assert_eq!(CSharpType::Float.to_string(), "float");
422 assert_eq!(CSharpType::Bool.to_string(), "bool");
423 assert_eq!(CSharpType::String.to_string(), "string");
424 assert_eq!(CSharpType::Void.to_string(), "void");
425 assert_eq!(CSharpType::Object.to_string(), "object");
426 }
427 #[test]
428 pub(super) fn test_type_list() {
429 let ty = CSharpType::List(Box::new(CSharpType::Int));
430 assert_eq!(ty.to_string(), "List<int>");
431 }
432 #[test]
433 pub(super) fn test_type_dict() {
434 let ty = CSharpType::Dict(Box::new(CSharpType::String), Box::new(CSharpType::Int));
435 assert_eq!(ty.to_string(), "Dictionary<string, int>");
436 }
437 #[test]
438 pub(super) fn test_type_tuple() {
439 let ty = CSharpType::Tuple(vec![CSharpType::Int, CSharpType::String]);
440 assert_eq!(ty.to_string(), "(int, string)");
441 }
442 #[test]
443 pub(super) fn test_type_nullable() {
444 let ty = CSharpType::Nullable(Box::new(CSharpType::String));
445 assert_eq!(ty.to_string(), "string?");
446 }
447 #[test]
448 pub(super) fn test_type_task_void() {
449 let ty = CSharpType::Task(Box::new(CSharpType::Void));
450 assert_eq!(ty.to_string(), "Task");
451 }
452 #[test]
453 pub(super) fn test_type_task_int() {
454 let ty = CSharpType::Task(Box::new(CSharpType::Int));
455 assert_eq!(ty.to_string(), "Task<int>");
456 }
457 #[test]
458 pub(super) fn test_type_custom() {
459 let ty = CSharpType::Custom("MyClass".to_string());
460 assert_eq!(ty.to_string(), "MyClass");
461 }
462 #[test]
463 pub(super) fn test_type_ienumerable() {
464 let ty = CSharpType::IEnumerable(Box::new(CSharpType::Long));
465 assert_eq!(ty.to_string(), "IEnumerable<long>");
466 }
467 #[test]
468 pub(super) fn test_type_func() {
469 let ty = CSharpType::Func(
470 vec![CSharpType::Int, CSharpType::Int],
471 Box::new(CSharpType::Bool),
472 );
473 assert_eq!(ty.to_string(), "Func<int, int, bool>");
474 }
475 #[test]
476 pub(super) fn test_type_action_empty() {
477 let ty = CSharpType::Action(vec![]);
478 assert_eq!(ty.to_string(), "Action");
479 }
480 #[test]
481 pub(super) fn test_lit_int() {
482 assert_eq!(CSharpLit::Int(42).to_string(), "42");
483 assert_eq!(CSharpLit::Int(-7).to_string(), "-7");
484 }
485 #[test]
486 pub(super) fn test_lit_long() {
487 assert_eq!(CSharpLit::Long(100).to_string(), "100L");
488 }
489 #[test]
490 pub(super) fn test_lit_bool() {
491 assert_eq!(CSharpLit::Bool(true).to_string(), "true");
492 assert_eq!(CSharpLit::Bool(false).to_string(), "false");
493 }
494 #[test]
495 pub(super) fn test_lit_null() {
496 assert_eq!(CSharpLit::Null.to_string(), "null");
497 }
498 #[test]
499 pub(super) fn test_lit_str_basic() {
500 assert_eq!(CSharpLit::Str("hello".to_string()).to_string(), "\"hello\"");
501 }
502 #[test]
503 pub(super) fn test_lit_str_escapes() {
504 let s = CSharpLit::Str("hi\n\"world\"\\".to_string());
505 let result = s.to_string();
506 assert!(result.contains("\\n"));
507 assert!(result.contains("\\\""));
508 assert!(result.contains("\\\\"));
509 }
510 #[test]
511 pub(super) fn test_lit_double() {
512 assert_eq!(CSharpLit::Double(3.14).to_string(), "3.14");
513 assert_eq!(CSharpLit::Double(2.0).to_string(), "2.0");
514 }
515 #[test]
516 pub(super) fn test_lit_float() {
517 assert_eq!(CSharpLit::Float(1.0).to_string(), "1.0f");
518 }
519 #[test]
520 pub(super) fn test_expr_var() {
521 let e = CSharpExpr::Var("myVar".to_string());
522 assert_eq!(e.to_string(), "myVar");
523 }
524 #[test]
525 pub(super) fn test_expr_binop() {
526 let e = CSharpExpr::BinOp {
527 op: "+".to_string(),
528 lhs: Box::new(CSharpExpr::Lit(CSharpLit::Int(1))),
529 rhs: Box::new(CSharpExpr::Lit(CSharpLit::Int(2))),
530 };
531 assert_eq!(e.to_string(), "(1 + 2)");
532 }
533 #[test]
534 pub(super) fn test_expr_call() {
535 let e = CSharpExpr::Call {
536 callee: Box::new(CSharpExpr::Var("Foo".to_string())),
537 args: vec![
538 CSharpExpr::Lit(CSharpLit::Int(1)),
539 CSharpExpr::Lit(CSharpLit::Int(2)),
540 ],
541 };
542 assert_eq!(e.to_string(), "Foo(1, 2)");
543 }
544 #[test]
545 pub(super) fn test_expr_method_call_linq() {
546 let e = CSharpExpr::MethodCall {
547 receiver: Box::new(CSharpExpr::Var("list".to_string())),
548 method: "Where".to_string(),
549 type_args: vec![],
550 args: vec![CSharpExpr::Lambda {
551 params: vec![("x".to_string(), None)],
552 body: Box::new(CSharpExpr::BinOp {
553 op: ">".to_string(),
554 lhs: Box::new(CSharpExpr::Var("x".to_string())),
555 rhs: Box::new(CSharpExpr::Lit(CSharpLit::Int(0))),
556 }),
557 }],
558 };
559 assert!(e.to_string().contains("list.Where("));
560 assert!(e.to_string().contains("x => (x > 0)"));
561 }
562 #[test]
563 pub(super) fn test_expr_new() {
564 let e = CSharpExpr::New {
565 ty: CSharpType::Custom("MyClass".to_string()),
566 args: vec![CSharpExpr::Lit(CSharpLit::Int(42))],
567 };
568 assert_eq!(e.to_string(), "new MyClass(42)");
569 }
570 #[test]
571 pub(super) fn test_expr_lambda_single_param() {
572 let e = CSharpExpr::Lambda {
573 params: vec![("x".to_string(), None)],
574 body: Box::new(CSharpExpr::BinOp {
575 op: "*".to_string(),
576 lhs: Box::new(CSharpExpr::Var("x".to_string())),
577 rhs: Box::new(CSharpExpr::Lit(CSharpLit::Int(2))),
578 }),
579 };
580 assert_eq!(e.to_string(), "x => (x * 2)");
581 }
582 #[test]
583 pub(super) fn test_expr_lambda_multi_param() {
584 let e = CSharpExpr::Lambda {
585 params: vec![
586 ("x".to_string(), Some(CSharpType::Int)),
587 ("y".to_string(), Some(CSharpType::Int)),
588 ],
589 body: Box::new(CSharpExpr::BinOp {
590 op: "+".to_string(),
591 lhs: Box::new(CSharpExpr::Var("x".to_string())),
592 rhs: Box::new(CSharpExpr::Var("y".to_string())),
593 }),
594 };
595 assert!(e.to_string().contains("(int x, int y)"));
596 assert!(e.to_string().contains("=> (x + y)"));
597 }
598 #[test]
599 pub(super) fn test_expr_ternary() {
600 let e = CSharpExpr::Ternary {
601 cond: Box::new(CSharpExpr::Lit(CSharpLit::Bool(true))),
602 then_expr: Box::new(CSharpExpr::Lit(CSharpLit::Int(1))),
603 else_expr: Box::new(CSharpExpr::Lit(CSharpLit::Int(0))),
604 };
605 assert_eq!(e.to_string(), "(true ? 1 : 0)");
606 }
607 #[test]
608 pub(super) fn test_expr_await() {
609 let e = CSharpExpr::Await(Box::new(CSharpExpr::Call {
610 callee: Box::new(CSharpExpr::Var("FetchAsync".to_string())),
611 args: vec![],
612 }));
613 assert_eq!(e.to_string(), "await FetchAsync()");
614 }
615 #[test]
616 pub(super) fn test_expr_is_pattern() {
617 let e = CSharpExpr::Is {
618 expr: Box::new(CSharpExpr::Var("obj".to_string())),
619 pattern: "string s".to_string(),
620 };
621 assert_eq!(e.to_string(), "(obj is string s)");
622 }
623 #[test]
624 pub(super) fn test_expr_as_cast() {
625 let e = CSharpExpr::As {
626 expr: Box::new(CSharpExpr::Var("obj".to_string())),
627 ty: CSharpType::Custom("MyClass".to_string()),
628 };
629 assert_eq!(e.to_string(), "(obj as MyClass)");
630 }
631 #[test]
632 pub(super) fn test_expr_switch_expression() {
633 let e = CSharpExpr::SwitchExpr {
634 scrutinee: Box::new(CSharpExpr::Var("x".to_string())),
635 arms: vec![
636 CSharpSwitchArm {
637 pattern: "1".to_string(),
638 guard: None,
639 body: CSharpExpr::Lit(CSharpLit::Str("one".to_string())),
640 },
641 CSharpSwitchArm {
642 pattern: "_".to_string(),
643 guard: None,
644 body: CSharpExpr::Lit(CSharpLit::Str("other".to_string())),
645 },
646 ],
647 };
648 let out = e.to_string();
649 assert!(out.contains("x switch"));
650 assert!(out.contains("1 =>"));
651 assert!(out.contains("_ =>"));
652 }
653 #[test]
654 pub(super) fn test_expr_nameof_typeof() {
655 let e1 = CSharpExpr::NameOf("myProp".to_string());
656 let e2 = CSharpExpr::TypeOf(CSharpType::Custom("MyClass".to_string()));
657 assert_eq!(e1.to_string(), "nameof(myProp)");
658 assert_eq!(e2.to_string(), "typeof(MyClass)");
659 }
660 #[test]
661 pub(super) fn test_expr_collection() {
662 let e = CSharpExpr::CollectionExpr(vec![
663 CSharpExpr::Lit(CSharpLit::Int(1)),
664 CSharpExpr::Lit(CSharpLit::Int(2)),
665 CSharpExpr::Lit(CSharpLit::Int(3)),
666 ]);
667 assert_eq!(e.to_string(), "[1, 2, 3]");
668 }
669 #[test]
670 pub(super) fn test_expr_default() {
671 let e1 = CSharpExpr::Default(None);
672 let e2 = CSharpExpr::Default(Some(CSharpType::Int));
673 assert_eq!(e1.to_string(), "default");
674 assert_eq!(e2.to_string(), "default(int)");
675 }
676 #[test]
677 pub(super) fn test_record_simple() {
678 let mut r = CSharpRecord::new("Point");
679 r.fields.push(("X".to_string(), CSharpType::Int));
680 r.fields.push(("Y".to_string(), CSharpType::Int));
681 let out = r.emit("");
682 assert!(
683 out.contains("public record Point(int X, int Y)"),
684 "got: {}",
685 out
686 );
687 }
688 #[test]
689 pub(super) fn test_record_sealed() {
690 let mut r = CSharpRecord::new("Token");
691 r.is_sealed = true;
692 r.fields.push(("Value".to_string(), CSharpType::String));
693 let out = r.emit("");
694 assert!(
695 out.contains("public sealed record Token(string Value)"),
696 "got: {}",
697 out
698 );
699 }
700 #[test]
701 pub(super) fn test_record_readonly_struct() {
702 let mut r = CSharpRecord::new("Vec2");
703 r.is_readonly = true;
704 r.fields.push(("X".to_string(), CSharpType::Double));
705 r.fields.push(("Y".to_string(), CSharpType::Double));
706 let out = r.emit("");
707 assert!(
708 out.contains("record struct Vec2(double X, double Y)"),
709 "got: {}",
710 out
711 );
712 }
713 #[test]
714 pub(super) fn test_record_with_methods() {
715 let mut r = CSharpRecord::new("Person");
716 r.fields.push(("Name".to_string(), CSharpType::String));
717 let mut m = CSharpMethod::new("Greet", CSharpType::String);
718 m.expr_body = Some(CSharpExpr::Interpolated(vec![
719 CSharpInterpolationPart::Text("Hello, ".to_string()),
720 CSharpInterpolationPart::Expr(CSharpExpr::Var("Name".to_string())),
721 ]));
722 r.methods.push(m);
723 let out = r.emit("");
724 assert!(
725 out.contains("public record Person(string Name)"),
726 "got: {}",
727 out
728 );
729 assert!(out.contains("Greet"), "got: {}", out);
730 }
731 #[test]
732 pub(super) fn test_interface_basic() {
733 let mut iface = CSharpInterface::new("IFoo");
734 let mut m = CSharpMethod::new("Bar", CSharpType::Int);
735 m.is_abstract = true;
736 iface.methods.push(m);
737 let out = iface.emit("");
738 assert!(out.contains("public interface IFoo"), "got: {}", out);
739 assert!(out.contains("public int Bar()"), "got: {}", out);
740 }
741 #[test]
742 pub(super) fn test_interface_with_type_params() {
743 let mut iface = CSharpInterface::new("IRepository");
744 iface.type_params.push("T".to_string());
745 let out = iface.emit("");
746 assert!(
747 out.contains("public interface IRepository<T>"),
748 "got: {}",
749 out
750 );
751 }
752 #[test]
753 pub(super) fn test_class_basic() {
754 let cls = CSharpClass::new("Foo");
755 let out = cls.emit("");
756 assert!(out.contains("public class Foo"), "got: {}", out);
757 assert!(out.contains("{"));
758 assert!(out.contains("}"));
759 }
760 #[test]
761 pub(super) fn test_class_abstract() {
762 let mut cls = CSharpClass::new("Base");
763 cls.is_abstract = true;
764 let out = cls.emit("");
765 assert!(out.contains("public abstract class Base"), "got: {}", out);
766 }
767 #[test]
768 pub(super) fn test_class_sealed() {
769 let mut cls = CSharpClass::new("Final");
770 cls.is_sealed = true;
771 let out = cls.emit("");
772 assert!(out.contains("public sealed class Final"), "got: {}", out);
773 }
774 #[test]
775 pub(super) fn test_class_with_base_and_interfaces() {
776 let mut cls = CSharpClass::new("Dog");
777 cls.base_class = Some("Animal".to_string());
778 cls.interfaces.push("IComparable".to_string());
779 let out = cls.emit("");
780 assert!(
781 out.contains("class Dog : Animal, IComparable"),
782 "got: {}",
783 out
784 );
785 }
786 #[test]
787 pub(super) fn test_class_with_method() {
788 let mut cls = CSharpClass::new("Calculator");
789 let mut m = CSharpMethod::new("Add", CSharpType::Int);
790 m.params.push(("a".to_string(), CSharpType::Int));
791 m.params.push(("b".to_string(), CSharpType::Int));
792 m.body.push(CSharpStmt::Return(Some(CSharpExpr::BinOp {
793 op: "+".to_string(),
794 lhs: Box::new(CSharpExpr::Var("a".to_string())),
795 rhs: Box::new(CSharpExpr::Var("b".to_string())),
796 })));
797 cls.methods.push(m);
798 let out = cls.emit("");
799 assert!(out.contains("public int Add(int a, int b)"), "got: {}", out);
800 assert!(out.contains("return (a + b)"), "got: {}", out);
801 }
802 #[test]
803 pub(super) fn test_class_async_method() {
804 let mut cls = CSharpClass::new("Fetcher");
805 let mut m = CSharpMethod::new("FetchAsync", CSharpType::Task(Box::new(CSharpType::String)));
806 m.is_async = true;
807 m.body
808 .push(CSharpStmt::Return(Some(CSharpExpr::Await(Box::new(
809 CSharpExpr::Call {
810 callee: Box::new(CSharpExpr::Var("httpClient.GetStringAsync".to_string())),
811 args: vec![CSharpExpr::Lit(CSharpLit::Str(
812 "https://example.com".to_string(),
813 ))],
814 },
815 )))));
816 cls.methods.push(m);
817 let out = cls.emit("");
818 assert!(
819 out.contains("public async Task<string> FetchAsync()"),
820 "got: {}",
821 out
822 );
823 assert!(out.contains("await"), "got: {}", out);
824 }
825 #[test]
826 pub(super) fn test_module_namespace() {
827 let m = CSharpModule::new("OxiLean.Generated");
828 let out = m.emit();
829 assert!(out.contains("namespace OxiLean.Generated;"), "got: {}", out);
830 }
831 #[test]
832 pub(super) fn test_module_nullable_enable() {
833 let m = CSharpModule::new("Test");
834 let out = m.emit();
835 assert!(out.contains("#nullable enable"), "got: {}", out);
836 }
837 #[test]
838 pub(super) fn test_module_using_dedup() {
839 let mut m = CSharpModule::new("Test");
840 m.add_using("System");
841 m.add_using("System");
842 m.add_using("System.Linq");
843 let out = m.emit();
844 assert_eq!(out.matches("using System;").count(), 1, "got: {}", out);
845 assert!(out.contains("using System.Linq;"), "got: {}", out);
846 }
847 #[test]
848 pub(super) fn test_module_contains_runtime() {
849 let m = CSharpModule::new("Test");
850 let out = m.emit();
851 assert!(out.contains("OxiLeanRt"), "got: {}", out);
852 assert!(out.contains("NatAdd"), "got: {}", out);
853 assert!(out.contains("NatSub"), "got: {}", out);
854 assert!(out.contains("Cons"), "got: {}", out);
855 }
856 #[test]
857 pub(super) fn test_mangle_name_keywords() {
858 for kw in &["int", "class", "namespace", "return", "void", "var"] {
859 let result = CSharpBackend::mangle_name(kw);
860 assert!(
861 result.starts_with("ox_"),
862 "keyword '{}' should be prefixed, got '{}'",
863 kw,
864 result
865 );
866 }
867 }
868 #[test]
869 pub(super) fn test_mangle_name_digit_prefix() {
870 assert_eq!(CSharpBackend::mangle_name("0abc"), "ox_0abc");
871 }
872 #[test]
873 pub(super) fn test_mangle_name_empty() {
874 assert_eq!(CSharpBackend::mangle_name(""), "ox_empty");
875 }
876 #[test]
877 pub(super) fn test_mangle_name_special_chars() {
878 assert_eq!(CSharpBackend::mangle_name("foo-bar"), "foo_bar");
879 assert_eq!(CSharpBackend::mangle_name("a.b.c"), "a_b_c");
880 }
881 #[test]
882 pub(super) fn test_mangle_name_valid() {
883 assert_eq!(CSharpBackend::mangle_name("myFunc"), "myFunc");
884 assert_eq!(CSharpBackend::mangle_name("_private"), "_private");
885 }
886 #[test]
887 pub(super) fn test_fresh_var() {
888 let mut backend = CSharpBackend::new();
889 assert_eq!(backend.fresh_var(), "_cs0");
890 assert_eq!(backend.fresh_var(), "_cs1");
891 assert_eq!(backend.fresh_var(), "_cs2");
892 }
893 #[test]
894 pub(super) fn test_compile_decl_simple() {
895 let decl = LcnfFunDecl {
896 name: "myFn".to_string(),
897 original_name: None,
898 params: vec![LcnfParam {
899 id: LcnfVarId(0),
900 name: "x".to_string(),
901 ty: LcnfType::Nat,
902 erased: false,
903 borrowed: false,
904 }],
905 body: LcnfExpr::Return(LcnfArg::Var(LcnfVarId(0))),
906 ret_type: LcnfType::Nat,
907 is_recursive: false,
908 is_lifted: false,
909 inline_cost: 0,
910 };
911 let backend = CSharpBackend::new();
912 let method = backend.compile_decl(&decl);
913 assert_eq!(method.name, "myFn");
914 assert_eq!(method.params.len(), 1);
915 let out = method.emit("");
916 assert!(
917 out.contains("public static long myFn(long _x0)"),
918 "got: {}",
919 out
920 );
921 assert!(out.contains("return _x0"), "got: {}", out);
922 }
923 #[test]
924 pub(super) fn test_compile_decl_string_return() {
925 let decl = LcnfFunDecl {
926 name: "greeting".to_string(),
927 original_name: None,
928 params: vec![],
929 body: LcnfExpr::Return(LcnfArg::Lit(LcnfLit::Str("hello".to_string()))),
930 ret_type: LcnfType::LcnfString,
931 is_recursive: false,
932 is_lifted: false,
933 inline_cost: 0,
934 };
935 let backend = CSharpBackend::new();
936 let method = backend.compile_decl(&decl);
937 let out = method.emit("");
938 assert!(
939 out.contains("public static string greeting()"),
940 "got: {}",
941 out
942 );
943 assert!(out.contains("return \"hello\""), "got: {}", out);
944 }
945 #[test]
946 pub(super) fn test_emit_module_empty() {
947 let backend = CSharpBackend::new();
948 let module = backend.emit_module("OxiLean.Test", &[]);
949 let out = module.emit();
950 assert!(
951 out.contains("OxiLean-generated C# module: OxiLean.Test"),
952 "got: {}",
953 out
954 );
955 assert!(out.contains("namespace OxiLean.Test;"), "got: {}", out);
956 assert!(out.contains("using System;"), "got: {}", out);
957 }
958 #[test]
959 pub(super) fn test_backend_default() {
960 let b = CSharpBackend::default();
961 assert!(b.emit_public);
962 assert!(b.emit_comments);
963 }
964 #[test]
965 pub(super) fn test_runtime_nat_ops() {
966 assert!(CSHARP_RUNTIME.contains("NatAdd"));
967 assert!(CSHARP_RUNTIME.contains("NatSub"));
968 assert!(CSHARP_RUNTIME.contains("NatMul"));
969 assert!(CSHARP_RUNTIME.contains("NatDiv"));
970 assert!(CSHARP_RUNTIME.contains("NatMod"));
971 }
972 #[test]
973 pub(super) fn test_runtime_list_ops() {
974 assert!(CSHARP_RUNTIME.contains("Cons"));
975 assert!(CSHARP_RUNTIME.contains("Nil"));
976 }
977 #[test]
978 pub(super) fn test_enum_basic() {
979 let mut e = CSharpEnum::new("Color");
980 e.variants.push(("Red".to_string(), None));
981 e.variants.push(("Green".to_string(), Some(10)));
982 e.variants.push(("Blue".to_string(), None));
983 let out = e.emit("");
984 assert!(out.contains("public enum Color"), "got: {}", out);
985 assert!(out.contains("Red,"), "got: {}", out);
986 assert!(out.contains("Green = 10,"), "got: {}", out);
987 assert!(out.contains("Blue,"), "got: {}", out);
988 }
989 #[test]
990 pub(super) fn test_enum_with_underlying_type() {
991 let mut e = CSharpEnum::new("Flags");
992 e.underlying_type = Some(CSharpType::Int);
993 e.variants.push(("None".to_string(), Some(0)));
994 e.variants.push(("Read".to_string(), Some(1)));
995 e.variants.push(("Write".to_string(), Some(2)));
996 let out = e.emit("");
997 assert!(out.contains("public enum Flags : int"), "got: {}", out);
998 }
999 #[test]
1000 pub(super) fn test_property_auto_readwrite() {
1001 let p = CSharpProperty::new_auto("Name", CSharpType::String);
1002 let out = p.emit(" ");
1003 assert!(
1004 out.contains("public string Name { get; set; }"),
1005 "got: {}",
1006 out
1007 );
1008 }
1009 #[test]
1010 pub(super) fn test_property_expr_body() {
1011 let mut p = CSharpProperty::new_auto("Count", CSharpType::Int);
1012 p.has_setter = false;
1013 p.expr_body = Some(CSharpExpr::Lit(CSharpLit::Int(42)));
1014 let out = p.emit(" ");
1015 assert!(out.contains("public int Count => 42"), "got: {}", out);
1016 }
1017 #[test]
1018 pub(super) fn test_property_init_only() {
1019 let mut p = CSharpProperty::new_auto("Id", CSharpType::Long);
1020 p.is_init_only = true;
1021 let out = p.emit(" ");
1022 assert!(out.contains("{ get; init; }"), "got: {}", out);
1023 }
1024 #[test]
1025 pub(super) fn test_is_keyword_true() {
1026 assert!(is_csharp_keyword("int"));
1027 assert!(is_csharp_keyword("class"));
1028 assert!(is_csharp_keyword("namespace"));
1029 assert!(is_csharp_keyword("async"));
1030 assert!(is_csharp_keyword("await"));
1031 assert!(is_csharp_keyword("record"));
1032 assert!(is_csharp_keyword("var"));
1033 assert!(is_csharp_keyword("yield"));
1034 }
1035 #[test]
1036 pub(super) fn test_is_keyword_false() {
1037 assert!(!is_csharp_keyword("myFunc"));
1038 assert!(!is_csharp_keyword("oxilean"));
1039 assert!(!is_csharp_keyword("Foo"));
1040 }
1041}