blots_core/
ast_to_source.rs

1use crate::ast::{BinaryOp, DoStatement, Expr, PostfixOp, RecordEntry, RecordKey, UnaryOp};
2use crate::values::{LambdaArg, SerializableValue};
3use indexmap::IndexMap;
4
5pub fn expr_to_source(expr: &Expr) -> String {
6    match expr {
7        Expr::Number(n) => {
8            if n.fract() == 0.0 && n.abs() < 1e15 {
9                format!("{:.0}", n)
10            } else {
11                n.to_string()
12            }
13        }
14        Expr::String(s) => format!("\"{}\"", s.replace("\\", "\\\\").replace("\"", "\\\"")),
15        Expr::Bool(b) => b.to_string(),
16        Expr::Null => "null".to_string(),
17        Expr::Identifier(name) => name.clone(),
18        Expr::InputReference(field) => format!("#{}", field),
19        Expr::BuiltIn(built_in) => built_in.name().to_string(),
20        Expr::List(items) => {
21            let items_str: Vec<String> = items.iter().map(expr_to_source).collect();
22            format!("[{}]", items_str.join(", "))
23        }
24        Expr::Record(entries) => {
25            let entries_str: Vec<String> = entries.iter().map(record_entry_to_source).collect();
26            format!("{{{}}}", entries_str.join(", "))
27        }
28        Expr::Lambda { args, body } => {
29            let args_str: Vec<String> = args.iter().map(lambda_arg_to_source).collect();
30            format!("({}) => {}", args_str.join(", "), expr_to_source(body))
31        }
32        Expr::Conditional {
33            condition,
34            then_expr,
35            else_expr,
36        } => format!(
37            "if {} then {} else {}",
38            expr_to_source(condition),
39            expr_to_source(then_expr),
40            expr_to_source(else_expr)
41        ),
42        Expr::DoBlock {
43            statements,
44            return_expr,
45        } => {
46            let mut result = "do {".to_string();
47            for stmt in statements {
48                match stmt {
49                    DoStatement::Expression(e) => {
50                        result.push_str(&format!("\n  {}", expr_to_source(e)));
51                    }
52                    DoStatement::Comment(c) => {
53                        result.push_str(&format!("\n  {}", c));
54                    }
55                }
56            }
57            result.push_str(&format!("\n  return {}\n}}", expr_to_source(return_expr)));
58            result
59        }
60        Expr::Assignment { ident, value } => format!("{} = {}", ident, expr_to_source(value)),
61        Expr::Call { func, args } => {
62            let args_str: Vec<String> = args.iter().map(expr_to_source).collect();
63            format!("{}({})", expr_to_source(func), args_str.join(", "))
64        }
65        Expr::Access { expr, index } => {
66            format!("{}[{}]", expr_to_source(expr), expr_to_source(index))
67        }
68        Expr::DotAccess { expr, field } => format!("{}.{}", expr_to_source(expr), field),
69        Expr::BinaryOp { op, left, right } => {
70            let op_str = binary_op_to_source(op);
71            format!(
72                "{} {} {}",
73                expr_to_source(left),
74                op_str,
75                expr_to_source(right)
76            )
77        }
78        Expr::UnaryOp { op, expr } => {
79            let op_str = unary_op_to_source(op);
80            format!("{}{}", op_str, expr_to_source(expr))
81        }
82        Expr::PostfixOp { op, expr } => {
83            let op_str = postfix_op_to_source(op);
84            format!("{}{}", expr_to_source(expr), op_str)
85        }
86        Expr::Spread(expr) => format!("...{}", expr_to_source(expr)),
87    }
88}
89
90fn lambda_arg_to_source(arg: &LambdaArg) -> String {
91    match arg {
92        LambdaArg::Required(name) => name.clone(),
93        LambdaArg::Optional(name) => format!("{}?", name),
94        LambdaArg::Rest(name) => format!("...{}", name),
95    }
96}
97
98fn record_entry_to_source(entry: &RecordEntry) -> String {
99    match &entry.key {
100        RecordKey::Static(key) => format!("{}: {}", key, expr_to_source(&entry.value)),
101        RecordKey::Dynamic(key_expr) => {
102            format!(
103                "[{}]: {}",
104                expr_to_source(key_expr),
105                expr_to_source(&entry.value)
106            )
107        }
108        RecordKey::Shorthand(name) => name.clone(),
109        RecordKey::Spread(expr) => expr_to_source(expr),
110    }
111}
112
113fn binary_op_to_source(op: &BinaryOp) -> &'static str {
114    match op {
115        BinaryOp::Add => "+",
116        BinaryOp::Subtract => "-",
117        BinaryOp::Multiply => "*",
118        BinaryOp::Divide => "/",
119        BinaryOp::Modulo => "%",
120        BinaryOp::Power => "^",
121        BinaryOp::Equal => "==",
122        BinaryOp::NotEqual => "!=",
123        BinaryOp::Less => "<",
124        BinaryOp::LessEq => "<=",
125        BinaryOp::Greater => ">",
126        BinaryOp::GreaterEq => ">=",
127        BinaryOp::DotEqual => ".==",
128        BinaryOp::DotNotEqual => ".!=",
129        BinaryOp::DotLess => ".<",
130        BinaryOp::DotLessEq => ".<=",
131        BinaryOp::DotGreater => ".>",
132        BinaryOp::DotGreaterEq => ".>=",
133        BinaryOp::And => "&&",
134        BinaryOp::NaturalAnd => "and",
135        BinaryOp::Or => "||",
136        BinaryOp::NaturalOr => "or",
137        BinaryOp::Via => "via",
138        BinaryOp::Into => "into",
139        BinaryOp::Coalesce => "??",
140    }
141}
142
143fn unary_op_to_source(op: &UnaryOp) -> &'static str {
144    match op {
145        UnaryOp::Negate => "-",
146        UnaryOp::Not => "!",
147        UnaryOp::Invert => "~",
148    }
149}
150
151fn postfix_op_to_source(op: &PostfixOp) -> &'static str {
152    match op {
153        PostfixOp::Factorial => "!",
154    }
155}
156
157/// Convert an expression to source code with inlined scope values
158pub fn expr_to_source_with_scope(
159    expr: &Expr,
160    scope: &IndexMap<String, SerializableValue>,
161) -> String {
162    match expr {
163        Expr::Identifier(name) => {
164            // If the identifier is in the scope, inline its value
165            if let Some(value) = scope.get(name) {
166                serializable_value_to_source(value)
167            } else {
168                name.clone()
169            }
170        }
171        Expr::InputReference(field) => format!("#{}", field),
172        // For all other expression types, recursively process with scope
173        Expr::Number(n) => {
174            if n.fract() == 0.0 && n.abs() < 1e15 {
175                format!("{:.0}", n)
176            } else {
177                n.to_string()
178            }
179        }
180        Expr::String(s) => format!("\"{}\"", s.replace("\\", "\\\\").replace("\"", "\\\"")),
181        Expr::Bool(b) => b.to_string(),
182        Expr::Null => "null".to_string(),
183        Expr::BuiltIn(built_in) => built_in.name().to_string(),
184        Expr::List(items) => {
185            let items_str: Vec<String> = items
186                .iter()
187                .map(|e| expr_to_source_with_scope(e, scope))
188                .collect();
189            format!("[{}]", items_str.join(", "))
190        }
191        Expr::Record(entries) => {
192            let entries_str: Vec<String> = entries
193                .iter()
194                .map(|e| record_entry_to_source_with_scope(e, scope))
195                .collect();
196            format!("{{{}}}", entries_str.join(", "))
197        }
198        Expr::Lambda { args, body } => {
199            let args_str: Vec<String> = args.iter().map(lambda_arg_to_source).collect();
200            // Lambda bodies should not inline their own parameters
201            format!(
202                "({}) => {}",
203                args_str.join(", "),
204                expr_to_source_with_scope(body, scope)
205            )
206        }
207        Expr::Conditional {
208            condition,
209            then_expr,
210            else_expr,
211        } => format!(
212            "if {} then {} else {}",
213            expr_to_source_with_scope(condition, scope),
214            expr_to_source_with_scope(then_expr, scope),
215            expr_to_source_with_scope(else_expr, scope)
216        ),
217        Expr::DoBlock {
218            statements,
219            return_expr,
220        } => {
221            let mut result = "do {".to_string();
222            for stmt in statements {
223                match stmt {
224                    DoStatement::Expression(e) => {
225                        result.push_str(&format!("\n  {}", expr_to_source_with_scope(e, scope)));
226                    }
227                    DoStatement::Comment(c) => {
228                        result.push_str(&format!("\n  // {}", c));
229                    }
230                }
231            }
232            result.push_str(&format!(
233                "\n  return {}",
234                expr_to_source_with_scope(return_expr, scope)
235            ));
236            result.push_str("\n}");
237            result
238        }
239        Expr::BinaryOp { op, left, right } => {
240            let op_str = binary_op_to_source(op);
241            format!(
242                "{} {} {}",
243                expr_to_source_with_scope(left, scope),
244                op_str,
245                expr_to_source_with_scope(right, scope)
246            )
247        }
248        Expr::UnaryOp { op, expr } => {
249            let op_str = match op {
250                UnaryOp::Negate => "-",
251                UnaryOp::Not => "!",
252                UnaryOp::Invert => "~",
253            };
254            format!("{}{}", op_str, expr_to_source_with_scope(expr, scope))
255        }
256        Expr::PostfixOp { op, expr } => {
257            let op_str = postfix_op_to_source(op);
258            format!("{}{}", expr_to_source_with_scope(expr, scope), op_str)
259        }
260        Expr::Spread(expr) => format!("...{}", expr_to_source_with_scope(expr, scope)),
261        Expr::Assignment { ident, value } => {
262            format!("{} = {}", ident, expr_to_source_with_scope(value, scope))
263        }
264        Expr::Call { func, args } => {
265            let args_str: Vec<String> = args
266                .iter()
267                .map(|e| expr_to_source_with_scope(e, scope))
268                .collect();
269            format!(
270                "{}({})",
271                expr_to_source_with_scope(func, scope),
272                args_str.join(", ")
273            )
274        }
275        Expr::Access { expr, index } => {
276            format!(
277                "{}[{}]",
278                expr_to_source_with_scope(expr, scope),
279                expr_to_source_with_scope(index, scope)
280            )
281        }
282        Expr::DotAccess { expr, field } => {
283            format!("{}.{}", expr_to_source_with_scope(expr, scope), field)
284        }
285    }
286}
287
288fn record_entry_to_source_with_scope(
289    entry: &RecordEntry,
290    scope: &IndexMap<String, SerializableValue>,
291) -> String {
292    match &entry.key {
293        RecordKey::Static(key) => {
294            format!(
295                "{}: {}",
296                key,
297                expr_to_source_with_scope(&entry.value, scope)
298            )
299        }
300        RecordKey::Dynamic(key_expr) => {
301            format!(
302                "[{}]: {}",
303                expr_to_source_with_scope(key_expr, scope),
304                expr_to_source_with_scope(&entry.value, scope)
305            )
306        }
307        RecordKey::Shorthand(name) => {
308            // For shorthand, check if the value is in scope
309            if let Some(value) = scope.get(name) {
310                format!("{}: {}", name, serializable_value_to_source(value))
311            } else {
312                name.clone()
313            }
314        }
315        RecordKey::Spread(expr) => expr_to_source_with_scope(expr, scope),
316    }
317}
318
319/// Convert a SerializableValue to its source representation
320fn serializable_value_to_source(value: &SerializableValue) -> String {
321    match value {
322        SerializableValue::Number(n) => {
323            if n.fract() == 0.0 && n.abs() < 1e15 {
324                format!("{:.0}", n)
325            } else {
326                n.to_string()
327            }
328        }
329        SerializableValue::Bool(b) => b.to_string(),
330        SerializableValue::Null => "null".to_string(),
331        SerializableValue::String(s) => {
332            format!("\"{}\"", s.replace("\\", "\\\\").replace("\"", "\\\""))
333        }
334        SerializableValue::List(items) => {
335            let items_str: Vec<String> = items.iter().map(serializable_value_to_source).collect();
336            format!("[{}]", items_str.join(", "))
337        }
338        SerializableValue::Record(fields) => {
339            let entries_str: Vec<String> = fields
340                .iter()
341                .map(|(k, v)| format!("{}: {}", k, serializable_value_to_source(v)))
342                .collect();
343            format!("{{{}}}", entries_str.join(", "))
344        }
345        SerializableValue::Lambda(_) => "<function>".to_string(),
346        SerializableValue::BuiltIn(name) => name.clone(),
347    }
348}