1use crate::lcnf::*;
6use std::collections::{HashMap, HashSet};
7
8use super::types::{
9 DartAnnotation, DartBackend, DartClass, DartCodeMetrics, DartEnum, DartEnumVariant, DartExpr,
10 DartExtension, DartField, DartFile, DartFunction, DartImportExt, DartLit, DartMixin,
11 DartNullSafety, DartParam, DartSealedHierarchy, DartStmt, DartStreamBuilder, DartType,
12 DartTypeAlias,
13};
14use std::fmt;
15
16pub(super) fn lcnf_type_to_dart(ty: &LcnfType) -> DartType {
18 match ty {
19 LcnfType::Nat => DartType::DtInt,
20 LcnfType::LcnfString => DartType::DtString,
21 LcnfType::Unit => DartType::DtVoid,
22 LcnfType::Erased | LcnfType::Irrelevant => DartType::DtDynamic,
23 LcnfType::Object => DartType::DtObject,
24 LcnfType::Var(name) => DartType::DtNamed(name.clone()),
25 LcnfType::Fun(params, ret) => {
26 let dart_params: Vec<DartType> = params.iter().map(lcnf_type_to_dart).collect();
27 let dart_ret = lcnf_type_to_dart(ret);
28 DartType::DtFunction(dart_params, Box::new(dart_ret))
29 }
30 LcnfType::Ctor(name, _args) => DartType::DtNamed(name.clone()),
31 }
32}
33pub(super) fn fmt_args(f: &mut fmt::Formatter<'_>, args: &[DartExpr]) -> fmt::Result {
34 for (i, a) in args.iter().enumerate() {
35 if i > 0 {
36 write!(f, ", ")?;
37 }
38 write!(f, "{}", a)?;
39 }
40 Ok(())
41}
42pub(super) fn fmt_typed_params(
43 f: &mut fmt::Formatter<'_>,
44 params: &[(DartType, String)],
45) -> fmt::Result {
46 for (i, (ty, name)) in params.iter().enumerate() {
47 if i > 0 {
48 write!(f, ", ")?;
49 }
50 write!(f, "{} {}", ty, name)?;
51 }
52 Ok(())
53}
54pub fn emit_dart_field(field: &DartField) -> String {
56 let mut modifiers = String::new();
57 if field.is_static {
58 modifiers.push_str("static ");
59 }
60 if field.is_final {
61 modifiers.push_str("final ");
62 }
63 if field.is_late {
64 modifiers.push_str("late ");
65 }
66 if let Some(ref init) = field.default_value {
67 format!("{}{} {} = {};", modifiers, field.ty, field.name, init)
68 } else {
69 format!("{}{} {};", modifiers, field.ty, field.name)
70 }
71}
72pub(super) fn mangle_dart_ident(name: &str, keywords: &HashSet<&'static str>) -> String {
73 let base: String = name
74 .chars()
75 .map(|c| {
76 if c.is_alphanumeric() || c == '_' {
77 c
78 } else {
79 '_'
80 }
81 })
82 .collect();
83 let base = if base.starts_with(|c: char| c.is_ascii_digit()) {
84 format!("_{}", base)
85 } else {
86 base
87 };
88 if keywords.contains(base.as_str()) {
89 format!("{}_", base)
90 } else {
91 base
92 }
93}
94pub(super) fn collect_ctor_names(module: &LcnfModule) -> HashSet<String> {
95 let mut out = HashSet::new();
96 for func in &module.fun_decls {
97 collect_ctor_names_from_expr(&func.body, &mut out);
98 }
99 out
100}
101pub(super) fn collect_ctor_names_from_expr(expr: &LcnfExpr, out: &mut HashSet<String>) {
102 match expr {
103 LcnfExpr::Let { value, body, .. } => {
104 collect_ctor_names_from_value(value, out);
105 collect_ctor_names_from_expr(body, out);
106 }
107 LcnfExpr::Case { alts, default, .. } => {
108 for alt in alts {
109 out.insert(alt.ctor_name.clone());
110 collect_ctor_names_from_expr(&alt.body, out);
111 }
112 if let Some(d) = default {
113 collect_ctor_names_from_expr(d, out);
114 }
115 }
116 LcnfExpr::Return(_) | LcnfExpr::Unreachable | LcnfExpr::TailCall(_, _) => {}
117 }
118}
119pub(super) fn collect_ctor_names_from_value(value: &LcnfLetValue, out: &mut HashSet<String>) {
120 match value {
121 LcnfLetValue::Ctor(name, _, _) => {
122 out.insert(name.clone());
123 }
124 LcnfLetValue::Reuse(_, name, _, _) => {
125 out.insert(name.clone());
126 }
127 _ => {}
128 }
129}
130pub(super) fn make_ctor_class(name: &str) -> DartClass {
132 let mut class = DartClass::new(name);
133 class.fields.push(DartField {
134 ty: DartType::DtInt,
135 name: "tag".to_string(),
136 is_final: true,
137 is_static: false,
138 is_late: false,
139 default_value: None,
140 doc: None,
141 });
142 class.fields.push(DartField {
143 ty: DartType::DtList(Box::new(DartType::DtDynamic)),
144 name: "fields".to_string(),
145 is_final: true,
146 is_static: false,
147 is_late: false,
148 default_value: None,
149 doc: None,
150 });
151 let mut ctor = DartFunction::new("", DartType::DtVoid);
152 ctor.params = vec![
153 DartParam::positional(DartType::DtInt, "tag"),
154 DartParam::positional(DartType::DtList(Box::new(DartType::DtDynamic)), "fields"),
155 ];
156 ctor.body = vec![
157 DartStmt::Raw("this.tag = tag;".to_string()),
158 DartStmt::Raw("this.fields = fields;".to_string()),
159 ];
160 class.constructors.push(ctor);
161 class
162}
163pub static DART_KEYWORDS: &[&str] = &[
164 "abstract",
165 "as",
166 "assert",
167 "async",
168 "await",
169 "base",
170 "break",
171 "case",
172 "catch",
173 "class",
174 "const",
175 "continue",
176 "covariant",
177 "default",
178 "deferred",
179 "do",
180 "dynamic",
181 "else",
182 "enum",
183 "export",
184 "extends",
185 "extension",
186 "external",
187 "factory",
188 "false",
189 "final",
190 "finally",
191 "for",
192 "Function",
193 "get",
194 "hide",
195 "if",
196 "implements",
197 "import",
198 "in",
199 "interface",
200 "is",
201 "late",
202 "library",
203 "mixin",
204 "new",
205 "null",
206 "of",
207 "on",
208 "operator",
209 "part",
210 "required",
211 "rethrow",
212 "return",
213 "sealed",
214 "set",
215 "show",
216 "static",
217 "super",
218 "switch",
219 "sync",
220 "this",
221 "throw",
222 "true",
223 "try",
224 "typedef",
225 "var",
226 "void",
227 "when",
228 "while",
229 "with",
230 "yield",
231];
232pub const DART_RUNTIME: &str = r#"
234class OxiLeanRuntime {
235 /// Called when pattern matching reaches an unreachable branch.
236 static Never unreachable() =>
237 throw StateError('OxiLean: unreachable code reached');
238
239 /// Natural number addition.
240 static int natAdd(int a, int b) => a + b;
241
242 /// Natural number subtraction (saturating at 0).
243 static int natSub(int a, int b) => a - b < 0 ? 0 : a - b;
244
245 /// Natural number multiplication.
246 static int natMul(int a, int b) => a * b;
247
248 /// Natural number division (truncating, 0 if divisor is 0).
249 static int natDiv(int a, int b) => b == 0 ? 0 : a ~/ b;
250
251 /// Natural number modulo.
252 static int natMod(int a, int b) => b == 0 ? a : a % b;
253
254 /// Decide a boolean as a Nat (0 or 1).
255 static int decide(bool b) => b ? 1 : 0;
256
257 /// Convert Nat to String.
258 static String natToString(int n) => n.toString();
259
260 /// String append.
261 static String strAppend(String a, String b) => a + b;
262
263 /// List.cons: prepend element.
264 static List<T> cons<T>(T head, List<T> tail) => [head, ...tail];
265
266 /// List.nil: empty list.
267 static List<T> nil<T>() => [];
268}
269"#;
270#[cfg(test)]
271mod tests {
272 use super::*;
273 #[test]
274 pub(super) fn test_dart_type_display_primitives() {
275 assert_eq!(format!("{}", DartType::DtInt), "int");
276 assert_eq!(format!("{}", DartType::DtDouble), "double");
277 assert_eq!(format!("{}", DartType::DtBool), "bool");
278 assert_eq!(format!("{}", DartType::DtString), "String");
279 assert_eq!(format!("{}", DartType::DtVoid), "void");
280 assert_eq!(format!("{}", DartType::DtDynamic), "dynamic");
281 }
282 #[test]
283 pub(super) fn test_dart_type_display_nullable() {
284 let nullable_int = DartType::DtNullable(Box::new(DartType::DtInt));
285 assert_eq!(format!("{}", nullable_int), "int?");
286 let nullable_str = DartType::DtNullable(Box::new(DartType::DtString));
287 assert_eq!(format!("{}", nullable_str), "String?");
288 }
289 #[test]
290 pub(super) fn test_dart_type_display_generics() {
291 let list_int = DartType::DtList(Box::new(DartType::DtInt));
292 assert_eq!(format!("{}", list_int), "List<int>");
293 let map_str_int = DartType::DtMap(Box::new(DartType::DtString), Box::new(DartType::DtInt));
294 assert_eq!(format!("{}", map_str_int), "Map<String, int>");
295 let future_str = DartType::DtFuture(Box::new(DartType::DtString));
296 assert_eq!(format!("{}", future_str), "Future<String>");
297 }
298 #[test]
299 pub(super) fn test_dart_type_display_function() {
300 let fn_ty = DartType::DtFunction(
301 vec![DartType::DtInt, DartType::DtString],
302 Box::new(DartType::DtBool),
303 );
304 assert_eq!(format!("{}", fn_ty), "bool Function(int, String)");
305 }
306 #[test]
307 pub(super) fn test_dart_lit_display() {
308 assert_eq!(format!("{}", DartLit::Int(42)), "42");
309 assert_eq!(format!("{}", DartLit::Double(3.14)), "3.14");
310 assert_eq!(format!("{}", DartLit::Bool(true)), "true");
311 assert_eq!(format!("{}", DartLit::Bool(false)), "false");
312 assert_eq!(format!("{}", DartLit::Null), "null");
313 assert_eq!(format!("{}", DartLit::Str("hi".to_string())), "'hi'");
314 assert_eq!(format!("{}", DartLit::Str("it's".to_string())), "'it\\'s'");
315 }
316 #[test]
317 pub(super) fn test_dart_expr_display() {
318 let var = DartExpr::Var("x".to_string());
319 assert_eq!(format!("{}", var), "x");
320 let field = DartExpr::Field(
321 Box::new(DartExpr::Var("obj".to_string())),
322 "len".to_string(),
323 );
324 assert_eq!(format!("{}", field), "obj.len");
325 let bin = DartExpr::BinOp(
326 Box::new(DartExpr::Lit(DartLit::Int(1))),
327 "+".to_string(),
328 Box::new(DartExpr::Lit(DartLit::Int(2))),
329 );
330 assert_eq!(format!("{}", bin), "(1 + 2)");
331 let null_coal = DartExpr::NullCoalesce(
332 Box::new(DartExpr::Var("x".to_string())),
333 Box::new(DartExpr::Lit(DartLit::Int(0))),
334 );
335 assert_eq!(format!("{}", null_coal), "(x ?? 0)");
336 }
337 #[test]
338 pub(super) fn test_dart_stmt_emit_if() {
339 let backend = DartBackend::new();
340 let stmt = DartStmt::If(
341 DartExpr::Var("cond".to_string()),
342 vec![DartStmt::Return(Some(DartExpr::Lit(DartLit::Int(1))))],
343 vec![DartStmt::Return(Some(DartExpr::Lit(DartLit::Int(0))))],
344 );
345 let code = backend.emit_stmt(&stmt, 0);
346 assert!(code.contains("if (cond)"));
347 assert!(code.contains("return 1;"));
348 assert!(code.contains("else"));
349 assert!(code.contains("return 0;"));
350 }
351 #[test]
352 pub(super) fn test_dart_stmt_emit_for_in() {
353 let backend = DartBackend::new();
354 let stmt = DartStmt::ForIn(
355 "item".to_string(),
356 DartExpr::Var("list".to_string()),
357 vec![DartStmt::Expr(DartExpr::MethodCall(
358 Box::new(DartExpr::Var("print".to_string())),
359 "call".to_string(),
360 vec![DartExpr::Var("item".to_string())],
361 ))],
362 );
363 let code = backend.emit_stmt(&stmt, 0);
364 assert!(code.contains("for (final item in list)"));
365 }
366 #[test]
367 pub(super) fn test_dart_backend_emit_function() {
368 let backend = DartBackend::new();
369 let mut func = DartFunction::new("add", DartType::DtInt);
370 func.params = vec![
371 DartParam::positional(DartType::DtInt, "a"),
372 DartParam::positional(DartType::DtInt, "b"),
373 ];
374 func.body = vec![DartStmt::Return(Some(DartExpr::BinOp(
375 Box::new(DartExpr::Var("a".to_string())),
376 "+".to_string(),
377 Box::new(DartExpr::Var("b".to_string())),
378 )))];
379 let code = backend.emit_function(&func, 0);
380 assert!(code.contains("int add(int a, int b)"));
381 assert!(code.contains("return (a + b);"));
382 }
383 #[test]
384 pub(super) fn test_dart_backend_emit_class() {
385 let backend = DartBackend::new();
386 let mut class = DartClass::new("Point");
387 class
388 .fields
389 .push(DartField::final_field(DartType::DtDouble, "x"));
390 class
391 .fields
392 .push(DartField::final_field(DartType::DtDouble, "y"));
393 let mut ctor = DartFunction::new("", DartType::DtVoid);
394 ctor.params = vec![
395 DartParam::positional(DartType::DtDouble, "x"),
396 DartParam::positional(DartType::DtDouble, "y"),
397 ];
398 ctor.body = vec![
399 DartStmt::Raw("this.x = x;".to_string()),
400 DartStmt::Raw("this.y = y;".to_string()),
401 ];
402 class.constructors.push(ctor);
403 let code = backend.emit_class(&class, 0);
404 assert!(code.contains("class Point {"));
405 assert!(code.contains("final double x;"));
406 assert!(code.contains("final double y;"));
407 assert!(code.contains("Point(double x, double y)"));
408 }
409 #[test]
410 pub(super) fn test_mangle_dart_ident_keyword() {
411 let mut backend = DartBackend::new();
412 let mangled = backend.mangle_name("class");
413 assert_eq!(mangled, "class_");
414 let mangled2 = backend.mangle_name("myFunc");
415 assert_eq!(mangled2, "myFunc");
416 }
417}
418#[allow(dead_code)]
420pub fn emit_future_value_fn(name: &str, ty: DartType, val: DartExpr) -> DartFunction {
421 let mut f = DartFunction::new(name, DartType::DtFuture(Box::new(ty)));
422 f.is_async = true;
423 f.body = vec![DartStmt::Return(Some(val))];
424 f
425}
426#[allow(dead_code)]
428pub fn dart_print(expr: DartExpr) -> DartStmt {
429 DartStmt::Expr(DartExpr::MethodCall(
430 Box::new(DartExpr::Var("print".to_string())),
431 "call".to_string(),
432 vec![expr],
433 ))
434}
435#[allow(dead_code)]
437pub fn list_generate(n: usize, body: DartExpr) -> DartExpr {
438 DartExpr::MethodCall(
439 Box::new(DartExpr::Var("List".to_string())),
440 "generate".to_string(),
441 vec![
442 DartExpr::Lit(DartLit::Int(n as i64)),
443 DartExpr::Arrow(vec![(DartType::DtInt, "i".to_string())], Box::new(body)),
444 ],
445 )
446}
447#[allow(dead_code)]
449pub fn dart_assert(cond: DartExpr, msg: &str) -> DartStmt {
450 let _ = msg;
451 DartStmt::Assert(cond)
452}
453#[allow(dead_code)]
455pub fn map_from_entries(
456 key_ty: DartType,
457 val_ty: DartType,
458 entries: Vec<(DartExpr, DartExpr)>,
459) -> DartExpr {
460 let _ = (key_ty, val_ty);
461 let entry_exprs: Vec<DartExpr> = entries
462 .into_iter()
463 .map(|(k, v)| DartExpr::New("MapEntry".to_string(), None, vec![k, v]))
464 .collect();
465 DartExpr::MethodCall(
466 Box::new(DartExpr::Var("Map".to_string())),
467 "fromEntries".to_string(),
468 vec![DartExpr::ListLit(entry_exprs)],
469 )
470}
471#[cfg(test)]
472mod tests_extended {
473 use super::*;
474 use crate::dart_backend::*;
475 #[test]
476 pub(super) fn test_type_alias_emit() {
477 let ta = DartTypeAlias::new("StringList", DartType::DtList(Box::new(DartType::DtString)))
478 .with_doc("A list of strings");
479 let out = ta.emit();
480 assert!(out.contains("typedef StringList = List<String>;"));
481 assert!(out.contains("/// A list of strings"));
482 }
483 #[test]
484 pub(super) fn test_import_simple() {
485 let imp = DartImportExt::simple("dart:async");
486 assert_eq!(imp.emit(), "import 'dart:async';\n");
487 }
488 #[test]
489 pub(super) fn test_import_with_prefix() {
490 let imp = DartImportExt::simple("package:http/http.dart").with_prefix("http");
491 assert!(imp.emit().contains("as http"));
492 }
493 #[test]
494 pub(super) fn test_import_show() {
495 let imp = DartImportExt::simple("dart:math")
496 .show_identifiers(vec!["Random".to_string(), "pi".to_string()]);
497 let s = imp.emit();
498 assert!(s.contains("show Random, pi"));
499 }
500 #[test]
501 pub(super) fn test_dart_enum_emit() {
502 let mut e = DartEnum::new("Color");
503 e.add_variant(DartEnumVariant::simple("red"));
504 e.add_variant(DartEnumVariant::simple("green"));
505 e.add_variant(DartEnumVariant::simple("blue"));
506 let out = e.emit();
507 assert!(out.contains("enum Color {"));
508 assert!(out.contains("red,"));
509 assert!(out.contains("blue;"));
510 }
511 #[test]
512 pub(super) fn test_dart_annotation_display() {
513 assert_eq!(DartAnnotation::Override.to_string(), "@override");
514 assert_eq!(DartAnnotation::Deprecated.to_string(), "@deprecated");
515 let custom = DartAnnotation::Custom(
516 "JsonSerializable".into(),
517 vec!["explicitToJson: true".into()],
518 );
519 assert!(custom.to_string().contains("@JsonSerializable"));
520 }
521 #[test]
522 pub(super) fn test_dart_file_emit() {
523 let backend = DartBackend::new();
524 let mut file = DartFile::new().with_library("my_lib");
525 file.add_import(DartImport::simple("dart:core"));
526 let mut cls = DartClass::new("Foo");
527 cls.fields
528 .push(DartField::final_field(DartType::DtInt, "x"));
529 file.add_class(cls);
530 let out = file.emit(&backend);
531 assert!(out.contains("library my_lib;"));
532 assert!(out.contains("import 'dart:core';"));
533 assert!(out.contains("class Foo {"));
534 }
535 #[test]
536 pub(super) fn test_sealed_hierarchy_emit() {
537 let backend = DartBackend::new();
538 let mut hier = DartSealedHierarchy::new("Shape");
539 let mut circle = DartClass::new("Circle");
540 circle.extends = Some("Shape".to_string());
541 circle
542 .fields
543 .push(DartField::final_field(DartType::DtDouble, "radius"));
544 hier.add_variant(circle);
545 let out = hier.emit(&backend);
546 assert!(out.contains("sealed class Shape {"));
547 assert!(out.contains("class Circle extends Shape {"));
548 }
549 #[test]
550 pub(super) fn test_dart_metrics_collect() {
551 let mut file = DartFile::new();
552 file.add_class(DartClass::new("A"));
553 file.add_class(DartClass::new("B"));
554 file.add_function(DartFunction::new("foo", DartType::DtVoid));
555 file.add_import(DartImport::simple("dart:math"));
556 let metrics = DartCodeMetrics::collect(&file);
557 assert_eq!(metrics.class_count, 2);
558 assert_eq!(metrics.function_count, 1);
559 assert_eq!(metrics.import_count, 1);
560 }
561 #[test]
562 pub(super) fn test_list_generate_expr() {
563 let expr = list_generate(5, DartExpr::Var("i".to_string()));
564 let s = format!("{}", expr);
565 assert!(s.contains("List"));
566 assert!(s.contains("generate"));
567 }
568 #[test]
569 pub(super) fn test_stream_from_iterable() {
570 let expr = DartStreamBuilder::from_iterable(vec![
571 DartExpr::Lit(DartLit::Int(1)),
572 DartExpr::Lit(DartLit::Int(2)),
573 ]);
574 let s = format!("{}", expr);
575 assert!(s.contains("fromIterable"));
576 }
577 #[test]
578 pub(super) fn test_null_safety_helpers() {
579 let decl = DartNullSafety::nullable_decl(DartType::DtInt, "count");
580 let backend = DartBackend::new();
581 let out = backend.emit_stmt(&decl, 0);
582 assert!(out.contains("int?"));
583 assert!(out.contains("null"));
584 }
585 #[test]
586 pub(super) fn test_mixin_emit() {
587 let backend = DartBackend::new();
588 let mut mixin = DartMixin::new("Serializable");
589 let mut to_json = DartFunction::new(
590 "toJson",
591 DartType::DtMap(Box::new(DartType::DtString), Box::new(DartType::DtDynamic)),
592 );
593 to_json.body = vec![DartStmt::Return(Some(DartExpr::MapLit(vec![])))];
594 mixin.methods.push(to_json);
595 let out = mixin.emit(&backend, 0);
596 assert!(out.contains("mixin Serializable {"));
597 assert!(out.contains("toJson"));
598 }
599 #[test]
600 pub(super) fn test_extension_emit() {
601 let backend = DartBackend::new();
602 let mut ext = DartExtension::new(DartType::DtString).named("StringExt");
603 let mut is_blank = DartFunction::new("isBlank", DartType::DtBool);
604 is_blank.body = vec![DartStmt::Return(Some(DartExpr::MethodCall(
605 Box::new(DartExpr::Var("this".to_string())),
606 "trim".to_string(),
607 vec![],
608 )))];
609 ext.add_method(is_blank);
610 let out = ext.emit(&backend, 0);
611 assert!(out.contains("extension StringExt on String {"));
612 }
613 #[test]
614 pub(super) fn test_emit_future_value_fn() {
615 let f = emit_future_value_fn(
616 "getAnswer",
617 DartType::DtInt,
618 DartExpr::Lit(DartLit::Int(42)),
619 );
620 assert_eq!(f.name, "getAnswer");
621 assert!(f.is_async);
622 assert!(matches!(f.return_type, DartType::DtFuture(_)));
623 }
624 #[test]
625 pub(super) fn test_stream_listen_stmt() {
626 let stream = DartExpr::Var("myStream".to_string());
627 let stmt = DartStreamBuilder::listen(
628 stream,
629 "event",
630 vec![DartStmt::Expr(DartExpr::Var("event".to_string()))],
631 );
632 let backend = DartBackend::new();
633 let out = backend.emit_stmt(&stmt, 0);
634 assert!(out.contains("listen"));
635 }
636}