tlq_fhirpath/
visualize.rs

1//! Visualization utilities for compiler intermediate representations
2//!
3//! This module provides visualization capabilities for:
4//! - AST (Abstract Syntax Tree)
5//! - HIR (High-level Intermediate Representation)
6//! - VM Plans (bytecode)
7//!
8//! Supports multiple output formats:
9//! - Mermaid diagrams (for markdown/web rendering)
10//! - DOT/Graphviz (for generating PNG/SVG)
11//! - ASCII tree (for terminal viewing)
12
13use 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/// Visualization format
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum VisualizationFormat {
22    /// Mermaid diagram format (renders in markdown, GitHub, etc.)
23    Mermaid,
24    /// DOT/Graphviz format (can be rendered with `dot` command)
25    Dot,
26    /// ASCII tree format (for terminal viewing)
27    AsciiTree,
28}
29
30/// Trait for types that can be visualized
31pub trait Visualize {
32    /// Generate visualization in the specified format
33    fn visualize(&self, format: VisualizationFormat) -> String;
34}
35
36// =============================================================================
37// AST Visualization
38// =============================================================================
39
40impl 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    // Visit children
74    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        _ => {} // Leaf nodes
135    }
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    // Visit children (same logic as mermaid)
164    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        _ => {} // Leaf nodes
229    }
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    // Visit children
237    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        _ => {} // Leaf nodes
295    }
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
341// =============================================================================
342// HIR Visualization
343// =============================================================================
344
345impl 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    // Visit children
428    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        _ => {} // Leaf nodes
500    }
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    // Escape special characters for DOT format
523    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    // Visit children (same as mermaid)
535    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        _ => {} // Leaf nodes
607    }
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    // Visit children
616    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        _ => {} // Leaf nodes
697    }
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
875// =============================================================================
876// VM Plan Visualization
877// =============================================================================
878
879impl 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    // Show subplans
904    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    // Show subplans
940    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        // Test all formats compile
1084        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}