blots_core/
ast_to_source.rs

1use crate::ast::{BinaryOp, DoStatement, Expr, PostfixOp, RecordEntry, RecordKey, UnaryOp};
2use crate::values::LambdaArg;
3
4pub fn expr_to_source(expr: &Expr) -> String {
5    match expr {
6        Expr::Number(n) => {
7            if n.fract() == 0.0 && n.abs() < 1e15 {
8                format!("{:.0}", n)
9            } else {
10                n.to_string()
11            }
12        }
13        Expr::String(s) => format!("\"{}\"", s.replace("\\", "\\\\").replace("\"", "\\\"")),
14        Expr::Bool(b) => b.to_string(),
15        Expr::Null => "null".to_string(),
16        Expr::Identifier(name) => name.clone(),
17        Expr::BuiltIn(built_in) => built_in.name().to_string(),
18        Expr::List(items) => {
19            let items_str: Vec<String> = items.iter().map(expr_to_source).collect();
20            format!("[{}]", items_str.join(", "))
21        }
22        Expr::Record(entries) => {
23            let entries_str: Vec<String> = entries.iter().map(record_entry_to_source).collect();
24            format!("{{{}}}", entries_str.join(", "))
25        }
26        Expr::Lambda { args, body } => {
27            let args_str: Vec<String> = args.iter().map(lambda_arg_to_source).collect();
28            format!("({}) => {}", args_str.join(", "), expr_to_source(body))
29        }
30        Expr::Conditional {
31            condition,
32            then_expr,
33            else_expr,
34        } => format!(
35            "if {} then {} else {}",
36            expr_to_source(condition),
37            expr_to_source(then_expr),
38            expr_to_source(else_expr)
39        ),
40        Expr::DoBlock {
41            statements,
42            return_expr,
43        } => {
44            let mut result = "do {".to_string();
45            for stmt in statements {
46                match stmt {
47                    DoStatement::Expression(e) => {
48                        result.push_str(&format!("\n  {}", expr_to_source(e)));
49                    }
50                    DoStatement::Comment(c) => {
51                        result.push_str(&format!("\n  {}", c));
52                    }
53                }
54            }
55            result.push_str(&format!("\n  return {}\n}}", expr_to_source(return_expr)));
56            result
57        }
58        Expr::Assignment { ident, value } => format!("{} = {}", ident, expr_to_source(value)),
59        Expr::Call { func, args } => {
60            let args_str: Vec<String> = args.iter().map(expr_to_source).collect();
61            format!("{}({})", expr_to_source(func), args_str.join(", "))
62        }
63        Expr::Access { expr, index } => {
64            format!("{}[{}]", expr_to_source(expr), expr_to_source(index))
65        }
66        Expr::DotAccess { expr, field } => format!("{}.{}", expr_to_source(expr), field),
67        Expr::BinaryOp { op, left, right } => {
68            let op_str = binary_op_to_source(op);
69            format!(
70                "{} {} {}",
71                expr_to_source(left),
72                op_str,
73                expr_to_source(right)
74            )
75        }
76        Expr::UnaryOp { op, expr } => {
77            let op_str = unary_op_to_source(op);
78            format!("{}{}", op_str, expr_to_source(expr))
79        }
80        Expr::PostfixOp { op, expr } => {
81            let op_str = postfix_op_to_source(op);
82            format!("{}{}", expr_to_source(expr), op_str)
83        }
84        Expr::Spread(expr) => format!("...{}", expr_to_source(expr)),
85    }
86}
87
88fn lambda_arg_to_source(arg: &LambdaArg) -> String {
89    match arg {
90        LambdaArg::Required(name) => name.clone(),
91        LambdaArg::Optional(name) => format!("{}?", name),
92        LambdaArg::Rest(name) => format!("...{}", name),
93    }
94}
95
96fn record_entry_to_source(entry: &RecordEntry) -> String {
97    match &entry.key {
98        RecordKey::Static(key) => format!("{}: {}", key, expr_to_source(&entry.value)),
99        RecordKey::Dynamic(key_expr) => {
100            format!(
101                "[{}]: {}",
102                expr_to_source(key_expr),
103                expr_to_source(&entry.value)
104            )
105        }
106        RecordKey::Shorthand(name) => name.clone(),
107        RecordKey::Spread(expr) => expr_to_source(expr),
108    }
109}
110
111fn binary_op_to_source(op: &BinaryOp) -> &'static str {
112    match op {
113        BinaryOp::Add => "+",
114        BinaryOp::Subtract => "-",
115        BinaryOp::Multiply => "*",
116        BinaryOp::Divide => "/",
117        BinaryOp::Modulo => "%",
118        BinaryOp::Power => "^",
119        BinaryOp::Equal => "==",
120        BinaryOp::NotEqual => "!=",
121        BinaryOp::Less => "<",
122        BinaryOp::LessEq => "<=",
123        BinaryOp::Greater => ">",
124        BinaryOp::GreaterEq => ">=",
125        BinaryOp::And => "&&",
126        BinaryOp::NaturalAnd => "and",
127        BinaryOp::Or => "||",
128        BinaryOp::NaturalOr => "or",
129        BinaryOp::Via => "via",
130        BinaryOp::Into => "into",
131        BinaryOp::Coalesce => "??",
132    }
133}
134
135fn unary_op_to_source(op: &UnaryOp) -> &'static str {
136    match op {
137        UnaryOp::Negate => "-",
138        UnaryOp::Not => "!",
139        UnaryOp::Invert => "~",
140    }
141}
142
143fn postfix_op_to_source(op: &PostfixOp) -> &'static str {
144    match op {
145        PostfixOp::Factorial => "!",
146    }
147}