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