1use crate::ast::{
2 BinaryOp, DoStatement, Expr, PostfixOp, RecordEntry, RecordKey, SpannedExpr, UnaryOp,
3};
4use crate::precedence::{Assoc, operator_info};
5use crate::values::{LambdaArg, SerializableValue};
6use indexmap::IndexMap;
7
8const RESERVED_WORDS: &[&str] = &[
10 "if", "then", "else", "true", "false", "null", "and", "or", "not", "do", "return", "output",
11];
12
13pub fn is_valid_identifier(s: &str) -> bool {
19 if s.is_empty() {
20 return false;
21 }
22
23 if RESERVED_WORDS.contains(&s) {
25 return false;
26 }
27
28 let mut chars = s.chars();
29
30 match chars.next() {
32 Some(c) if c.is_ascii_alphabetic() || c == '_' => {}
33 _ => return false,
34 }
35
36 chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
38}
39
40pub fn format_record_key(key: &str) -> String {
42 if is_valid_identifier(key) {
43 key.to_string()
44 } else {
45 format!("\"{}\"", key.replace('\\', "\\\\").replace('"', "\\\""))
46 }
47}
48
49pub fn expr_to_source(spanned_expr: &SpannedExpr) -> String {
50 match &spanned_expr.node {
51 Expr::Number(n) => {
52 if n.fract() == 0.0 && n.abs() < 1e15 {
53 format!("{:.0}", n)
54 } else {
55 n.to_string()
56 }
57 }
58 Expr::String(s) => format!("\"{}\"", s.replace("\\", "\\\\").replace("\"", "\\\"")),
59 Expr::Bool(b) => b.to_string(),
60 Expr::Null => "null".to_string(),
61 Expr::Identifier(name) => name.clone(),
62 Expr::InputReference(field) => format!("#{}", field),
63 Expr::BuiltIn(built_in) => built_in.name().to_string(),
64 Expr::List(items) => {
65 let items_str: Vec<String> = items.iter().map(expr_to_source).collect();
66 format!("[{}]", items_str.join(", "))
67 }
68 Expr::Record(entries) => {
69 let entries_str: Vec<String> = entries.iter().map(record_entry_to_source).collect();
70 format!("{{{}}}", entries_str.join(", "))
71 }
72 Expr::Lambda { args, body } => {
73 let args_str: Vec<String> = args.iter().map(lambda_arg_to_source).collect();
74 format!("({}) => {}", args_str.join(", "), expr_to_source(body))
75 }
76 Expr::Conditional {
77 condition,
78 then_expr,
79 else_expr,
80 } => format!(
81 "if {} then {} else {}",
82 expr_to_source(condition),
83 expr_to_source(then_expr),
84 expr_to_source(else_expr)
85 ),
86 Expr::DoBlock {
87 statements,
88 return_expr,
89 } => {
90 let mut result = "do {".to_string();
91 for stmt in statements {
92 match stmt {
93 DoStatement::Expression(e) => {
94 result.push_str(&format!("\n {}", expr_to_source(e)));
95 }
96 DoStatement::Comment(c) => {
97 result.push_str(&format!("\n {}", c));
98 }
99 }
100 }
101 result.push_str(&format!("\n return {}\n}}", expr_to_source(return_expr)));
102 result
103 }
104 Expr::Assignment { ident, value } => format!("{} = {}", ident, expr_to_source(value)),
105 Expr::Output { expr } => format!("output {}", expr_to_source(expr)),
106 Expr::Call { func, args } => {
107 let args_str: Vec<String> = args.iter().map(expr_to_source).collect();
108 let func_str = match &func.node {
109 Expr::Lambda { .. } => format!("({})", expr_to_source(func)),
111 _ => expr_to_source(func),
112 };
113 format!("{}({})", func_str, args_str.join(", "))
114 }
115 Expr::Access { expr, index } => {
116 format!("{}[{}]", expr_to_source(expr), expr_to_source(index))
117 }
118 Expr::DotAccess { expr, field } => format!("{}.{}", expr_to_source(expr), field),
119 Expr::BinaryOp { op, left, right } => {
120 let op_str = binary_op_to_source(op);
121 let left_str = if needs_parens_in_binop(op, left, true) {
122 format!("({})", expr_to_source(left))
123 } else {
124 expr_to_source(left)
125 };
126 let right_str = if needs_parens_in_binop(op, right, false) {
127 format!("({})", expr_to_source(right))
128 } else {
129 expr_to_source(right)
130 };
131 format!("{} {} {}", left_str, op_str, right_str)
132 }
133 Expr::UnaryOp { op, expr } => {
134 let op_str = unary_op_to_source(op);
135 format!("{}{}", op_str, expr_to_source(expr))
136 }
137 Expr::PostfixOp { op, expr } => {
138 let op_str = postfix_op_to_source(op);
139 format!("{}{}", expr_to_source(expr), op_str)
140 }
141 Expr::Spread(expr) => format!("...{}", expr_to_source(expr)),
142 }
143}
144
145fn lambda_arg_to_source(arg: &LambdaArg) -> String {
146 match arg {
147 LambdaArg::Required(name) => name.clone(),
148 LambdaArg::Optional(name) => format!("{}?", name),
149 LambdaArg::Rest(name) => format!("...{}", name),
150 }
151}
152
153fn record_entry_to_source(entry: &RecordEntry) -> String {
154 match &entry.key {
155 RecordKey::Static(key) => format!(
156 "{}: {}",
157 format_record_key(key),
158 expr_to_source(&entry.value)
159 ),
160 RecordKey::Dynamic(key_expr) => {
161 format!(
162 "[{}]: {}",
163 expr_to_source(key_expr),
164 expr_to_source(&entry.value)
165 )
166 }
167 RecordKey::Shorthand(name) => name.clone(),
168 RecordKey::Spread(expr) => expr_to_source(expr),
169 }
170}
171
172fn binary_op_to_source(op: &BinaryOp) -> &'static str {
173 match op {
174 BinaryOp::Add => "+",
175 BinaryOp::Subtract => "-",
176 BinaryOp::Multiply => "*",
177 BinaryOp::Divide => "/",
178 BinaryOp::Modulo => "%",
179 BinaryOp::Power => "^",
180 BinaryOp::Equal => "==",
181 BinaryOp::NotEqual => "!=",
182 BinaryOp::Less => "<",
183 BinaryOp::LessEq => "<=",
184 BinaryOp::Greater => ">",
185 BinaryOp::GreaterEq => ">=",
186 BinaryOp::DotEqual => ".==",
187 BinaryOp::DotNotEqual => ".!=",
188 BinaryOp::DotLess => ".<",
189 BinaryOp::DotLessEq => ".<=",
190 BinaryOp::DotGreater => ".>",
191 BinaryOp::DotGreaterEq => ".>=",
192 BinaryOp::And => "&&",
193 BinaryOp::NaturalAnd => "and",
194 BinaryOp::Or => "||",
195 BinaryOp::NaturalOr => "or",
196 BinaryOp::Via => "via",
197 BinaryOp::Into => "into",
198 BinaryOp::Where => "where",
199 BinaryOp::Coalesce => "??",
200 }
201}
202
203pub fn needs_parens_in_binop(
205 parent_op: &BinaryOp,
206 child_expr: &SpannedExpr,
207 is_left: bool,
208) -> bool {
209 match &child_expr.node {
210 Expr::BinaryOp { op: child_op, .. } => {
211 let (parent_prec, parent_assoc) = operator_info(parent_op);
212 let (child_prec, _child_assoc) = operator_info(child_op);
213
214 if child_prec < parent_prec {
216 return true;
217 }
218
219 if child_prec == parent_prec && !is_left {
223 match parent_assoc {
224 Assoc::Right => return true,
225 Assoc::Left => {
226 if matches!(
228 parent_op,
229 BinaryOp::Subtract | BinaryOp::Divide | BinaryOp::Modulo
230 ) {
231 return true;
232 }
233 }
234 }
235 }
236
237 false
238 }
239 _ => false,
240 }
241}
242
243fn unary_op_to_source(op: &UnaryOp) -> &'static str {
244 match op {
245 UnaryOp::Negate => "-",
246 UnaryOp::Not => "!",
247 UnaryOp::Invert => "~",
248 }
249}
250
251fn postfix_op_to_source(op: &PostfixOp) -> &'static str {
252 match op {
253 PostfixOp::Factorial => "!",
254 }
255}
256
257pub fn expr_to_source_with_scope(
259 spanned_expr: &SpannedExpr,
260 scope: &IndexMap<String, SerializableValue>,
261) -> String {
262 match &spanned_expr.node {
263 Expr::Identifier(name) => {
264 if let Some(value) = scope.get(name) {
266 serializable_value_to_source(value)
267 } else {
268 name.clone()
269 }
270 }
271 Expr::InputReference(field) => format!("#{}", field),
272 Expr::Number(n) => {
274 if n.fract() == 0.0 && n.abs() < 1e15 {
275 format!("{:.0}", n)
276 } else {
277 n.to_string()
278 }
279 }
280 Expr::String(s) => format!("\"{}\"", s.replace("\\", "\\\\").replace("\"", "\\\"")),
281 Expr::Bool(b) => b.to_string(),
282 Expr::Null => "null".to_string(),
283 Expr::BuiltIn(built_in) => built_in.name().to_string(),
284 Expr::List(items) => {
285 let items_str: Vec<String> = items
286 .iter()
287 .map(|e| expr_to_source_with_scope(e, scope))
288 .collect();
289 format!("[{}]", items_str.join(", "))
290 }
291 Expr::Record(entries) => {
292 let entries_str: Vec<String> = entries
293 .iter()
294 .map(|e| record_entry_to_source_with_scope(e, scope))
295 .collect();
296 format!("{{{}}}", entries_str.join(", "))
297 }
298 Expr::Lambda { args, body } => {
299 let args_str: Vec<String> = args.iter().map(lambda_arg_to_source).collect();
300 let mut filtered_scope = scope.clone();
303 for arg in args {
304 filtered_scope.shift_remove(arg.get_name());
305 }
306 format!(
307 "({}) => {}",
308 args_str.join(", "),
309 expr_to_source_with_scope(body, &filtered_scope)
310 )
311 }
312 Expr::Conditional {
313 condition,
314 then_expr,
315 else_expr,
316 } => format!(
317 "if {} then {} else {}",
318 expr_to_source_with_scope(condition, scope),
319 expr_to_source_with_scope(then_expr, scope),
320 expr_to_source_with_scope(else_expr, scope)
321 ),
322 Expr::DoBlock {
323 statements,
324 return_expr,
325 } => {
326 let mut result = "do {".to_string();
327 for stmt in statements {
328 match stmt {
329 DoStatement::Expression(e) => {
330 result.push_str(&format!("\n {}", expr_to_source_with_scope(e, scope)));
331 }
332 DoStatement::Comment(c) => {
333 result.push_str(&format!("\n // {}", c));
334 }
335 }
336 }
337 result.push_str(&format!(
338 "\n return {}",
339 expr_to_source_with_scope(return_expr, scope)
340 ));
341 result.push_str("\n}");
342 result
343 }
344 Expr::BinaryOp { op, left, right } => {
345 let op_str = binary_op_to_source(op);
346 let left_str = if needs_parens_in_binop(op, left, true) {
347 format!("({})", expr_to_source_with_scope(left, scope))
348 } else {
349 expr_to_source_with_scope(left, scope)
350 };
351 let right_str = if needs_parens_in_binop(op, right, false) {
352 format!("({})", expr_to_source_with_scope(right, scope))
353 } else {
354 expr_to_source_with_scope(right, scope)
355 };
356 format!("{} {} {}", left_str, op_str, right_str)
357 }
358 Expr::UnaryOp { op, expr } => {
359 let op_str = match op {
360 UnaryOp::Negate => "-",
361 UnaryOp::Not => "!",
362 UnaryOp::Invert => "~",
363 };
364 format!("{}{}", op_str, expr_to_source_with_scope(expr, scope))
365 }
366 Expr::PostfixOp { op, expr } => {
367 let op_str = postfix_op_to_source(op);
368 format!("{}{}", expr_to_source_with_scope(expr, scope), op_str)
369 }
370 Expr::Spread(expr) => format!("...{}", expr_to_source_with_scope(expr, scope)),
371 Expr::Assignment { ident, value } => {
372 format!("{} = {}", ident, expr_to_source_with_scope(value, scope))
373 }
374 Expr::Output { expr } => {
375 format!("output {}", expr_to_source_with_scope(expr, scope))
376 }
377 Expr::Call { func, args } => {
378 let args_str: Vec<String> = args
379 .iter()
380 .map(|e| expr_to_source_with_scope(e, scope))
381 .collect();
382 let func_str = match &func.node {
383 Expr::Lambda { .. } => {
385 format!("({})", expr_to_source_with_scope(func, scope))
386 }
387 _ => expr_to_source_with_scope(func, scope),
388 };
389 format!("{}({})", func_str, args_str.join(", "))
390 }
391 Expr::Access { expr, index } => {
392 format!(
393 "{}[{}]",
394 expr_to_source_with_scope(expr, scope),
395 expr_to_source_with_scope(index, scope)
396 )
397 }
398 Expr::DotAccess { expr, field } => {
399 format!("{}.{}", expr_to_source_with_scope(expr, scope), field)
400 }
401 }
402}
403
404fn record_entry_to_source_with_scope(
405 entry: &RecordEntry,
406 scope: &IndexMap<String, SerializableValue>,
407) -> String {
408 match &entry.key {
409 RecordKey::Static(key) => {
410 format!(
411 "{}: {}",
412 format_record_key(key),
413 expr_to_source_with_scope(&entry.value, scope)
414 )
415 }
416 RecordKey::Dynamic(key_expr) => {
417 format!(
418 "[{}]: {}",
419 expr_to_source_with_scope(key_expr, scope),
420 expr_to_source_with_scope(&entry.value, scope)
421 )
422 }
423 RecordKey::Shorthand(name) => {
424 if let Some(value) = scope.get(name) {
427 format!("{}: {}", name, serializable_value_to_source(value))
428 } else {
429 name.clone()
430 }
431 }
432 RecordKey::Spread(expr) => expr_to_source_with_scope(expr, scope),
433 }
434}
435
436fn serializable_value_to_source(value: &SerializableValue) -> String {
438 match value {
439 SerializableValue::Number(n) => {
440 if n.fract() == 0.0 && n.abs() < 1e15 {
441 format!("{:.0}", n)
442 } else {
443 n.to_string()
444 }
445 }
446 SerializableValue::Bool(b) => b.to_string(),
447 SerializableValue::Null => "null".to_string(),
448 SerializableValue::String(s) => {
449 format!("\"{}\"", s.replace("\\", "\\\\").replace("\"", "\\\""))
450 }
451 SerializableValue::List(items) => {
452 let items_str: Vec<String> = items.iter().map(serializable_value_to_source).collect();
453 format!("[{}]", items_str.join(", "))
454 }
455 SerializableValue::Record(fields) => {
456 let entries_str: Vec<String> = fields
457 .iter()
458 .map(|(k, v)| {
459 format!(
460 "{}: {}",
461 format_record_key(k),
462 serializable_value_to_source(v)
463 )
464 })
465 .collect();
466 format!("{{{}}}", entries_str.join(", "))
467 }
468 SerializableValue::Lambda(lambda_def) => {
469 let args_str: Vec<String> = lambda_def.args.iter().map(lambda_arg_to_source).collect();
470 format!("(({}) => {})", args_str.join(", "), lambda_def.body)
472 }
473 SerializableValue::BuiltIn(name) => name.clone(),
474 }
475}