1use crate::ast::AstNode;
14use crate::hir::HirNode;
15use crate::types::{ExprType, TypeNamespace};
16use crate::vm::{Opcode, Plan};
17use std::fmt::Write as FmtWrite;
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum VisualizationFormat {
22 Mermaid,
24 Dot,
26 AsciiTree,
28}
29
30pub trait Visualize {
32 fn visualize(&self, format: VisualizationFormat) -> String;
34}
35
36impl Visualize for AstNode {
41 fn visualize(&self, format: VisualizationFormat) -> String {
42 match format {
43 VisualizationFormat::Mermaid => visualize_ast_mermaid(self),
44 VisualizationFormat::Dot => visualize_ast_dot(self),
45 VisualizationFormat::AsciiTree => visualize_ast_ascii(self, 0),
46 }
47 }
48}
49
50fn visualize_ast_mermaid(node: &AstNode) -> String {
51 let mut output = String::from("graph TD\n");
52 let mut counter = 0;
53 visit_ast_mermaid(node, &mut counter, None, &mut output);
54 output
55}
56
57fn visit_ast_mermaid(
58 node: &AstNode,
59 counter: &mut usize,
60 parent_id: Option<usize>,
61 output: &mut String,
62) {
63 let current_id = *counter;
64 *counter += 1;
65
66 let label = ast_node_label(node);
67 let _ = writeln!(output, " n{}[\"{}\"]", current_id, label);
68
69 if let Some(parent) = parent_id {
70 let _ = writeln!(output, " n{} --> n{}", parent, current_id);
71 }
72
73 match node {
75 AstNode::TermExpression { term }
76 | AstNode::InvocationTerm { invocation: term }
77 | AstNode::LiteralTerm { literal: term }
78 | AstNode::ParenthesizedTerm { expression: term } => {
79 visit_ast_mermaid(term, counter, Some(current_id), output);
80 }
81 AstNode::InvocationExpression {
82 expression,
83 invocation,
84 } => {
85 visit_ast_mermaid(expression, counter, Some(current_id), output);
86 visit_ast_mermaid(invocation, counter, Some(current_id), output);
87 }
88 AstNode::IndexerExpression { collection, index } => {
89 visit_ast_mermaid(collection, counter, Some(current_id), output);
90 visit_ast_mermaid(index, counter, Some(current_id), output);
91 }
92 AstNode::PolarityExpression {
93 operator: _,
94 expression,
95 } => {
96 visit_ast_mermaid(expression, counter, Some(current_id), output);
97 }
98 AstNode::MultiplicativeExpression { left, right, .. }
99 | AstNode::AdditiveExpression { left, right, .. }
100 | AstNode::UnionExpression { left, right }
101 | AstNode::InequalityExpression { left, right, .. }
102 | AstNode::EqualityExpression { left, right, .. }
103 | AstNode::MembershipExpression { left, right, .. }
104 | AstNode::AndExpression { left, right }
105 | AstNode::OrExpression { left, right, .. }
106 | AstNode::ImpliesExpression { left, right } => {
107 visit_ast_mermaid(left, counter, Some(current_id), output);
108 visit_ast_mermaid(right, counter, Some(current_id), output);
109 }
110 AstNode::TypeExpression {
111 expression,
112 type_specifier,
113 ..
114 } => {
115 visit_ast_mermaid(expression, counter, Some(current_id), output);
116 let type_id = *counter;
117 *counter += 1;
118 let _ = writeln!(output, " n{}[\"Type: {:?}\"]", type_id, type_specifier);
119 let _ = writeln!(output, " n{} --> n{}", current_id, type_id);
120 }
121 AstNode::FunctionInvocation {
122 function_name: _,
123 parameters,
124 } => {
125 for param in parameters {
126 visit_ast_mermaid(param, counter, Some(current_id), output);
127 }
128 }
129 AstNode::CollectionLiteral { elements } => {
130 for elem in elements {
131 visit_ast_mermaid(elem, counter, Some(current_id), output);
132 }
133 }
134 _ => {} }
136}
137
138fn visualize_ast_dot(node: &AstNode) -> String {
139 let mut output = String::from("digraph AST {\n");
140 output.push_str(" node [shape=box, style=rounded];\n");
141 let mut counter = 0;
142 visit_ast_dot(node, &mut counter, None, &mut output);
143 output.push_str("}\n");
144 output
145}
146
147fn visit_ast_dot(
148 node: &AstNode,
149 counter: &mut usize,
150 parent_id: Option<usize>,
151 output: &mut String,
152) {
153 let current_id = *counter;
154 *counter += 1;
155
156 let label = ast_node_label(node);
157 let _ = writeln!(output, " n{} [label=\"{}\"];", current_id, label);
158
159 if let Some(parent) = parent_id {
160 let _ = writeln!(output, " n{} -> n{};", parent, current_id);
161 }
162
163 match node {
165 AstNode::TermExpression { term }
166 | AstNode::InvocationTerm { invocation: term }
167 | AstNode::LiteralTerm { literal: term }
168 | AstNode::ParenthesizedTerm { expression: term } => {
169 visit_ast_dot(term, counter, Some(current_id), output);
170 }
171 AstNode::InvocationExpression {
172 expression,
173 invocation,
174 } => {
175 visit_ast_dot(expression, counter, Some(current_id), output);
176 visit_ast_dot(invocation, counter, Some(current_id), output);
177 }
178 AstNode::IndexerExpression { collection, index } => {
179 visit_ast_dot(collection, counter, Some(current_id), output);
180 visit_ast_dot(index, counter, Some(current_id), output);
181 }
182 AstNode::PolarityExpression {
183 operator: _,
184 expression,
185 } => {
186 visit_ast_dot(expression, counter, Some(current_id), output);
187 }
188 AstNode::MultiplicativeExpression { left, right, .. }
189 | AstNode::AdditiveExpression { left, right, .. }
190 | AstNode::UnionExpression { left, right }
191 | AstNode::InequalityExpression { left, right, .. }
192 | AstNode::EqualityExpression { left, right, .. }
193 | AstNode::MembershipExpression { left, right, .. }
194 | AstNode::AndExpression { left, right }
195 | AstNode::OrExpression { left, right, .. }
196 | AstNode::ImpliesExpression { left, right } => {
197 visit_ast_dot(left, counter, Some(current_id), output);
198 visit_ast_dot(right, counter, Some(current_id), output);
199 }
200 AstNode::TypeExpression {
201 expression,
202 type_specifier,
203 ..
204 } => {
205 visit_ast_dot(expression, counter, Some(current_id), output);
206 let type_id = *counter;
207 *counter += 1;
208 let _ = writeln!(
209 output,
210 " n{} [label=\"Type: {:?}\"];",
211 type_id, type_specifier
212 );
213 let _ = writeln!(output, " n{} -> n{};", current_id, type_id);
214 }
215 AstNode::FunctionInvocation {
216 function_name: _,
217 parameters,
218 } => {
219 for param in parameters {
220 visit_ast_dot(param, counter, Some(current_id), output);
221 }
222 }
223 AstNode::CollectionLiteral { elements } => {
224 for elem in elements {
225 visit_ast_dot(elem, counter, Some(current_id), output);
226 }
227 }
228 _ => {} }
230}
231
232fn visualize_ast_ascii(node: &AstNode, depth: usize) -> String {
233 let indent = " ".repeat(depth);
234 let mut output = format!("{}├─ {}\n", indent, ast_node_label(node));
235
236 match node {
238 AstNode::TermExpression { term }
239 | AstNode::InvocationTerm { invocation: term }
240 | AstNode::LiteralTerm { literal: term }
241 | AstNode::ParenthesizedTerm { expression: term } => {
242 output.push_str(&visualize_ast_ascii(term, depth + 1));
243 }
244 AstNode::InvocationExpression {
245 expression,
246 invocation,
247 } => {
248 output.push_str(&visualize_ast_ascii(expression, depth + 1));
249 output.push_str(&visualize_ast_ascii(invocation, depth + 1));
250 }
251 AstNode::IndexerExpression { collection, index } => {
252 output.push_str(&visualize_ast_ascii(collection, depth + 1));
253 output.push_str(&visualize_ast_ascii(index, depth + 1));
254 }
255 AstNode::PolarityExpression {
256 operator: _,
257 expression,
258 } => {
259 output.push_str(&visualize_ast_ascii(expression, depth + 1));
260 }
261 AstNode::MultiplicativeExpression { left, right, .. }
262 | AstNode::AdditiveExpression { left, right, .. }
263 | AstNode::UnionExpression { left, right }
264 | AstNode::InequalityExpression { left, right, .. }
265 | AstNode::EqualityExpression { left, right, .. }
266 | AstNode::MembershipExpression { left, right, .. }
267 | AstNode::AndExpression { left, right }
268 | AstNode::OrExpression { left, right, .. }
269 | AstNode::ImpliesExpression { left, right } => {
270 output.push_str(&visualize_ast_ascii(left, depth + 1));
271 output.push_str(&visualize_ast_ascii(right, depth + 1));
272 }
273 AstNode::TypeExpression {
274 expression,
275 type_specifier,
276 ..
277 } => {
278 output.push_str(&visualize_ast_ascii(expression, depth + 1));
279 output.push_str(&format!("{} ├─ Type: {:?}\n", indent, type_specifier));
280 }
281 AstNode::FunctionInvocation {
282 function_name: _,
283 parameters,
284 } => {
285 for param in parameters {
286 output.push_str(&visualize_ast_ascii(param, depth + 1));
287 }
288 }
289 AstNode::CollectionLiteral { elements } => {
290 for elem in elements {
291 output.push_str(&visualize_ast_ascii(elem, depth + 1));
292 }
293 }
294 _ => {} }
296
297 output
298}
299
300fn ast_node_label(node: &AstNode) -> String {
301 match node {
302 AstNode::NullLiteral => "null".to_string(),
303 AstNode::BooleanLiteral(b) => format!("Boolean: {}", b),
304 AstNode::StringLiteral(s) => format!("String: \"{}\"", s),
305 AstNode::IntegerLiteral(i) => format!("Integer: {}", i),
306 AstNode::NumberLiteral(d) => format!("Decimal: {}", d),
307 AstNode::LongNumberLiteral(i) => format!("Long: {}", i),
308 AstNode::DateLiteral(d, _) => format!("Date: {}", d),
309 AstNode::DateTimeLiteral(dt, _, _) => format!("DateTime: {}", dt),
310 AstNode::TimeLiteral(t, _) => format!("Time: {}", t),
311 AstNode::QuantityLiteral { value, unit } => {
312 format!("Quantity: {} {:?}", value, unit)
313 }
314 AstNode::CollectionLiteral { elements } => format!("Collection[{}]", elements.len()),
315 AstNode::TermExpression { .. } => "Term".to_string(),
316 AstNode::InvocationTerm { .. } => "Invocation".to_string(),
317 AstNode::LiteralTerm { .. } => "Literal".to_string(),
318 AstNode::ParenthesizedTerm { .. } => "()".to_string(),
319 AstNode::ExternalConstantTerm { constant } => format!("External: {}", constant),
320 AstNode::MemberInvocation { identifier } => format!("Field: {}", identifier),
321 AstNode::FunctionInvocation { function_name, .. } => format!("Fn: {}()", function_name),
322 AstNode::ThisInvocation => "$this".to_string(),
323 AstNode::IndexInvocation => "$index".to_string(),
324 AstNode::TotalInvocation => "$total".to_string(),
325 AstNode::InvocationExpression { .. } => "Path".to_string(),
326 AstNode::IndexerExpression { .. } => "[]".to_string(),
327 AstNode::PolarityExpression { operator, .. } => format!("Polarity: {:?}", operator),
328 AstNode::MultiplicativeExpression { operator, .. } => format!("Mul: {:?}", operator),
329 AstNode::AdditiveExpression { operator, .. } => format!("Add: {:?}", operator),
330 AstNode::UnionExpression { .. } => "|".to_string(),
331 AstNode::InequalityExpression { operator, .. } => format!("Cmp: {:?}", operator),
332 AstNode::EqualityExpression { operator, .. } => format!("Eq: {:?}", operator),
333 AstNode::MembershipExpression { operator, .. } => format!("Member: {:?}", operator),
334 AstNode::AndExpression { .. } => "and".to_string(),
335 AstNode::OrExpression { operator, .. } => format!("{:?}", operator),
336 AstNode::ImpliesExpression { .. } => "implies".to_string(),
337 AstNode::TypeExpression { operator, .. } => format!("Type: {:?}", operator),
338 }
339}
340
341impl Visualize for HirNode {
346 fn visualize(&self, format: VisualizationFormat) -> String {
347 match format {
348 VisualizationFormat::Mermaid => visualize_hir_mermaid(self),
349 VisualizationFormat::Dot => visualize_hir_dot(self),
350 VisualizationFormat::AsciiTree => visualize_hir_ascii(self, 0),
351 }
352 }
353}
354
355fn format_expr_type(ty: &ExprType) -> String {
356 let types = if ty.types.is_unknown() {
357 "Any".to_string()
358 } else {
359 ty.types
360 .iter()
361 .map(|t| match t.namespace {
362 TypeNamespace::System => format!("System.{}", t.name),
363 TypeNamespace::Fhir => format!("FHIR.{}", t.name),
364 })
365 .collect::<Vec<_>>()
366 .join("|")
367 };
368
369 let card = match ty.cardinality.max {
370 Some(max) => format!("{}..{}", ty.cardinality.min, max),
371 None => format!("{}..*", ty.cardinality.min),
372 };
373
374 format!("[{}] {}", types, card)
375}
376
377fn format_expr_type_compact(ty: &ExprType) -> String {
378 let types = if ty.types.is_unknown() {
379 "Any".to_string()
380 } else {
381 ty.types
382 .iter()
383 .map(|t| match t.namespace {
384 TypeNamespace::System => t.name.as_ref().to_string(),
385 TypeNamespace::Fhir => format!("{}", t.name),
386 })
387 .collect::<Vec<_>>()
388 .join("|")
389 };
390
391 let card = match ty.cardinality.max {
392 Some(max) if ty.cardinality.min == max && max == 1 => "".to_string(),
393 Some(max) => format!(" [{}..{}]", ty.cardinality.min, max),
394 None => format!(" [{}..*]", ty.cardinality.min),
395 };
396
397 format!("{}{}", types, card)
398}
399
400fn visualize_hir_mermaid(node: &HirNode) -> String {
401 let mut output = String::from("graph TD\n");
402 let mut counter = 0;
403 visit_hir_mermaid(node, &mut counter, None, &mut output);
404 output
405}
406
407fn visit_hir_mermaid(
408 node: &HirNode,
409 counter: &mut usize,
410 parent_id: Option<usize>,
411 output: &mut String,
412) {
413 let current_id = *counter;
414 *counter += 1;
415
416 let label = hir_node_label_with_types(node);
417 let _ = writeln!(
418 output,
419 " n{}[\"{}\"]",
420 current_id, label
421 );
422
423 if let Some(parent) = parent_id {
424 let _ = writeln!(output, " n{} --> n{}", parent, current_id);
425 }
426
427 match node {
429 HirNode::Path { base, .. } => {
430 visit_hir_mermaid(base, counter, Some(current_id), output);
431 }
432 HirNode::BinaryOp { left, right, .. } => {
433 visit_hir_mermaid(left, counter, Some(current_id), output);
434 visit_hir_mermaid(right, counter, Some(current_id), output);
435 }
436 HirNode::UnaryOp { expr, .. } => {
437 visit_hir_mermaid(expr, counter, Some(current_id), output);
438 }
439 HirNode::FunctionCall { args, .. } => {
440 for arg in args {
441 visit_hir_mermaid(arg, counter, Some(current_id), output);
442 }
443 }
444 HirNode::MethodCall { base, args, .. } => {
445 visit_hir_mermaid(base, counter, Some(current_id), output);
446 for arg in args {
447 visit_hir_mermaid(arg, counter, Some(current_id), output);
448 }
449 }
450 HirNode::Where {
451 collection,
452 predicate_hir,
453 ..
454 } => {
455 visit_hir_mermaid(collection, counter, Some(current_id), output);
456 visit_hir_mermaid(predicate_hir, counter, Some(current_id), output);
457 }
458 HirNode::Select {
459 collection,
460 projection_hir,
461 ..
462 } => {
463 visit_hir_mermaid(collection, counter, Some(current_id), output);
464 visit_hir_mermaid(projection_hir, counter, Some(current_id), output);
465 }
466 HirNode::Repeat {
467 collection,
468 projection_hir,
469 ..
470 } => {
471 visit_hir_mermaid(collection, counter, Some(current_id), output);
472 visit_hir_mermaid(projection_hir, counter, Some(current_id), output);
473 }
474 HirNode::Aggregate {
475 collection,
476 aggregator_hir,
477 init_value_hir,
478 ..
479 } => {
480 visit_hir_mermaid(collection, counter, Some(current_id), output);
481 visit_hir_mermaid(aggregator_hir, counter, Some(current_id), output);
482 if let Some(init) = init_value_hir {
483 visit_hir_mermaid(init, counter, Some(current_id), output);
484 }
485 }
486 HirNode::Exists {
487 collection,
488 predicate_hir,
489 ..
490 } => {
491 visit_hir_mermaid(collection, counter, Some(current_id), output);
492 if let Some(pred) = predicate_hir {
493 visit_hir_mermaid(pred, counter, Some(current_id), output);
494 }
495 }
496 HirNode::TypeOp { expr, .. } => {
497 visit_hir_mermaid(expr, counter, Some(current_id), output);
498 }
499 _ => {} }
501}
502
503fn visualize_hir_dot(node: &HirNode) -> String {
504 let mut output = String::from("digraph HIR {\n");
505 output.push_str(" node [shape=box, style=\"rounded,filled\", fillcolor=lightblue];\n");
506 let mut counter = 0;
507 visit_hir_dot(node, &mut counter, None, &mut output);
508 output.push_str("}\n");
509 output
510}
511
512fn visit_hir_dot(
513 node: &HirNode,
514 counter: &mut usize,
515 parent_id: Option<usize>,
516 output: &mut String,
517) {
518 let current_id = *counter;
519 *counter += 1;
520
521 let label = hir_node_label_with_types(node);
522 let escaped_label = label.replace('"', "\\\"").replace('\n', "\\n");
524 let _ = writeln!(
525 output,
526 " n{} [label=\"{}\"];",
527 current_id, escaped_label
528 );
529
530 if let Some(parent) = parent_id {
531 let _ = writeln!(output, " n{} -> n{};", parent, current_id);
532 }
533
534 match node {
536 HirNode::Path { base, .. } => {
537 visit_hir_dot(base, counter, Some(current_id), output);
538 }
539 HirNode::BinaryOp { left, right, .. } => {
540 visit_hir_dot(left, counter, Some(current_id), output);
541 visit_hir_dot(right, counter, Some(current_id), output);
542 }
543 HirNode::UnaryOp { expr, .. } => {
544 visit_hir_dot(expr, counter, Some(current_id), output);
545 }
546 HirNode::FunctionCall { args, .. } => {
547 for arg in args {
548 visit_hir_dot(arg, counter, Some(current_id), output);
549 }
550 }
551 HirNode::MethodCall { base, args, .. } => {
552 visit_hir_dot(base, counter, Some(current_id), output);
553 for arg in args {
554 visit_hir_dot(arg, counter, Some(current_id), output);
555 }
556 }
557 HirNode::Where {
558 collection,
559 predicate_hir,
560 ..
561 } => {
562 visit_hir_dot(collection, counter, Some(current_id), output);
563 visit_hir_dot(predicate_hir, counter, Some(current_id), output);
564 }
565 HirNode::Select {
566 collection,
567 projection_hir,
568 ..
569 } => {
570 visit_hir_dot(collection, counter, Some(current_id), output);
571 visit_hir_dot(projection_hir, counter, Some(current_id), output);
572 }
573 HirNode::Repeat {
574 collection,
575 projection_hir,
576 ..
577 } => {
578 visit_hir_dot(collection, counter, Some(current_id), output);
579 visit_hir_dot(projection_hir, counter, Some(current_id), output);
580 }
581 HirNode::Aggregate {
582 collection,
583 aggregator_hir,
584 init_value_hir,
585 ..
586 } => {
587 visit_hir_dot(collection, counter, Some(current_id), output);
588 visit_hir_dot(aggregator_hir, counter, Some(current_id), output);
589 if let Some(init) = init_value_hir {
590 visit_hir_dot(init, counter, Some(current_id), output);
591 }
592 }
593 HirNode::Exists {
594 collection,
595 predicate_hir,
596 ..
597 } => {
598 visit_hir_dot(collection, counter, Some(current_id), output);
599 if let Some(pred) = predicate_hir {
600 visit_hir_dot(pred, counter, Some(current_id), output);
601 }
602 }
603 HirNode::TypeOp { expr, .. } => {
604 visit_hir_dot(expr, counter, Some(current_id), output);
605 }
606 _ => {} }
608}
609
610fn visualize_hir_ascii(node: &HirNode, depth: usize) -> String {
611 let indent = " ".repeat(depth);
612 let label = hir_node_label_with_types(node);
613 let mut output = format!("{}├─ {}\n", indent, label);
614
615 match node {
617 HirNode::Path { base, segments, .. } => {
618 output.push_str(&visualize_hir_ascii(base, depth + 1));
619 for seg in segments {
620 output.push_str(&format!("{} ├─ .{:?}\n", indent, seg));
621 }
622 }
623 HirNode::BinaryOp { left, right, .. } => {
624 output.push_str(&visualize_hir_ascii(left, depth + 1));
625 output.push_str(&visualize_hir_ascii(right, depth + 1));
626 }
627 HirNode::UnaryOp { expr, .. } => {
628 output.push_str(&visualize_hir_ascii(expr, depth + 1));
629 }
630 HirNode::FunctionCall { args, .. } => {
631 for arg in args {
632 output.push_str(&visualize_hir_ascii(arg, depth + 1));
633 }
634 }
635 HirNode::MethodCall { base, args, .. } => {
636 output.push_str(&visualize_hir_ascii(base, depth + 1));
637 for arg in args {
638 output.push_str(&visualize_hir_ascii(arg, depth + 1));
639 }
640 }
641 HirNode::Where {
642 collection,
643 predicate_hir,
644 ..
645 } => {
646 output.push_str(&visualize_hir_ascii(collection, depth + 1));
647 output.push_str(&format!("{} ├─ [predicate]\n", indent));
648 output.push_str(&visualize_hir_ascii(predicate_hir, depth + 2));
649 }
650 HirNode::Select {
651 collection,
652 projection_hir,
653 ..
654 } => {
655 output.push_str(&visualize_hir_ascii(collection, depth + 1));
656 output.push_str(&format!("{} ├─ [projection]\n", indent));
657 output.push_str(&visualize_hir_ascii(projection_hir, depth + 2));
658 }
659 HirNode::Repeat {
660 collection,
661 projection_hir,
662 ..
663 } => {
664 output.push_str(&visualize_hir_ascii(collection, depth + 1));
665 output.push_str(&format!("{} ├─ [repeat]\n", indent));
666 output.push_str(&visualize_hir_ascii(projection_hir, depth + 2));
667 }
668 HirNode::Aggregate {
669 collection,
670 aggregator_hir,
671 init_value_hir,
672 ..
673 } => {
674 output.push_str(&visualize_hir_ascii(collection, depth + 1));
675 output.push_str(&format!("{} ├─ [aggregator]\n", indent));
676 output.push_str(&visualize_hir_ascii(aggregator_hir, depth + 2));
677 if let Some(init) = init_value_hir {
678 output.push_str(&format!("{} ├─ [init]\n", indent));
679 output.push_str(&visualize_hir_ascii(init, depth + 2));
680 }
681 }
682 HirNode::Exists {
683 collection,
684 predicate_hir,
685 ..
686 } => {
687 output.push_str(&visualize_hir_ascii(collection, depth + 1));
688 if let Some(pred) = predicate_hir {
689 output.push_str(&format!("{} ├─ [predicate]\n", indent));
690 output.push_str(&visualize_hir_ascii(pred, depth + 2));
691 }
692 }
693 HirNode::TypeOp { expr, .. } => {
694 output.push_str(&visualize_hir_ascii(expr, depth + 1));
695 }
696 _ => {} }
698
699 output
700}
701
702#[allow(dead_code)]
703fn hir_node_label(node: &HirNode) -> String {
704 match node {
705 HirNode::Literal { value, .. } => format!("Lit: {:?}", value),
706 HirNode::Variable { var_id, name, .. } => {
707 if let Some(n) = name {
708 format!("Var: {}", n)
709 } else {
710 format!("Var[{}]", var_id)
711 }
712 }
713 HirNode::Path { segments, .. } => {
714 let path = segments
715 .iter()
716 .filter_map(|s| s.as_field())
717 .collect::<Vec<_>>()
718 .join(".");
719 format!("Path: {}", path)
720 }
721 HirNode::BinaryOp { op, .. } => format!("BinOp: {:?}", op),
722 HirNode::UnaryOp { op, .. } => format!("UnaryOp: {:?}", op),
723 HirNode::FunctionCall { func_id, args, .. } => format!("Fn[{}]({})", func_id, args.len()),
724 HirNode::MethodCall { func_id, args, .. } => format!("Method[{}]({})", func_id, args.len()),
725 HirNode::Where { .. } => "where()".to_string(),
726 HirNode::Select { .. } => "select()".to_string(),
727 HirNode::Repeat { .. } => "repeat()".to_string(),
728 HirNode::Aggregate { .. } => "aggregate()".to_string(),
729 HirNode::All { .. } => "all()".to_string(),
730 HirNode::Exists { .. } => "exists()".to_string(),
731 HirNode::TypeOp {
732 op, type_specifier, ..
733 } => format!("{:?} {}", op, type_specifier),
734 }
735}
736
737fn hir_node_label_with_types(node: &HirNode) -> String {
738
739 match node {
740 HirNode::Literal { value, ty } => {
741 format!("Lit: {:?}\nType: {}", value, format_expr_type(ty))
742 }
743 HirNode::Variable { var_id, name, ty } => {
744 let var_name = if let Some(n) = name {
745 format!("Var: {}", n)
746 } else {
747 format!("Var[{}]", var_id)
748 };
749 format!("{}\nType: {}", var_name, format_expr_type(ty))
750 }
751 HirNode::Path { segments, result_ty, base } => {
752 let path = segments
753 .iter()
754 .filter_map(|s| s.as_field())
755 .collect::<Vec<_>>()
756 .join(".");
757 let base_type = base.result_type()
758 .map(|t| format!("Base: {}\n", format_expr_type_compact(&t)))
759 .unwrap_or_default();
760 format!("Path: {}\n{}Result: {}", path, base_type, format_expr_type(result_ty))
761 }
762 HirNode::BinaryOp { op, left, right, result_ty, .. } => {
763 let left_type = left.result_type()
764 .map(|t| format_expr_type_compact(&t))
765 .unwrap_or_else(|| "?".to_string());
766 let right_type = right.result_type()
767 .map(|t| format_expr_type_compact(&t))
768 .unwrap_or_else(|| "?".to_string());
769 format!("BinOp: {:?}\nLeft: {}\nRight: {}\nResult: {}",
770 op, left_type, right_type, format_expr_type(result_ty))
771 }
772 HirNode::UnaryOp { op, expr, result_ty } => {
773 let expr_type = expr.result_type()
774 .map(|t| format_expr_type_compact(&t))
775 .unwrap_or_else(|| "?".to_string());
776 format!("UnaryOp: {:?}\nOperand: {}\nResult: {}",
777 op, expr_type, format_expr_type(result_ty))
778 }
779 HirNode::FunctionCall { func_id, args, result_ty } => {
780 let arg_types: Vec<String> = args.iter()
781 .map(|arg| arg.result_type()
782 .map(|t| format_expr_type_compact(&t))
783 .unwrap_or_else(|| "?".to_string()))
784 .collect();
785 format!("Fn[{}]({})\nArgs: {}\nResult: {}",
786 func_id, args.len(), arg_types.join(", "), format_expr_type(result_ty))
787 }
788 HirNode::MethodCall { base, func_id, args, result_ty } => {
789 let base_type = base.result_type()
790 .map(|t| format_expr_type_compact(&t))
791 .unwrap_or_else(|| "?".to_string());
792 let arg_types: Vec<String> = args.iter()
793 .map(|arg| arg.result_type()
794 .map(|t| format_expr_type_compact(&t))
795 .unwrap_or_else(|| "?".to_string()))
796 .collect();
797 format!("Method[{}]({})\nBase: {}\nArgs: {}\nResult: {}",
798 func_id, args.len(), base_type, arg_types.join(", "), format_expr_type(result_ty))
799 }
800 HirNode::Where { collection, predicate_hir, result_ty, .. } => {
801 let coll_type = collection.result_type()
802 .map(|t| format_expr_type_compact(&t))
803 .unwrap_or_else(|| "?".to_string());
804 let pred_type = predicate_hir.result_type()
805 .map(|t| format_expr_type_compact(&t))
806 .unwrap_or_else(|| "?".to_string());
807 format!("where()\nCollection: {}\nPredicate: {}\nResult: {}",
808 coll_type, pred_type, format_expr_type(result_ty))
809 }
810 HirNode::Select { collection, projection_hir, result_ty, .. } => {
811 let coll_type = collection.result_type()
812 .map(|t| format_expr_type_compact(&t))
813 .unwrap_or_else(|| "?".to_string());
814 let proj_type = projection_hir.result_type()
815 .map(|t| format_expr_type_compact(&t))
816 .unwrap_or_else(|| "?".to_string());
817 format!("select()\nCollection: {}\nProjection: {}\nResult: {}",
818 coll_type, proj_type, format_expr_type(result_ty))
819 }
820 HirNode::Repeat { collection, projection_hir, result_ty, .. } => {
821 let coll_type = collection.result_type()
822 .map(|t| format_expr_type_compact(&t))
823 .unwrap_or_else(|| "?".to_string());
824 let proj_type = projection_hir.result_type()
825 .map(|t| format_expr_type_compact(&t))
826 .unwrap_or_else(|| "?".to_string());
827 format!("repeat()\nCollection: {}\nProjection: {}\nResult: {}",
828 coll_type, proj_type, format_expr_type(result_ty))
829 }
830 HirNode::Aggregate { collection, aggregator_hir, init_value_hir, result_ty, .. } => {
831 let coll_type = collection.result_type()
832 .map(|t| format_expr_type_compact(&t))
833 .unwrap_or_else(|| "?".to_string());
834 let agg_type = aggregator_hir.result_type()
835 .map(|t| format_expr_type_compact(&t))
836 .unwrap_or_else(|| "?".to_string());
837 let init_type = init_value_hir.as_ref()
838 .and_then(|init| init.result_type())
839 .map(|t| format_expr_type_compact(&t))
840 .unwrap_or_else(|| "None".to_string());
841 format!("aggregate()\nCollection: {}\nAggregator: {}\nInit: {}\nResult: {}",
842 coll_type, agg_type, init_type, format_expr_type(result_ty))
843 }
844 HirNode::All { collection, predicate_hir, result_ty, .. } => {
845 let coll_type = collection.result_type()
846 .map(|t| format_expr_type_compact(&t))
847 .unwrap_or_else(|| "?".to_string());
848 let pred_type = predicate_hir.result_type()
849 .map(|t| format_expr_type_compact(&t))
850 .unwrap_or_else(|| "?".to_string());
851 format!("all()\nCollection: {}\nPredicate: {}\nResult: {}",
852 coll_type, pred_type, format_expr_type(result_ty))
853 }
854 HirNode::Exists { collection, predicate_hir, result_ty, .. } => {
855 let coll_type = collection.result_type()
856 .map(|t| format_expr_type_compact(&t))
857 .unwrap_or_else(|| "?".to_string());
858 let pred_type = predicate_hir.as_ref()
859 .and_then(|pred| pred.result_type())
860 .map(|t| format_expr_type_compact(&t))
861 .unwrap_or_else(|| "None".to_string());
862 format!("exists()\nCollection: {}\nPredicate: {}\nResult: {}",
863 coll_type, pred_type, format_expr_type(result_ty))
864 }
865 HirNode::TypeOp { op, expr, type_specifier, result_ty } => {
866 let expr_type = expr.result_type()
867 .map(|t| format_expr_type_compact(&t))
868 .unwrap_or_else(|| "?".to_string());
869 format!("{:?} {}\nOperand: {}\nResult: {}",
870 op, type_specifier, expr_type, format_expr_type(result_ty))
871 }
872 }
873}
874
875impl Visualize for Plan {
880 fn visualize(&self, format: VisualizationFormat) -> String {
881 match format {
882 VisualizationFormat::Mermaid => visualize_plan_mermaid(self),
883 VisualizationFormat::Dot => visualize_plan_dot(self),
884 VisualizationFormat::AsciiTree => visualize_plan_ascii(self),
885 }
886 }
887}
888
889fn visualize_plan_mermaid(plan: &Plan) -> String {
890 let mut output = String::from("graph LR\n");
891 output.push_str(" subgraph \"Main Plan\"\n");
892
893 for (i, opcode) in plan.opcodes.iter().enumerate() {
894 let label = format_opcode(opcode, plan);
895 let _ = writeln!(output, " i{}[\"{}. {}\"]", i, i, label);
896 if i > 0 {
897 let _ = writeln!(output, " i{} --> i{}", i - 1, i);
898 }
899 }
900
901 output.push_str(" end\n");
902
903 for (id, subplan) in plan.subplans.iter().enumerate() {
905 output.push_str(&format!(" subgraph \"Subplan {}\"\n", id));
906 for (i, opcode) in subplan.opcodes.iter().enumerate() {
907 let label = format_opcode(opcode, subplan);
908 let _ = writeln!(output, " s{}_{}[\"{}. {}\"]", id, i, i, label);
909 if i > 0 {
910 let _ = writeln!(output, " s{}_{} --> s{}_{}", id, i - 1, id, i);
911 }
912 }
913 output.push_str(" end\n");
914 }
915
916 output
917}
918
919fn visualize_plan_dot(plan: &Plan) -> String {
920 let mut output = String::from("digraph Plan {\n");
921 output.push_str(" rankdir=TB;\n");
922 output.push_str(" node [shape=box, style=\"rounded,filled\", fillcolor=lightyellow];\n");
923
924 output.push_str(" subgraph cluster_main {\n");
925 output.push_str(" label=\"Main Plan\";\n");
926 output.push_str(" style=filled;\n");
927 output.push_str(" color=lightgrey;\n");
928
929 for (i, opcode) in plan.opcodes.iter().enumerate() {
930 let label = format_opcode(opcode, plan);
931 let _ = writeln!(output, " i{} [label=\"{}. {}\"];", i, i, label);
932 if i > 0 {
933 let _ = writeln!(output, " i{} -> i{};", i - 1, i);
934 }
935 }
936
937 output.push_str(" }\n");
938
939 for (id, subplan) in plan.subplans.iter().enumerate() {
941 output.push_str(&format!(" subgraph cluster_sub{} {{\n", id));
942 output.push_str(&format!(" label=\"Subplan {}\";\n", id));
943 output.push_str(" style=filled;\n");
944 output.push_str(" color=lightblue;\n");
945
946 for (i, opcode) in subplan.opcodes.iter().enumerate() {
947 let label = format_opcode(opcode, subplan);
948 let _ = writeln!(
949 output,
950 " s{}_i{} [label=\"{}. {}\"];",
951 id, i, i, label
952 );
953 if i > 0 {
954 let _ = writeln!(output, " s{}_i{} -> s{}_i{};", id, i - 1, id, i);
955 }
956 }
957
958 output.push_str(" }\n");
959 }
960
961 output.push_str("}\n");
962 output
963}
964
965fn visualize_plan_ascii(plan: &Plan) -> String {
966 let mut output = String::from("VM Plan\n");
967 output.push_str("========\n\n");
968 output.push_str("Main Opcodes:\n");
969
970 for (i, opcode) in plan.opcodes.iter().enumerate() {
971 let label = format_opcode(opcode, plan);
972 output.push_str(&format!(" {:3}: {}\n", i, label));
973 }
974
975 if !plan.subplans.is_empty() {
976 output.push_str("\nSubplans:\n");
977 for (id, subplan) in plan.subplans.iter().enumerate() {
978 output.push_str(&format!("\n Subplan {}:\n", id));
979 for (i, opcode) in subplan.opcodes.iter().enumerate() {
980 let label = format_opcode(opcode, subplan);
981 output.push_str(&format!(" {:3}: {}\n", i, label));
982 }
983 }
984 }
985
986 output
987}
988
989fn format_opcode(opcode: &Opcode, plan: &Plan) -> String {
990 match opcode {
991 Opcode::PushConst(idx) => {
992 let value = plan
993 .constants
994 .get(*idx as usize)
995 .map(|v| format!("{:?}", v))
996 .unwrap_or_else(|| "?".to_string());
997 format!("PUSH_CONST[{}] = {}", idx, value)
998 }
999 Opcode::PushVariable(id) => format!("PUSH_VAR ${}", id),
1000 Opcode::LoadThis => "LOAD_THIS".to_string(),
1001 Opcode::LoadIndex => "LOAD_INDEX".to_string(),
1002 Opcode::LoadTotal => "LOAD_TOTAL".to_string(),
1003 Opcode::Pop => "POP".to_string(),
1004 Opcode::Dup => "DUP".to_string(),
1005 Opcode::Navigate(idx) => {
1006 let field = plan
1007 .segments
1008 .get(*idx as usize)
1009 .map(|s| s.as_ref())
1010 .unwrap_or("?");
1011 format!("NAVIGATE .{}", field)
1012 }
1013 Opcode::Index(idx) => format!("INDEX [{}]", idx),
1014 Opcode::CallBinary(impl_id) => format!("CALL_BINARY #{}", impl_id),
1015 Opcode::CallUnary(op) => {
1016 let op_name = match *op {
1017 0 => "+",
1018 1 => "-",
1019 _ => "?",
1020 };
1021 format!("CALL_UNARY {}", op_name)
1022 }
1023 Opcode::TypeIs(idx) => {
1024 let type_spec = plan
1025 .type_specifiers
1026 .get(*idx as usize)
1027 .map(|s| s.as_str())
1028 .unwrap_or("?");
1029 format!("TYPE_IS {}", type_spec)
1030 }
1031 Opcode::TypeAs(idx) => {
1032 let type_spec = plan
1033 .type_specifiers
1034 .get(*idx as usize)
1035 .map(|s| s.as_str())
1036 .unwrap_or("?");
1037 format!("TYPE_AS {}", type_spec)
1038 }
1039 Opcode::CallFunction(func_id, argc) => format!("CALL_FN #{}({})", func_id, argc),
1040 Opcode::Where(plan_id) => format!("WHERE subplan[{}]", plan_id),
1041 Opcode::Select(plan_id) => format!("SELECT subplan[{}]", plan_id),
1042 Opcode::Repeat(plan_id) => format!("REPEAT subplan[{}]", plan_id),
1043 Opcode::Aggregate(plan_id, init_id) => {
1044 if let Some(init) = init_id {
1045 format!("AGGREGATE subplan[{}] init[{}]", plan_id, init)
1046 } else {
1047 format!("AGGREGATE subplan[{}]", plan_id)
1048 }
1049 }
1050 Opcode::Exists(pred_id) => {
1051 if let Some(pred) = pred_id {
1052 format!("EXISTS subplan[{}]", pred)
1053 } else {
1054 "EXISTS".to_string()
1055 }
1056 }
1057 Opcode::All(plan_id) => format!("ALL subplan[{}]", plan_id),
1058 Opcode::Jump(target) => format!("JUMP {}", target),
1059 Opcode::JumpIfEmpty(target) => format!("JUMP_IF_EMPTY {}", target),
1060 Opcode::JumpIfNotEmpty(target) => format!("JUMP_IF_NOT_EMPTY {}", target),
1061 Opcode::Iif(true_plan, false_plan, else_plan) => {
1062 if let Some(else_id) = else_plan {
1063 format!(
1064 "IIF true[{}] false[{}] else[{}]",
1065 true_plan, false_plan, else_id
1066 )
1067 } else {
1068 format!("IIF true[{}] false[{}]", true_plan, false_plan)
1069 }
1070 }
1071 Opcode::Return => "RETURN".to_string(),
1072 }
1073}
1074
1075#[cfg(test)]
1076mod tests {
1077 use super::*;
1078
1079 #[test]
1080 fn test_ast_visualization_formats() {
1081 let node = AstNode::IntegerLiteral(42);
1082
1083 let _ = node.visualize(VisualizationFormat::Mermaid);
1085 let _ = node.visualize(VisualizationFormat::Dot);
1086 let ascii = node.visualize(VisualizationFormat::AsciiTree);
1087
1088 assert!(ascii.contains("Integer: 42"));
1089 }
1090}