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
150pub 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 let Some(value) = scope.get(name) {
159 serializable_value_to_source(value)
160 } else {
161 name.clone()
162 }
163 }
164 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 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 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
295fn 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}