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::And => "&&",
127        BinaryOp::NaturalAnd => "and",
128        BinaryOp::Or => "||",
129        BinaryOp::NaturalOr => "or",
130        BinaryOp::Via => "via",
131        BinaryOp::Into => "into",
132        BinaryOp::Coalesce => "??",
133    }
134}
135
136fn unary_op_to_source(op: &UnaryOp) -> &'static str {
137    match op {
138        UnaryOp::Negate => "-",
139        UnaryOp::Not => "!",
140        UnaryOp::Invert => "~",
141    }
142}
143
144fn postfix_op_to_source(op: &PostfixOp) -> &'static str {
145    match op {
146        PostfixOp::Factorial => "!",
147    }
148}
149
150/// Convert an expression to source code with inlined scope values
151pub fn expr_to_source_with_scope(
152    expr: &Expr,
153    scope: &IndexMap<String, SerializableValue>,
154) -> String {
155    match expr {
156        Expr::Identifier(name) => {
157            // If the identifier is in the scope, inline its value
158            if let Some(value) = scope.get(name) {
159                serializable_value_to_source(value)
160            } else {
161                name.clone()
162            }
163        }
164        // For all other expression types, recursively process with scope
165        Expr::Number(n) => {
166            if n.fract() == 0.0 && n.abs() < 1e15 {
167                format!("{:.0}", n)
168            } else {
169                n.to_string()
170            }
171        }
172        Expr::String(s) => format!("\"{}\"", s.replace("\\", "\\\\").replace("\"", "\\\"")),
173        Expr::Bool(b) => b.to_string(),
174        Expr::Null => "null".to_string(),
175        Expr::BuiltIn(built_in) => built_in.name().to_string(),
176        Expr::List(items) => {
177            let items_str: Vec<String> = items
178                .iter()
179                .map(|e| expr_to_source_with_scope(e, scope))
180                .collect();
181            format!("[{}]", items_str.join(", "))
182        }
183        Expr::Record(entries) => {
184            let entries_str: Vec<String> = entries
185                .iter()
186                .map(|e| record_entry_to_source_with_scope(e, scope))
187                .collect();
188            format!("{{{}}}", entries_str.join(", "))
189        }
190        Expr::Lambda { args, body } => {
191            let args_str: Vec<String> = args.iter().map(lambda_arg_to_source).collect();
192            // Lambda bodies should not inline their own parameters
193            format!("({}) => {}", args_str.join(", "), expr_to_source_with_scope(body, scope))
194        }
195        Expr::Conditional {
196            condition,
197            then_expr,
198            else_expr,
199        } => format!(
200            "if {} then {} else {}",
201            expr_to_source_with_scope(condition, scope),
202            expr_to_source_with_scope(then_expr, scope),
203            expr_to_source_with_scope(else_expr, scope)
204        ),
205        Expr::DoBlock {
206            statements,
207            return_expr,
208        } => {
209            let mut result = "do {".to_string();
210            for stmt in statements {
211                match stmt {
212                    DoStatement::Expression(e) => {
213                        result.push_str(&format!("\n  {}", expr_to_source_with_scope(e, scope)));
214                    }
215                    DoStatement::Comment(c) => {
216                        result.push_str(&format!("\n  // {}", c));
217                    }
218                }
219            }
220            result.push_str(&format!(
221                "\n  return {}",
222                expr_to_source_with_scope(return_expr, scope)
223            ));
224            result.push_str("\n}");
225            result
226        }
227        Expr::BinaryOp { op, left, right } => {
228            let op_str = binary_op_to_source(op);
229            format!(
230                "{} {} {}",
231                expr_to_source_with_scope(left, scope),
232                op_str,
233                expr_to_source_with_scope(right, scope)
234            )
235        }
236        Expr::UnaryOp { op, expr } => {
237            let op_str = match op {
238                UnaryOp::Negate => "-",
239                UnaryOp::Not => "!",
240                UnaryOp::Invert => "~",
241            };
242            format!("{}{}", op_str, expr_to_source_with_scope(expr, scope))
243        }
244        Expr::PostfixOp { op, expr } => {
245            let op_str = postfix_op_to_source(op);
246            format!("{}{}", expr_to_source_with_scope(expr, scope), op_str)
247        }
248        Expr::Spread(expr) => format!("...{}", expr_to_source_with_scope(expr, scope)),
249        Expr::Assignment { ident, value } => {
250            format!("{} = {}", ident, expr_to_source_with_scope(value, scope))
251        }
252        Expr::Call { func, args } => {
253            let args_str: Vec<String> = args
254                .iter()
255                .map(|e| expr_to_source_with_scope(e, scope))
256                .collect();
257            format!("{}({})", expr_to_source_with_scope(func, scope), args_str.join(", "))
258        }
259        Expr::Access { expr, index } => {
260            format!("{}[{}]", expr_to_source_with_scope(expr, scope), expr_to_source_with_scope(index, scope))
261        }
262        Expr::DotAccess { expr, field } => {
263            format!("{}.{}", expr_to_source_with_scope(expr, scope), field)
264        }
265    }
266}
267
268fn record_entry_to_source_with_scope(
269    entry: &RecordEntry,
270    scope: &IndexMap<String, SerializableValue>,
271) -> String {
272    match &entry.key {
273        RecordKey::Static(key) => {
274            format!("{}: {}", key, expr_to_source_with_scope(&entry.value, scope))
275        }
276        RecordKey::Dynamic(key_expr) => {
277            format!(
278                "[{}]: {}",
279                expr_to_source_with_scope(key_expr, scope),
280                expr_to_source_with_scope(&entry.value, scope)
281            )
282        }
283        RecordKey::Shorthand(name) => {
284            // For shorthand, check if the value is in scope
285            if let Some(value) = scope.get(name) {
286                format!("{}: {}", name, serializable_value_to_source(value))
287            } else {
288                name.clone()
289            }
290        }
291        RecordKey::Spread(expr) => expr_to_source_with_scope(expr, scope),
292    }
293}
294
295/// Convert a SerializableValue to its source representation
296fn serializable_value_to_source(value: &SerializableValue) -> String {
297    match value {
298        SerializableValue::Number(n) => {
299            if n.fract() == 0.0 && n.abs() < 1e15 {
300                format!("{:.0}", n)
301            } else {
302                n.to_string()
303            }
304        }
305        SerializableValue::Bool(b) => b.to_string(),
306        SerializableValue::Null => "null".to_string(),
307        SerializableValue::String(s) => format!("\"{}\"", s.replace("\\", "\\\\").replace("\"", "\\\"")),
308        SerializableValue::List(items) => {
309            let items_str: Vec<String> = items.iter().map(serializable_value_to_source).collect();
310            format!("[{}]", items_str.join(", "))
311        }
312        SerializableValue::Record(fields) => {
313            let entries_str: Vec<String> = fields
314                .iter()
315                .map(|(k, v)| format!("{}: {}", k, serializable_value_to_source(v)))
316                .collect();
317            format!("{{{}}}", entries_str.join(", "))
318        }
319        SerializableValue::Lambda(_) => "<function>".to_string(),
320        SerializableValue::BuiltIn(name) => name.clone(),
321    }
322}