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