Skip to main content

alegen/
lib.rs

1use aleph_syntax_tree::syntax::AlephTree as at;
2use aleph_syntax_tree::types::{Type, Variant};
3use aleph_syntax_tree::effects::Effect;
4
5/// Prints a `Type` as Aleph-Next surface syntax (`Int`, `List<Int>`,
6/// `(Int, Bool) -> String`, `{x: Float}`, `A | B(Int)`, ...).
7fn gen_type(ty: &Type) -> String {
8    match ty {
9        Type::Int => "Int".to_string(),
10        Type::Float => "Float".to_string(),
11        Type::Bool => "Bool".to_string(),
12        Type::String => "String".to_string(),
13        Type::Bytes => "Bytes".to_string(),
14        Type::Unit => "Unit".to_string(),
15        Type::Var{name} => name.clone(),
16        Type::Fun{params, ret} => format!(
17            "({}) -> {}",
18            params.iter().map(gen_type).collect::<Vec<String>>().join(", "),
19            gen_type(ret)
20        ),
21        Type::List{elem} => format!("List<{}>", gen_type(elem)),
22        Type::Tuple{elems} => format!(
23            "({})",
24            elems.iter().map(gen_type).collect::<Vec<String>>().join(", ")
25        ),
26        Type::Record{fields} => format!(
27            "{{{}}}",
28            fields.iter()
29                .map(|f| format!("{}: {}", f.name, gen_type(&f.ty)))
30                .collect::<Vec<String>>()
31                .join(", ")
32        ),
33        Type::Sum{variants} => variants.iter().map(gen_variant).collect::<Vec<String>>().join(" | "),
34    }
35}
36
37/// Prints one `Variant` of a sum type — `Name` if it has no fields,
38/// `Name(Type, Type, ...)` otherwise.
39fn gen_variant(v: &Variant) -> String {
40    if v.fields.is_empty() {
41        v.name.clone()
42    } else {
43        format!(
44            "{}({})",
45            v.name,
46            v.fields.iter().map(gen_type).collect::<Vec<String>>().join(", ")
47        )
48    }
49}
50
51/// Prints a `LetRec`'s params using the corresponding `Type::Fun`'s
52/// declared `params` list as the source of truth for each type (not each
53/// arg's own possibly-absent `Typed` wrapping) — `name: Type` for a
54/// position with a declared type, bare `name` past the end of `params`
55/// (a malformed/partial signature; this degrades rather than panicking).
56fn gen_params(args: Vec<Box<at>>, param_types: &[Type]) -> String {
57    args.into_iter().enumerate().map(|(i, a)| {
58        let name = match *a {
59            at::Typed{inner, ..} => gen(*inner, 0),
60            other => gen(other, 0),
61        };
62        match param_types.get(i) {
63            Some(ty) => format!("{}: {}", name, gen_type(ty)),
64            None => name,
65        }
66    }).collect::<Vec<String>>().join(", ")
67}
68
69/// Maps an `Effect` to its lowercase surface-syntax keyword (`pure`,
70/// `io`, `net`, `mut`, `act`).
71fn gen_effect(e: &Effect) -> &'static str {
72    match e {
73        Effect::Pure => "pure",
74        Effect::Io => "io",
75        Effect::Net => "net",
76        Effect::Mut => "mut",
77        Effect::Act => "act",
78    }
79}
80
81fn gen(ast: at, indent: i64) -> String {
82    let c_indent=aleph_syntax_tree::comp_indent(indent);
83    match ast {
84        at::Unit => format!("{}", ""),
85        at::Ellipsis => format!("{}", ""),
86        at::Int{value} => format!("{}{}", c_indent, value),
87        at::Float{value} => format!("{}{}", c_indent, value),
88        at::Bool{value} => format!("{}{}", c_indent, value),
89        at::String{value} => format!("{}{}", c_indent, value),
90        at::Ident{value} => format!("{}{}", c_indent, value),
91        at::Complex{real, imag} => format!("{}{} + ({} *j)", c_indent, real, imag),
92        at::Bytes{elems} => format!("{}", String::from_utf8(elems).expect("Found invalid UTF-8")),
93        at::Tuple{elems} => format!("{}", aleph_syntax_tree::gen_list_expr_sep(elems, gen, ", ")),
94        at::Array{elems} => format!("[{}]", aleph_syntax_tree::gen_list_expr_sep(elems, gen, ", ")),
95        at::Neg{expr} => format!("{}-{}", c_indent, gen(*expr, 0)),
96        at::Not{bool_expr} => format!("{}!({})", c_indent, gen(*bool_expr, 0)),
97        at::And{bool_expr1, bool_expr2} => format!("{}{} & {}", c_indent, gen(*bool_expr1, 0), gen(*bool_expr2, 0)),
98        at::Or{bool_expr1, bool_expr2} => format!("{}{} | {}", c_indent, gen(*bool_expr1, 0), gen(*bool_expr2, 0)),
99        at::Add{number_expr1, number_expr2} => format!("{}{} + {}", c_indent, gen(*number_expr1, 0), gen(*number_expr2, 0)),
100        at::Sub{number_expr1, number_expr2} => format!("{}{} - {}", c_indent, gen(*number_expr1, 0), gen(*number_expr2, 0)),
101        at::Mul{number_expr1, number_expr2} => format!("{}{} * {}", c_indent, gen(*number_expr1, 0), gen(*number_expr2, 0)),
102        at::Div{number_expr1, number_expr2} => format!("{}{} / {}", c_indent, gen(*number_expr1, 0), gen(*number_expr2, 0)),
103        at::Eq{expr1, expr2} => format!("{}{} = {}", c_indent, gen(*expr1, 0), gen(*expr2, 0)),
104        at::LE{expr1, expr2} => format!("{}{} <= {}", c_indent, gen(*expr1, 0), gen(*expr2, 0)),
105        at::In{expr1, expr2} => format!("{}{} in {}", c_indent, gen(*expr1, 0), gen(*expr2, 0)),
106        at::If{condition, then,els} => match *els {
107            at::Unit => format!("{c_indent}({cond})?{{\n{then}\n{c_indent}}}", c_indent=c_indent, cond=gen(*condition, 0), then=gen(*then, indent+1)),
108            _ => format!("{c_indent}({cond})?{{\n{then}\n{c_indent}}}:{{\n{els}\n{c_indent}}}", c_indent=c_indent, cond=gen(*condition, 0), then=gen(*then, indent+1), els=gen(*els, indent+1)),
109        },
110        at::While{init_expr, condition, loop_expr, post_expr} => {
111            format!("{}\n{}({})?*{{\n{}\n{}\n{}}}", gen(*init_expr, indent), c_indent, gen(*condition, 0), gen(*loop_expr, indent+1), gen(*post_expr, indent+1), c_indent)
112        },
113        at::Let{var, is_pointer, value, expr} => match *expr {
114            at::Unit{} => format!("{}{}{} = {};", c_indent, var, (if is_pointer=="true" {":"} else {""}), gen(*value, 0)),
115            _ => format!("{}{}{} = {};\n{}", c_indent, var, (if is_pointer=="true" {":"} else {""}), gen(*value, 0), gen(*expr, indent)),
116        },
117        at::LetRec{name, args, body} => format!("{}fun {}({}) = {{\n{}\n{}}}", c_indent, name, aleph_syntax_tree::gen_list_expr_sep(args, gen, ", "), gen(*body, indent+1), c_indent),
118        at::Get{array_name, elem} => format!("{}{}[{}]", c_indent, array_name, gen(*elem, 0)),
119        at::Put{array_name, elem, value, insert} => format!("{}{}[{}{}] = {}", c_indent, array_name, (if insert=="true" {"+"} else {""}), gen(*elem, 0), gen(*value, 0)),
120        at::Remove{array_name, elem, is_value} => format!("{}{}[{}{}]", c_indent, array_name, (if is_value=="true" {"-"} else {"/"}), gen(*elem, 0)),
121        at::Length{var} => format!("{}|{}|", c_indent, var),
122        at::Match{expr, case_list} => format!("{}match {} with\n{}", c_indent, gen(*expr, 0), aleph_syntax_tree::gen_list_expr(case_list, gen)),
123        at::MatchLine{condition, case_expr} => format!("{}: {} -> {}\n", c_indent, gen(*condition, 0), gen(*case_expr, 0)),
124        at::Var{var, is_pointer} => format!("{}{}{}",c_indent, (if is_pointer=="true" {"!"} else {""}), var),
125        at::App{object_name, fun, param_list} => format!("{}{}{}({})",c_indent, (if object_name.ne("") {format!("{}.", object_name)} else {String::from("")}), gen(*fun, 0), aleph_syntax_tree::gen_list_expr_sep(param_list, gen, ", ")),
126        at::Stmts{expr1, expr2} => format!("{};\n{}", gen(*expr1, indent), gen(*expr2, indent)), 
127        at::Iprt{name, ..} => format!("{}import {}", c_indent, name),
128        at::Clss{name, attribute_list, body, ..} => format!("{}class {} {{\n{}{};\n{}\n}}", c_indent, name, aleph_syntax_tree::comp_indent(indent+1), attribute_list.join(&format!(";\n{}", aleph_syntax_tree::comp_indent(indent+1))), gen(*body, indent+1)), 
129        at::Return{value} => format!("return {}", gen(*value, 0)),
130        at::Comment{value} => format!("{}{}", c_indent, value),
131        at::CommentMulti{value} => format!("{}{}", c_indent, value),
132        // ── Cognitive Layer ───────────────────────────────────────────────
133        at::Intend{name, params, body} => format!(
134            "{}intention {}({}) = {{\n{}\n{}}}",
135            c_indent, name,
136            aleph_syntax_tree::gen_list_expr_sep(params, gen, ", "),
137            gen(*body, indent+1),
138            c_indent
139        ),
140        at::Suggest{var, context, options, ..} => {
141            if options.is_empty() {
142                format!("{}suggest {} from {}", c_indent, var, gen(*context, 0))
143            } else {
144                format!("{}suggest {} from {} : [{}]", c_indent, var, gen(*context, 0),
145                    aleph_syntax_tree::gen_list_expr_sep(options, gen, ", "))
146            }
147        },
148        at::Act{intention, effect} => match effect {
149            None      => format!("{}act {}", c_indent, gen(*intention, 0)),
150            Some(eff) => format!("{}act {} as {}", c_indent, gen(*intention, 0), gen(*eff, 0)),
151        },
152        at::Remember{key, value, ttl} => match ttl {
153            None    => format!("{}remember {} = {}", c_indent, gen(*key, 0), gen(*value, 0)),
154            Some(t) => format!("{}remember {} = {} for {}", c_indent, gen(*key, 0), gen(*value, 0), gen(*t, 0)),
155        },
156        at::Perceive{source, var, ..} => match var {
157            None    => format!("{}perceive {}", c_indent, gen(*source, 0)),
158            Some(v) => format!("{}perceive {} as {}", c_indent, gen(*source, 0), v),
159        },
160        at::TypeDef{name, variants} => format!(
161            "{}type {} = {}",
162            c_indent, name, variants.iter().map(gen_variant).collect::<Vec<String>>().join(" | ")
163        ),
164        // The fallback branch below prints a single-line `(expr: Type)` and
165        // is not layout-aware — a multi-line `inner` (If/While/Match/Stmts)
166        // prints correctly but not prettily. Not reachable from this
167        // project's own parser (which only ever produces `Typed` wrapping a
168        // function `LetRec` or a bare parameter `Ident`, the latter never
169        // routed through this arm — see `gen_params`); kept only so a
170        // hand-built tree gets defined output instead of a panic.
171        at::Typed{inner, ty} => match (*inner, ty) {
172            (at::LetRec{name, args, body}, Type::Fun{params, ret}) => format!(
173                "{}fun {}({}) -> {} = {{\n{}\n{}}}",
174                c_indent, name, gen_params(args, &params), gen_type(&ret), gen(*body, indent+1), c_indent
175            ),
176            (fallback_inner, fallback_ty) => format!("{}({}: {})", c_indent, gen(fallback_inner, 0), gen_type(&fallback_ty)),
177        },
178        // Two fallback branches below (inner isn't Typed{LetRec,Fun}; or
179        // inner is Typed but around something other than a Fun-typed
180        // LetRec): both print as `<gen(inner)> | effects`, using `indent`
181        // (not 0) for the inner `gen` call — unlike Typed's own fallback,
182        // this format string doesn't prepend c_indent itself, so the
183        // indentation has to come from the recursive call to stay aligned
184        // when WithEffects is nested (not top-level).
185        at::WithEffects{inner, effects} => {
186            let effs_str = effects.iter().map(gen_effect).collect::<Vec<&str>>().join(", ");
187            match *inner {
188                at::Typed{inner: fn_inner, ty: Type::Fun{params, ret}} => match *fn_inner {
189                    at::LetRec{name, args, body} => format!(
190                        "{}fun {}({}) -> {} | {} = {{\n{}\n{}}}",
191                        c_indent, name, gen_params(args, &params), gen_type(&ret), effs_str, gen(*body, indent+1), c_indent
192                    ),
193                    other => format!("{} | {}", gen(other, indent), effs_str),
194                },
195                other => format!("{} | {}", gen(other, indent), effs_str),
196            }
197        },
198        _ => todo!()
199    }
200}
201
202pub fn generate(ast: at) -> String {
203    gen(ast, 0)
204}
205
206#[cfg(test)]
207mod tests {
208    use super::*;
209    use aleph_syntax_tree::effects::EffectSet;
210
211    #[test]
212    fn prints_a_sum_type_declaration() {
213        let node = at::TypeDef {
214            name: "Shape".to_string(),
215            variants: vec![
216                Variant { name: "Circle".to_string(), fields: vec![Type::Float] },
217                Variant { name: "Rect".to_string(), fields: vec![Type::Float, Type::Float] },
218            ],
219        };
220        assert_eq!(generate(node), "type Shape = Circle(Float) | Rect(Float, Float)");
221    }
222
223    #[test]
224    fn prints_a_zero_field_variant_without_parens() {
225        let node = at::TypeDef {
226            name: "Bool2".to_string(),
227            variants: vec![
228                Variant { name: "T".to_string(), fields: vec![] },
229                Variant { name: "F".to_string(), fields: vec![] },
230            ],
231        };
232        assert_eq!(generate(node), "type Bool2 = T | F");
233    }
234
235    #[test]
236    fn gen_type_prints_primitives() {
237        assert_eq!(gen_type(&Type::Int), "Int");
238        assert_eq!(gen_type(&Type::Float), "Float");
239        assert_eq!(gen_type(&Type::Bool), "Bool");
240        assert_eq!(gen_type(&Type::String), "String");
241        assert_eq!(gen_type(&Type::Bytes), "Bytes");
242        assert_eq!(gen_type(&Type::Unit), "Unit");
243    }
244
245    #[test]
246    fn gen_type_prints_a_var_reference() {
247        assert_eq!(gen_type(&Type::Var { name: "Shape".to_string() }), "Shape");
248    }
249
250    #[test]
251    fn gen_type_prints_a_function_type() {
252        let ty = Type::Fun {
253            params: vec![Type::Int, Type::Bool],
254            ret: Box::new(Type::String),
255        };
256        assert_eq!(gen_type(&ty), "(Int, Bool) -> String");
257    }
258
259    #[test]
260    fn gen_type_prints_a_list_type() {
261        let ty = Type::List { elem: Box::new(Type::Int) };
262        assert_eq!(gen_type(&ty), "List<Int>");
263    }
264
265    #[test]
266    fn gen_type_prints_a_tuple_type() {
267        let ty = Type::Tuple { elems: vec![Type::Int, Type::String] };
268        assert_eq!(gen_type(&ty), "(Int, String)");
269    }
270
271    #[test]
272    fn gen_type_prints_a_record_type() {
273        use aleph_syntax_tree::types::RecordField;
274        let ty = Type::Record {
275            fields: vec![RecordField { name: "x".to_string(), ty: Type::Float }],
276        };
277        assert_eq!(gen_type(&ty), "{x: Float}");
278    }
279
280    #[test]
281    fn prints_a_typed_zero_arg_function() {
282        let node = at::Typed {
283            inner: Box::new(at::LetRec {
284                name: "hello".to_string(),
285                args: Vec::new(),
286                body: Box::new(at::String { value: "\"hi\"".to_string() }),
287            }),
288            ty: Type::Fun { params: Vec::new(), ret: Box::new(Type::String) },
289        };
290        assert_eq!(generate(node), "fun hello() -> String = {\n    \"hi\"\n}");
291    }
292
293    #[test]
294    fn prints_a_typed_function_with_typed_params() {
295        let node = at::Typed {
296            inner: Box::new(at::LetRec {
297                name: "square".to_string(),
298                args: vec![Box::new(at::Typed {
299                    inner: Box::new(at::Ident { value: "n".to_string() }),
300                    ty: Type::Int,
301                })],
302                body: Box::new(at::Mul {
303                    number_expr1: Box::new(at::Ident { value: "n".to_string() }),
304                    number_expr2: Box::new(at::Ident { value: "n".to_string() }),
305                }),
306            }),
307            ty: Type::Fun { params: vec![Type::Int], ret: Box::new(Type::Int) },
308        };
309        assert_eq!(generate(node), "fun square(n: Int) -> Int = {\n    n * n\n}");
310    }
311
312    #[test]
313    fn prints_a_bare_typed_value_generically() {
314        let node = at::Typed {
315            inner: Box::new(at::Ident { value: "n".to_string() }),
316            ty: Type::Int,
317        };
318        assert_eq!(generate(node), "(n: Int)");
319    }
320
321    #[test]
322    fn prints_a_typed_function_with_multiple_params() {
323        let node = at::Typed {
324            inner: Box::new(at::LetRec {
325                name: "add".to_string(),
326                args: vec![
327                    Box::new(at::Typed { inner: Box::new(at::Ident { value: "a".to_string() }), ty: Type::Int }),
328                    Box::new(at::Typed { inner: Box::new(at::Ident { value: "b".to_string() }), ty: Type::Int }),
329                ],
330                body: Box::new(at::Add {
331                    number_expr1: Box::new(at::Ident { value: "a".to_string() }),
332                    number_expr2: Box::new(at::Ident { value: "b".to_string() }),
333                }),
334            }),
335            ty: Type::Fun { params: vec![Type::Int, Type::Int], ret: Box::new(Type::Int) },
336        };
337        assert_eq!(generate(node), "fun add(a: Int, b: Int) -> Int = {\n    a + b\n}");
338    }
339
340    #[test]
341    fn params_list_shorter_than_args_degrades_to_bare_names_past_the_end() {
342        // Fun.params has only 1 entry but there are 2 args — a malformed
343        // signature. This documents the degrade behavior from Fix 1: print
344        // whatever type IS declared per position, bare name beyond that,
345        // rather than silently trusting each arg's own (possibly absent)
346        // Typed wrapping.
347        let node = at::Typed {
348            inner: Box::new(at::LetRec {
349                name: "f".to_string(),
350                args: vec![
351                    Box::new(at::Typed { inner: Box::new(at::Ident { value: "n".to_string() }), ty: Type::Int }),
352                    Box::new(at::Ident { value: "m".to_string() }),
353                ],
354                body: Box::new(at::Ident { value: "n".to_string() }),
355            }),
356            ty: Type::Fun { params: vec![Type::Int], ret: Box::new(Type::Int) },
357        };
358        assert_eq!(generate(node), "fun f(n: Int, m) -> Int = {\n    n\n}");
359    }
360
361    #[test]
362    fn generic_fallback_handles_a_multiline_inner_without_panicking() {
363        let node = at::Typed {
364            inner: Box::new(at::If {
365                condition: Box::new(at::Bool { value: "true".to_string() }),
366                then: Box::new(at::Int { value: "1".to_string() }),
367                els: Box::new(at::Int { value: "2".to_string() }),
368            }),
369            ty: Type::Int,
370        };
371        // This is deliberately documenting current (not necessarily
372        // "pretty") behavior per Fix 2 — run this once, read the actual
373        // output, and use that exact string here rather than guessing.
374        assert_eq!(generate(node), "((true)?{\n    1\n}:{\n    2\n}: Int)");
375    }
376
377    #[test]
378    fn out_of_range_arg_with_its_own_type_annotation_is_still_printed_bare() {
379        // Distinguishes "sourced from Fun.params" from "accidentally still
380        // trusting the arg's own Typed wrapping": this arg (`m`) HAS its
381        // own annotation (String), but Fun.params only declares 1 entry —
382        // gen_params must ignore m's own `ty` and print it bare, not `m: String`.
383        let node = at::Typed {
384            inner: Box::new(at::LetRec {
385                name: "f".to_string(),
386                args: vec![
387                    Box::new(at::Typed { inner: Box::new(at::Ident { value: "n".to_string() }), ty: Type::Int }),
388                    Box::new(at::Typed { inner: Box::new(at::Ident { value: "m".to_string() }), ty: Type::String }),
389                ],
390                body: Box::new(at::Ident { value: "n".to_string() }),
391            }),
392            ty: Type::Fun { params: vec![Type::Int], ret: Box::new(Type::Int) },
393        };
394        assert_eq!(generate(node), "fun f(n: Int, m) -> Int = {\n    n\n}");
395    }
396
397    #[test]
398    fn prints_a_typed_function_with_one_effect() {
399        let node = at::WithEffects {
400            inner: Box::new(at::Typed {
401                inner: Box::new(at::LetRec {
402                    name: "square".to_string(),
403                    args: vec![Box::new(at::Typed {
404                        inner: Box::new(at::Ident { value: "n".to_string() }),
405                        ty: Type::Int,
406                    })],
407                    body: Box::new(at::Mul {
408                        number_expr1: Box::new(at::Ident { value: "n".to_string() }),
409                        number_expr2: Box::new(at::Ident { value: "n".to_string() }),
410                    }),
411                }),
412                ty: Type::Fun { params: vec![Type::Int], ret: Box::new(Type::Int) },
413            }),
414            effects: EffectSet::from([Effect::Pure]),
415        };
416        assert_eq!(generate(node), "fun square(n: Int) -> Int | pure = {\n    n * n\n}");
417    }
418
419    #[test]
420    fn prints_multiple_effects_comma_separated_in_declaration_order() {
421        let node = at::WithEffects {
422            inner: Box::new(at::Typed {
423                inner: Box::new(at::LetRec {
424                    name: "main".to_string(),
425                    args: Vec::new(),
426                    body: Box::new(at::Unit),
427                }),
428                ty: Type::Fun { params: Vec::new(), ret: Box::new(Type::Unit) },
429            }),
430            effects: EffectSet::from([Effect::Net, Effect::Io]),
431        };
432        // EffectSet is a BTreeSet, so iteration order follows Effect's
433        // declared enum order (Pure, Io, Net, Mut, Act), not insertion
434        // order — Io before Net regardless of how the set was built.
435        // Note: AlephTree::Unit's gen() arm (`format!("{}", "")`) ignores
436        // indentation and always prints as an empty string — so the body
437        // line here is genuinely blank, not 4 spaces of indent, unlike
438        // every other body-having node.
439        assert_eq!(generate(node), "fun main() -> Unit | io, net = {\n\n}");
440    }
441
442    #[test]
443    fn effects_on_a_non_function_typed_value_fall_back_generically() {
444        let node = at::WithEffects {
445            inner: Box::new(at::Typed {
446                inner: Box::new(at::Ident { value: "n".to_string() }),
447                ty: Type::Int,
448            }),
449            effects: EffectSet::from([Effect::Mut]),
450        };
451        assert_eq!(generate(node), "(n: Int) | mut");
452    }
453
454    #[test]
455    fn effects_on_a_non_typed_value_fall_back_generically() {
456        let node = at::WithEffects {
457            inner: Box::new(at::Ident { value: "n".to_string() }),
458            effects: EffectSet::from([Effect::Mut]),
459        };
460        assert_eq!(generate(node), "n | mut");
461    }
462}