Skip to main content

oxilean_codegen/dart_backend/
functions.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use 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
16/// Map an LCNF type to a Dart type.
17pub(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}
54/// Emit a DartField as a Dart source string (without indentation).
55pub 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}
130/// Build a simple OxiLean constructor class.
131pub(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];
232/// Minimal Dart runtime class emitted at the top of every generated file.
233pub 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/// Emit a standalone Dart function that wraps a value in a `Future`.
419#[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/// Emit a `print(expr);` statement.
427#[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/// Build a `List.generate(n, (i) => expr)` expression.
436#[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/// Emit a simple `assert(condition, message)` statement.
448#[allow(dead_code)]
449pub fn dart_assert(cond: DartExpr, msg: &str) -> DartStmt {
450    let _ = msg;
451    DartStmt::Assert(cond)
452}
453/// Build a `Map.fromEntries(entries)` expression.
454#[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}