1use crate::ast::{AST, AssignmentOp, Type};
2
3fn type_keyword(var_type: &Type) -> String {
4 match var_type {
5 Type::Arcana => "arcana".to_string(),
6 Type::Aether => "aether".to_string(),
7 Type::Rune => "rune".to_string(),
8 Type::Omen => "omen".to_string(),
9 Type::Abyss => "abyss".to_string(),
10 Type::Scroll => "scroll".to_string(),
11 Type::Lexicon => "lexicon".to_string(),
12 Type::Materia => "materia".to_string(),
13 Type::Glyph => "glyph".to_string(),
14 Type::Artifact(name) => name.clone(),
15 }
16}
17
18pub fn format_ast(ast: &AST, indent_level: usize) -> String {
29 let indent = " ".repeat(indent_level);
30
31 let precedence = |node: &AST| match node {
33 AST::LogicalOr(_, _, _) => 10,
34 AST::LogicalAnd(_, _, _) => 20,
35 AST::Equal(_, _, _) | AST::NotEqual(_, _, _) => 30,
36 AST::LessThan(_, _, _)
37 | AST::LessThanOrEqual(_, _, _)
38 | AST::GreaterThan(_, _, _)
39 | AST::GreaterThanOrEqual(_, _, _) => 40,
40 AST::Add(_, _, _) | AST::Sub(_, _, _) => 50,
41 AST::Mul(_, _, _) | AST::Div(_, _, _) | AST::Mod(_, _, _) => 60,
42 AST::PowArcana(_, _, _) | AST::PowAether(_, _, _) => 70,
43 AST::LogicalNot(_, _) => 80,
44 AST::IndexAccess { .. } | AST::FieldAccess { .. } => 90,
45 _ => 100,
46 };
47
48 let current_precedence = precedence(ast);
49
50 let format_with_parentheses = |expr: &AST, parent_precedence: u8| -> String {
52 let sub_precedence = precedence(expr);
53 let code = format_ast(expr, indent_level);
54
55 if sub_precedence < parent_precedence {
56 format!("({})", code)
57 } else {
58 code
59 }
60 };
61
62 match ast {
63 AST::Statement(statement, _) => {
64 format!("{}{};", indent, format_ast(statement, indent_level))
65 }
66 AST::Add(left, right, _)
67 | AST::Sub(left, right, _)
68 | AST::Mul(left, right, _)
69 | AST::Div(left, right, _)
70 | AST::Mod(left, right, _)
71 | AST::PowArcana(left, right, _)
72 | AST::PowAether(left, right, _)
73 | AST::LogicalAnd(left, right, _)
74 | AST::LogicalOr(left, right, _)
75 | AST::Equal(left, right, _)
76 | AST::NotEqual(left, right, _)
77 | AST::LessThan(left, right, _)
78 | AST::LessThanOrEqual(left, right, _)
79 | AST::GreaterThan(left, right, _)
80 | AST::GreaterThanOrEqual(left, right, _) => {
81 let operator = match ast {
82 AST::Add(_, _, _) => "+",
83 AST::Sub(_, _, _) => "-",
84 AST::Mul(_, _, _) => "*",
85 AST::Div(_, _, _) => "/",
86 AST::Mod(_, _, _) => "%",
87 AST::PowArcana(_, _, _) => "^",
88 AST::PowAether(_, _, _) => "**",
89 AST::LogicalAnd(_, _, _) => "&&",
90 AST::LogicalOr(_, _, _) => "||",
91 AST::Equal(_, _, _) => "==",
92 AST::NotEqual(_, _, _) => "!=",
93 AST::LessThan(_, _, _) => "<",
94 AST::LessThanOrEqual(_, _, _) => "<=",
95 AST::GreaterThan(_, _, _) => ">",
96 AST::GreaterThanOrEqual(_, _, _) => ">=",
97 _ => unreachable!(),
98 };
99 format!(
100 "{} {} {}",
101 format_with_parentheses(left, current_precedence),
102 operator,
103 format_with_parentheses(right, current_precedence)
104 )
105 }
106 AST::LogicalNot(expr, _) => {
107 format!("!{}", format_with_parentheses(expr, current_precedence))
108 }
109 AST::VarAssign {
110 name,
111 value,
112 var_type,
113 is_morph,
114 ..
115 } => {
116 format!(
117 "forge {}{}: {} = {}",
118 if *is_morph { "morph " } else { "" },
119 name,
120 type_keyword(var_type),
121 format_ast(value, indent_level)
122 )
123 }
124 AST::Assignment {
125 name, value, op, ..
126 } => match op {
127 AssignmentOp::Assign => format!("{} = {}", name, format_ast(value, indent_level)),
128 AssignmentOp::AddAssign => {
129 format!("{} += {}", name, format_ast(value, indent_level))
130 }
131 AssignmentOp::SubAssign => {
132 format!("{} -= {}", name, format_ast(value, indent_level))
133 }
134 AssignmentOp::MulAssign => {
135 format!("{} *= {}", name, format_ast(value, indent_level))
136 }
137 AssignmentOp::DivAssign => {
138 format!("{} /= {}", name, format_ast(value, indent_level))
139 }
140 AssignmentOp::ModAssign => {
141 format!("{} %= {}", name, format_ast(value, indent_level))
142 }
143 AssignmentOp::PowArcanaAssign => {
144 format!("{} ^= {}", name, format_ast(value, indent_level))
145 }
146 AssignmentOp::PowAetherAssign => {
147 format!("{} **= {}", name, format_ast(value, indent_level))
148 }
149 },
150 AST::Var(name, _) => name.clone(),
151 AST::FieldAccess { target, field, .. } => {
152 format!("{}.{}", format_ast(target, indent_level), field)
153 }
154 AST::Arcana(value, _) => format!("{}", value),
155 AST::Aether(value, _) => {
156 if value.fract() == 0.0 {
157 format!("{:.1}", value)
158 } else {
159 format!("{}", value)
160 }
161 }
162 AST::Rune(value, _) => format!("\"{}\"", value),
163 AST::Omen(value, _) => match value {
164 true => "boon".to_string(),
165 false => "hex".to_string(),
166 },
167 AST::Abyss(_) => "abyss".to_string(),
168 AST::Reveal(value, _) => {
169 let val = format_ast(value, indent_level);
170 let trimmed_val = val.trim();
171 match trimmed_val {
172 "abyss" => "reveal".to_string(),
173 _ => format!("reveal {}", trimmed_val),
174 }
175 }
176 AST::Block(statements, _) => {
177 let mut result = format!("{}{{\n", indent);
178 for statement in statements {
179 result.push_str(&format!("{}\n", format_ast(statement, indent_level + 1)));
180 }
181 result.push_str(&format!("{}}}", indent));
182 result
183 }
184 AST::Oracle {
185 is_match,
186 conditionals,
187 branches,
188 ..
189 } => {
190 let mut result = "oracle".to_string();
191 if !conditionals.is_empty() {
192 let conditions = conditionals
193 .iter()
194 .map(|cond| {
195 if *is_match {
196 format_ast(cond.expression.as_ref(), indent_level)
197 } else {
198 format!(
199 "{} = {}",
200 cond.variable,
201 format_ast(cond.expression.as_ref(), indent_level)
202 )
203 }
204 })
205 .collect::<Vec<_>>()
206 .join(", ");
207 result.push_str(&format!(" ({})", conditions));
208 }
209 result.push_str(" {\n");
210 for branch in branches {
211 if let AST::Comment(text, _) = branch {
212 result.push_str(&format!("{}{}\n", " ".repeat(indent_level + 1), text));
213 continue;
214 }
215
216 if let AST::OracleBranch {
217 pattern,
218 guard,
219 body,
220 ..
221 } = branch
222 {
223 let pattern_text = match pattern.as_slice() {
229 [] => "_".to_string(),
230 [only @ AST::OracleScrollPattern { .. }]
231 | [only @ AST::OracleArtifactPattern { .. }]
232 | [only @ AST::OracleLexiconPattern { .. }] => {
233 format_ast(only, indent_level + 1)
234 }
235 elems => {
236 let inner = elems
237 .iter()
238 .map(|pat| format_ast(pat, indent_level + 1))
239 .collect::<Vec<_>>()
240 .join(", ");
241 format!("({})", inner)
242 }
243 };
244 let guard_text = guard
245 .as_ref()
246 .map(|expr| {
247 format!(" ward {}", format_ast(expr.as_ref(), indent_level + 1))
248 })
249 .unwrap_or_default();
250 result.push_str(&format!(
251 "{}{}{} => {}\n",
252 " ".repeat(indent_level + 1),
253 pattern_text,
254 guard_text,
255 format_ast(body.as_ref(), indent_level + 1).trim()
256 ));
257 }
258 }
259 result.push_str(&format!("{}}}", indent));
260 result
261 }
262 AST::OracleDontCareItem(_) => "_".to_string(),
263 AST::OracleScrollPattern { elements, .. } => {
264 let inner = elements
265 .iter()
266 .map(|elem| format_ast(elem, indent_level))
267 .collect::<Vec<_>>()
268 .join(", ");
269 format!("[{}]", inner)
270 }
271 AST::OracleScrollRest { name, .. } => match name {
272 Some(name) => format!("..{}", name),
273 None => "..".to_string(),
274 },
275 AST::OracleArtifactPattern {
276 type_name, fields, ..
277 } => {
278 if fields.is_empty() {
279 format!("{} {{}}", type_name)
283 } else {
284 let inner = fields
285 .iter()
286 .map(|(name, sub)| match sub {
287 AST::Var(var_name, _) if var_name == name => name.clone(),
290 other => format!("{}: {}", name, format_ast(other, indent_level)),
291 })
292 .collect::<Vec<_>>()
293 .join(", ");
294 format!("{} {{ {} }}", type_name, inner)
295 }
296 }
297 AST::OracleLexiconPattern { entries, .. } => {
298 if entries.is_empty() {
299 "{}".to_string()
301 } else {
302 let inner = entries
303 .iter()
304 .map(|(key, sub)| format!("\"{}\": {}", key, format_ast(sub, indent_level)))
305 .collect::<Vec<_>>()
306 .join(", ");
307 format!("{{ {} }}", inner)
308 }
309 }
310 AST::Orbit { params, body, .. } => {
311 let mut result = "orbit".to_string();
312 if !params.is_empty() {
313 let params_str = params
314 .iter()
315 .map(|param| format_ast(param, indent_level))
316 .collect::<Vec<_>>()
317 .join(", ");
318 result.push_str(&format!(" ({})", params_str));
319 }
320 result.push_str(format_ast(body.as_ref(), indent_level).trim());
321 result
322 }
323 AST::OrbitParam {
324 name,
325 start,
326 end,
327 op,
328 ..
329 } => {
330 let start_expr = format_ast(start, 0);
331 let end_expr = format_ast(end, 0);
332 format!("{} = {}{}{}", name, start_expr, op, end_expr)
333 }
334 AST::Resume(value, _) => match value {
335 Some(idendifier) => format!("resume {}", idendifier),
336 None => "resume".to_string(),
337 },
338 AST::Eject(value, _) => match value {
339 Some(idendifier) => format!("eject {}", idendifier),
340 None => "eject".to_string(),
341 },
342 AST::Engrave {
343 name,
344 params,
345 return_type,
346 body,
347 method_target,
348 ..
349 } => {
350 let return_type_str = match return_type {
351 Type::Abyss => None,
352 _ => Some(type_keyword(return_type)),
353 };
354 let mut param_strings = Vec::new();
355 let mut iter = params.iter();
356 if let Some(target) = method_target {
357 let receiver = if target.requires_morph {
358 "morph core"
359 } else {
360 "core"
361 };
362 param_strings.push(receiver.to_string());
363 debug_assert!(
364 !params.is_empty(),
365 "Artifact method with method_target must have at least one parameter (the receiver)"
366 );
367 iter.next();
368 }
369 for param in iter {
370 param_strings.push(format_ast(param, indent_level));
371 }
372 let params_str = param_strings.join(", ");
373 let qualified_name = if let Some(target) = method_target {
374 format!("{}::{}", target.artifact, name)
375 } else {
376 name.clone()
377 };
378 match return_type_str {
379 None => format!(
380 "engrave {}({}) {}",
381 qualified_name,
382 params_str,
383 format_ast(body, indent_level)
384 ),
385 Some(ret) => format!(
386 "engrave {}({}) -> {} {}",
387 qualified_name,
388 params_str,
389 ret,
390 format_ast(body, indent_level)
391 ),
392 }
393 }
394 AST::EngraveParam {
395 name,
396 param_type,
397 is_morph,
398 ..
399 } => {
400 let qualifier = if *is_morph { "morph " } else { "" };
401 format!("{}{}: {}", qualifier, name, type_keyword(param_type))
402 }
403 AST::FuncCall { name, args, .. } => {
404 let args_str = args
405 .iter()
406 .map(|arg| format_ast(arg, indent_level))
407 .collect::<Vec<_>>()
408 .join(", ");
409 format!("{}({})", name, args_str)
410 }
411 AST::ListLiteral { elements, .. } => {
412 let contents = elements
413 .iter()
414 .map(|elem| format_ast(elem, indent_level))
415 .collect::<Vec<_>>()
416 .join(", ");
417 format!("[{}]", contents)
418 }
419 AST::MapLiteral { entries, .. } => {
420 let contents = entries
421 .iter()
422 .map(|(key, value)| format!("\"{}\": {}", key, format_ast(value, indent_level)))
423 .collect::<Vec<_>>()
424 .join(", ");
425 format!("{{{}}}", contents)
426 }
427 AST::ArtifactLiteral {
428 type_name, fields, ..
429 } => {
430 if fields.is_empty() {
431 format!("{} {{}}", type_name)
432 } else {
433 let contents = fields
434 .iter()
435 .map(|(field, value)| format!("{}: {}", field, format_ast(value, indent_level)))
436 .collect::<Vec<_>>()
437 .join(", ");
438 format!("{} {{ {} }}", type_name, contents)
439 }
440 }
441 AST::IndexAccess { target, index, .. } => {
442 format!(
443 "{}[{}]",
444 format_ast(target, indent_level),
445 format_ast(index, indent_level)
446 )
447 }
448 AST::IndexAssignment {
449 target,
450 index,
451 value,
452 ..
453 } => format!(
454 "{}[{}] = {}",
455 format_ast(target, indent_level),
456 format_ast(index, indent_level),
457 format_ast(value, indent_level)
458 ),
459 AST::FieldAssignment {
460 target,
461 field,
462 value,
463 ..
464 } => format!(
465 "{}.{} = {}",
466 format_ast(target, indent_level),
467 field,
468 format_ast(value, indent_level)
469 ),
470 AST::MethodCall {
471 receiver,
472 method,
473 args,
474 ..
475 } => {
476 let args_str = args
477 .iter()
478 .map(|arg| format_ast(arg, indent_level))
479 .collect::<Vec<_>>()
480 .join(", ");
481 format!(
482 "{}.{}({})",
483 format_ast(receiver, indent_level),
484 method,
485 args_str
486 )
487 }
488 AST::ArtifactDef { name, fields, .. } => {
489 let mut result = format!("artifact {} {{\n", name);
490 for field in fields {
491 result.push_str(&format!(
492 "{}{}: {};\n",
493 " ".repeat(indent_level + 1),
494 field.name,
495 type_keyword(&field.field_type)
496 ));
497 }
498 result.push_str(&format!("{}}}", indent));
499 result
500 }
501 AST::Comment(text, _) => text.clone(),
502 _ => format!("Not implemented: {:?}", ast),
503 }
504}