1use crate::ast::{BinaryOp, DoStatement, Expr, PostfixOp, RecordEntry, RecordKey, UnaryOp};
2use crate::values::LambdaArg;
3
4pub fn expr_to_source(expr: &Expr) -> String {
5 match expr {
6 Expr::Number(n) => {
7 if n.fract() == 0.0 && n.abs() < 1e15 {
8 format!("{:.0}", n)
9 } else {
10 n.to_string()
11 }
12 }
13 Expr::String(s) => format!("\"{}\"", s.replace("\\", "\\\\").replace("\"", "\\\"")),
14 Expr::Bool(b) => b.to_string(),
15 Expr::Null => "null".to_string(),
16 Expr::Identifier(name) => name.clone(),
17 Expr::BuiltIn(built_in) => built_in.name().to_string(),
18 Expr::List(items) => {
19 let items_str: Vec<String> = items.iter().map(expr_to_source).collect();
20 format!("[{}]", items_str.join(", "))
21 }
22 Expr::Record(entries) => {
23 let entries_str: Vec<String> = entries.iter().map(record_entry_to_source).collect();
24 format!("{{{}}}", entries_str.join(", "))
25 }
26 Expr::Lambda { args, body } => {
27 let args_str: Vec<String> = args.iter().map(lambda_arg_to_source).collect();
28 format!("({}) => {}", args_str.join(", "), expr_to_source(body))
29 }
30 Expr::Conditional {
31 condition,
32 then_expr,
33 else_expr,
34 } => format!(
35 "if {} then {} else {}",
36 expr_to_source(condition),
37 expr_to_source(then_expr),
38 expr_to_source(else_expr)
39 ),
40 Expr::DoBlock {
41 statements,
42 return_expr,
43 } => {
44 let mut result = "do {".to_string();
45 for stmt in statements {
46 match stmt {
47 DoStatement::Expression(e) => {
48 result.push_str(&format!("\n {}", expr_to_source(e)));
49 }
50 DoStatement::Comment(c) => {
51 result.push_str(&format!("\n {}", c));
52 }
53 }
54 }
55 result.push_str(&format!("\n return {}\n}}", expr_to_source(return_expr)));
56 result
57 }
58 Expr::Assignment { ident, value } => format!("{} = {}", ident, expr_to_source(value)),
59 Expr::Call { func, args } => {
60 let args_str: Vec<String> = args.iter().map(expr_to_source).collect();
61 format!("{}({})", expr_to_source(func), args_str.join(", "))
62 }
63 Expr::Access { expr, index } => {
64 format!("{}[{}]", expr_to_source(expr), expr_to_source(index))
65 }
66 Expr::DotAccess { expr, field } => format!("{}.{}", expr_to_source(expr), field),
67 Expr::BinaryOp { op, left, right } => {
68 let op_str = binary_op_to_source(op);
69 format!(
70 "{} {} {}",
71 expr_to_source(left),
72 op_str,
73 expr_to_source(right)
74 )
75 }
76 Expr::UnaryOp { op, expr } => {
77 let op_str = unary_op_to_source(op);
78 format!("{}{}", op_str, expr_to_source(expr))
79 }
80 Expr::PostfixOp { op, expr } => {
81 let op_str = postfix_op_to_source(op);
82 format!("{}{}", expr_to_source(expr), op_str)
83 }
84 Expr::Spread(expr) => format!("...{}", expr_to_source(expr)),
85 }
86}
87
88fn lambda_arg_to_source(arg: &LambdaArg) -> String {
89 match arg {
90 LambdaArg::Required(name) => name.clone(),
91 LambdaArg::Optional(name) => format!("{}?", name),
92 LambdaArg::Rest(name) => format!("...{}", name),
93 }
94}
95
96fn record_entry_to_source(entry: &RecordEntry) -> String {
97 match &entry.key {
98 RecordKey::Static(key) => format!("{}: {}", key, expr_to_source(&entry.value)),
99 RecordKey::Dynamic(key_expr) => {
100 format!(
101 "[{}]: {}",
102 expr_to_source(key_expr),
103 expr_to_source(&entry.value)
104 )
105 }
106 RecordKey::Shorthand(name) => name.clone(),
107 RecordKey::Spread(expr) => expr_to_source(expr),
108 }
109}
110
111fn binary_op_to_source(op: &BinaryOp) -> &'static str {
112 match op {
113 BinaryOp::Add => "+",
114 BinaryOp::Subtract => "-",
115 BinaryOp::Multiply => "*",
116 BinaryOp::Divide => "/",
117 BinaryOp::Modulo => "%",
118 BinaryOp::Power => "^",
119 BinaryOp::Equal => "==",
120 BinaryOp::NotEqual => "!=",
121 BinaryOp::Less => "<",
122 BinaryOp::LessEq => "<=",
123 BinaryOp::Greater => ">",
124 BinaryOp::GreaterEq => ">=",
125 BinaryOp::And => "&&",
126 BinaryOp::NaturalAnd => "and",
127 BinaryOp::Or => "||",
128 BinaryOp::NaturalOr => "or",
129 BinaryOp::Via => "via",
130 BinaryOp::Into => "into",
131 BinaryOp::Coalesce => "??",
132 }
133}
134
135fn unary_op_to_source(op: &UnaryOp) -> &'static str {
136 match op {
137 UnaryOp::Negate => "-",
138 UnaryOp::Not => "!",
139 UnaryOp::Invert => "~",
140 }
141}
142
143fn postfix_op_to_source(op: &PostfixOp) -> &'static str {
144 match op {
145 PostfixOp::Factorial => "!",
146 }
147}