1use std::{collections::HashSet, fmt};
2
3use num::complex::Complex64;
4
5use crate::{
6 BinaryOp, ExprGraph, ExprId, ExprMetadata, ExprNode, UnaryOp, expression::node_children,
7};
8
9#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
11pub enum ExprNodeKind {
12 RealConst,
14 ComplexConst,
16 ScalarParam,
18 EventScalar,
20 EventP4Component,
22 Unary,
24 Binary,
26 NaryAdd,
28 NaryMul,
30 Complex,
32 Vector,
34 Matrix,
36 Component,
38 MatrixElement,
40 MatMul,
42 MatVec,
44 Dot,
46 Solve,
48}
49
50impl ExprNodeKind {
51 pub fn of(node: &ExprNode) -> Self {
53 match node {
54 ExprNode::RealConst(_) => Self::RealConst,
55 ExprNode::ComplexConst(_) => Self::ComplexConst,
56 ExprNode::ScalarParam(_) => Self::ScalarParam,
57 ExprNode::EventScalar(_) => Self::EventScalar,
58 ExprNode::EventP4Component { .. } => Self::EventP4Component,
59 ExprNode::Unary { .. } => Self::Unary,
60 ExprNode::Binary { .. } => Self::Binary,
61 ExprNode::NaryAdd { .. } => Self::NaryAdd,
62 ExprNode::NaryMul { .. } => Self::NaryMul,
63 ExprNode::Complex { .. } => Self::Complex,
64 ExprNode::Vector { .. } => Self::Vector,
65 ExprNode::Matrix { .. } => Self::Matrix,
66 ExprNode::Component { .. } => Self::Component,
67 ExprNode::MatrixElement { .. } => Self::MatrixElement,
68 ExprNode::MatMul { .. } => Self::MatMul,
69 ExprNode::MatVec { .. } => Self::MatVec,
70 ExprNode::Dot { .. } => Self::Dot,
71 ExprNode::Solve { .. } => Self::Solve,
72 }
73 }
74}
75
76#[derive(Copy, Clone, Debug, PartialEq, Eq)]
78pub struct DisplayColor {
79 red: u8,
80 green: u8,
81 blue: u8,
82}
83
84impl DisplayColor {
85 pub const fn rgb(red: u8, green: u8, blue: u8) -> Self {
87 Self { red, green, blue }
88 }
89
90 fn dot(self) -> String {
91 format!("#{:02x}{:02x}{:02x}", self.red, self.green, self.blue)
92 }
93
94 fn ansi_foreground(self) -> String {
95 format!("\x1b[38;2;{};{};{}m", self.red, self.green, self.blue)
96 }
97
98 fn ansi_background(self) -> String {
99 format!("\x1b[48;2;{};{};{}m", self.red, self.green, self.blue)
100 }
101}
102
103#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
105pub struct NodeStyle {
106 pub foreground: Option<DisplayColor>,
108 pub fill: Option<DisplayColor>,
110 pub border: Option<DisplayColor>,
112}
113
114impl NodeStyle {
115 pub const fn new() -> Self {
117 Self {
118 foreground: None,
119 fill: None,
120 border: None,
121 }
122 }
123
124 pub const fn with_foreground(mut self, color: DisplayColor) -> Self {
126 self.foreground = Some(color);
127 self
128 }
129
130 pub const fn with_fill(mut self, color: DisplayColor) -> Self {
132 self.fill = Some(color);
133 self
134 }
135
136 pub const fn with_border(mut self, color: DisplayColor) -> Self {
138 self.border = Some(color);
139 self
140 }
141
142 fn overlay(&mut self, other: Self) {
143 if other.foreground.is_some() {
144 self.foreground = other.foreground;
145 }
146 if other.fill.is_some() {
147 self.fill = other.fill;
148 }
149 if other.border.is_some() {
150 self.border = other.border;
151 }
152 }
153
154 fn ansi(self, text: String) -> String {
155 if self.foreground.is_none() && self.fill.is_none() {
156 return text;
157 }
158 let mut prefix = String::new();
159 if let Some(color) = self.foreground {
160 prefix.push_str(&color.ansi_foreground());
161 }
162 if let Some(color) = self.fill {
163 prefix.push_str(&color.ansi_background());
164 }
165 format!("{prefix}{text}\x1b[0m")
166 }
167
168 fn latex(self, text: String) -> String {
169 self.foreground.map_or(text.clone(), |color| {
170 format!(
171 "{{\\color[RGB]{{{},{},{}}}{text}}}",
172 color.red, color.green, color.blue
173 )
174 })
175 }
176}
177
178#[derive(Clone, Debug, PartialEq, Eq)]
180pub enum NodeSelector {
181 Any,
183 Kind(ExprNodeKind),
185 Name(String),
187 Tag(String),
189}
190
191impl NodeSelector {
192 fn matches(&self, node: &ExprNode, metadata: Option<&ExprMetadata>) -> bool {
193 match self {
194 Self::Any => true,
195 Self::Kind(kind) => *kind == ExprNodeKind::of(node),
196 Self::Name(name) => {
197 metadata.and_then(ExprMetadata::name) == Some(name.as_str())
198 || match node {
199 ExprNode::ScalarParam(parameter) => parameter.name() == name,
200 ExprNode::EventScalar(node_name)
201 | ExprNode::EventP4Component {
202 name: node_name, ..
203 } => node_name.as_ref() == name,
204 _ => false,
205 }
206 }
207 Self::Tag(tag) => metadata.is_some_and(|metadata| metadata.has_tag(tag)),
208 }
209 }
210}
211
212#[derive(Clone, Debug, PartialEq, Eq)]
214pub struct NodeStyleRule {
215 pub selector: NodeSelector,
217 pub style: NodeStyle,
219}
220
221impl NodeStyleRule {
222 pub fn new(selector: NodeSelector, style: NodeStyle) -> Self {
224 Self { selector, style }
225 }
226}
227
228#[derive(Copy, Clone, Debug, PartialEq, Eq)]
230pub enum ColorPreset {
231 Light,
233 Dark,
235}
236
237#[derive(Clone, Debug)]
238struct DisplayOptions {
239 expand_repeated: bool,
240 rules: Vec<NodeStyleRule>,
241}
242
243impl Default for DisplayOptions {
244 fn default() -> Self {
245 Self {
246 expand_repeated: true,
247 rules: Vec::new(),
248 }
249 }
250}
251
252impl DisplayOptions {
253 fn with_preset(&mut self, preset: ColorPreset) {
254 let (constant, parameter, event, operation, linear_algebra) = match preset {
255 ColorPreset::Light => (
256 DisplayColor::rgb(88, 96, 105),
257 DisplayColor::rgb(0, 92, 197),
258 DisplayColor::rgb(3, 102, 214),
259 DisplayColor::rgb(130, 80, 223),
260 DisplayColor::rgb(207, 34, 46),
261 ),
262 ColorPreset::Dark => (
263 DisplayColor::rgb(139, 148, 158),
264 DisplayColor::rgb(88, 166, 255),
265 DisplayColor::rgb(121, 192, 255),
266 DisplayColor::rgb(210, 168, 255),
267 DisplayColor::rgb(255, 123, 114),
268 ),
269 };
270 let style = |color| NodeStyle::new().with_foreground(color).with_border(color);
271 for kind in [ExprNodeKind::RealConst, ExprNodeKind::ComplexConst] {
272 self.rules.push(NodeStyleRule::new(
273 NodeSelector::Kind(kind),
274 style(constant),
275 ));
276 }
277 self.rules.push(NodeStyleRule::new(
278 NodeSelector::Kind(ExprNodeKind::ScalarParam),
279 style(parameter),
280 ));
281 for kind in [ExprNodeKind::EventScalar, ExprNodeKind::EventP4Component] {
282 self.rules
283 .push(NodeStyleRule::new(NodeSelector::Kind(kind), style(event)));
284 }
285 for kind in [
286 ExprNodeKind::Unary,
287 ExprNodeKind::Binary,
288 ExprNodeKind::NaryAdd,
289 ExprNodeKind::NaryMul,
290 ExprNodeKind::Complex,
291 ExprNodeKind::Vector,
292 ExprNodeKind::Matrix,
293 ExprNodeKind::Component,
294 ExprNodeKind::MatrixElement,
295 ] {
296 self.rules.push(NodeStyleRule::new(
297 NodeSelector::Kind(kind),
298 style(operation),
299 ));
300 }
301 for kind in [
302 ExprNodeKind::MatMul,
303 ExprNodeKind::MatVec,
304 ExprNodeKind::Dot,
305 ExprNodeKind::Solve,
306 ] {
307 self.rules.push(NodeStyleRule::new(
308 NodeSelector::Kind(kind),
309 style(linear_algebra),
310 ));
311 }
312 }
313
314 fn resolve(&self, graph: &ExprGraph, id: ExprId, node: &ExprNode) -> NodeStyle {
315 let mut style = NodeStyle::default();
316 let metadata = graph.metadata(id);
317 for rule in &self.rules {
318 if rule.selector.matches(node, metadata) {
319 style.overlay(rule.style);
320 }
321 }
322 style
323 }
324}
325
326impl ExprGraph {
327 pub fn display_tree(&self) -> crate::ExprGraphTreeDisplay<'_> {
329 crate::ExprGraphTreeDisplay::new(self)
330 }
331
332 pub fn display_equation(&self) -> crate::ExprGraphEquationDisplay<'_> {
334 crate::ExprGraphEquationDisplay::new(self)
335 }
336
337 pub fn display_latex(&self) -> crate::ExprGraphLatexDisplay<'_> {
339 crate::ExprGraphLatexDisplay::new(self)
340 }
341
342 pub fn display_dot(&self) -> crate::ExprGraphDotDisplay<'_> {
344 crate::ExprGraphDotDisplay::new(self)
345 }
346
347 fn format_expression(&self, id: ExprId) -> String {
348 self.format_expression_with(id, &|_, _, text| text)
349 }
350
351 pub(crate) fn format_expression_with(
352 &self,
353 id: ExprId,
354 decorate: &dyn Fn(ExprId, &ExprNode, String) -> String,
355 ) -> String {
356 self.format_child(id, ExprPrecedence::Lowest, false, decorate)
357 }
358
359 fn format_child(
360 &self,
361 id: ExprId,
362 parent_precedence: ExprPrecedence,
363 parenthesize_equal: bool,
364 decorate: &dyn Fn(ExprId, &ExprNode, String) -> String,
365 ) -> String {
366 let (text, precedence) = self.format_node_expression(id, decorate);
367 if precedence < parent_precedence || (parenthesize_equal && precedence == parent_precedence)
368 {
369 format!("({text})")
370 } else {
371 text
372 }
373 }
374
375 fn format_node_expression(
376 &self,
377 id: ExprId,
378 decorate: &dyn Fn(ExprId, &ExprNode, String) -> String,
379 ) -> (String, ExprPrecedence) {
380 let Some(node) = self.node(id) else {
381 return (format!("<missing #{}>", id.index()), ExprPrecedence::Atom);
382 };
383
384 let (text, precedence) = match node {
385 ExprNode::RealConst(value) => (Self::format_real_number(*value), ExprPrecedence::Atom),
386 ExprNode::ComplexConst(value) => self.format_complex_const(*value),
387 ExprNode::ScalarParam(parameter) => (parameter.name().to_owned(), ExprPrecedence::Atom),
388 ExprNode::EventScalar(name) => (name.to_string(), ExprPrecedence::Atom),
389 ExprNode::EventP4Component { name, component } => (
390 format!("{name}.{}", component.label()),
391 ExprPrecedence::Atom,
392 ),
393 ExprNode::Unary { op, input } => self.format_unary_expression(*op, *input, decorate),
394 ExprNode::Binary { op, lhs, rhs } => {
395 self.format_binary_expression(*op, *lhs, *rhs, decorate)
396 }
397 ExprNode::NaryAdd { terms } => self.format_sum_expression(terms, decorate),
398 ExprNode::NaryMul { factors } => self.format_product_expression(factors, decorate),
399 ExprNode::Complex { re, im } => (
400 format!(
401 "complex({}, {})",
402 self.format_expression_with(*re, decorate),
403 self.format_expression_with(*im, decorate)
404 ),
405 ExprPrecedence::Atom,
406 ),
407 ExprNode::Vector { elements } => (
408 format!(
409 "[{}]",
410 elements
411 .iter()
412 .map(|id| self.format_expression_with(*id, decorate))
413 .collect::<Vec<_>>()
414 .join(", ")
415 ),
416 ExprPrecedence::Atom,
417 ),
418 ExprNode::Matrix {
419 rows,
420 cols,
421 elements,
422 } => {
423 let rows = (0..*rows)
424 .map(|row| {
425 let start = row * *cols;
426 let end = start + *cols;
427 format!(
428 "[{}]",
429 elements[start..end]
430 .iter()
431 .map(|id| self.format_expression_with(*id, decorate))
432 .collect::<Vec<_>>()
433 .join(", ")
434 )
435 })
436 .collect::<Vec<_>>()
437 .join(", ");
438 (format!("[{rows}]"), ExprPrecedence::Atom)
439 }
440 ExprNode::Component { input, index } => (
441 format!(
442 "{}[{index}]",
443 self.format_child(*input, ExprPrecedence::Postfix, false, decorate)
444 ),
445 ExprPrecedence::Postfix,
446 ),
447 ExprNode::MatrixElement { input, row, col } => (
448 format!(
449 "{}[{row}, {col}]",
450 self.format_child(*input, ExprPrecedence::Postfix, false, decorate)
451 ),
452 ExprPrecedence::Postfix,
453 ),
454 ExprNode::MatMul { lhs, rhs } => (
455 format!(
456 "matmul({}, {})",
457 self.format_expression_with(*lhs, decorate),
458 self.format_expression_with(*rhs, decorate)
459 ),
460 ExprPrecedence::Atom,
461 ),
462 ExprNode::MatVec { matrix, vector } => (
463 format!(
464 "matvec({}, {})",
465 self.format_expression_with(*matrix, decorate),
466 self.format_expression_with(*vector, decorate)
467 ),
468 ExprPrecedence::Atom,
469 ),
470 ExprNode::Dot { lhs, rhs } => (
471 format!(
472 "dot({}, {})",
473 self.format_expression_with(*lhs, decorate),
474 self.format_expression_with(*rhs, decorate)
475 ),
476 ExprPrecedence::Atom,
477 ),
478 ExprNode::Solve { matrix, rhs } => (
479 format!(
480 "solve({}, {})",
481 self.format_expression_with(*matrix, decorate),
482 self.format_expression_with(*rhs, decorate)
483 ),
484 ExprPrecedence::Atom,
485 ),
486 };
487 (decorate(id, node, text), precedence)
488 }
489
490 fn format_unary_expression(
491 &self,
492 op: UnaryOp,
493 input: ExprId,
494 decorate: &dyn Fn(ExprId, &ExprNode, String) -> String,
495 ) -> (String, ExprPrecedence) {
496 match op {
497 UnaryOp::Neg => (
498 format!(
499 "-{}",
500 self.format_child(input, ExprPrecedence::Unary, true, decorate)
501 ),
502 ExprPrecedence::Unary,
503 ),
504 UnaryOp::Real => (
505 self.format_call_expression("real", input, decorate),
506 ExprPrecedence::Atom,
507 ),
508 UnaryOp::Imag => (
509 self.format_call_expression("imag", input, decorate),
510 ExprPrecedence::Atom,
511 ),
512 UnaryOp::Conj => (
513 self.format_call_expression("conj", input, decorate),
514 ExprPrecedence::Atom,
515 ),
516 UnaryOp::NormSqr => (
517 format!("|{}|^2", self.format_expression_with(input, decorate)),
518 ExprPrecedence::Pow,
519 ),
520 UnaryOp::Sqrt => (
521 self.format_call_expression("sqrt", input, decorate),
522 ExprPrecedence::Atom,
523 ),
524 UnaryOp::Exp => (
525 self.format_call_expression("exp", input, decorate),
526 ExprPrecedence::Atom,
527 ),
528 UnaryOp::Sin => (
529 self.format_call_expression("sin", input, decorate),
530 ExprPrecedence::Atom,
531 ),
532 UnaryOp::Cos => (
533 self.format_call_expression("cos", input, decorate),
534 ExprPrecedence::Atom,
535 ),
536 UnaryOp::Log => (
537 self.format_call_expression("log", input, decorate),
538 ExprPrecedence::Atom,
539 ),
540 UnaryOp::PowI(power) => {
541 let exponent = if power < 0 {
542 format!("({power})")
543 } else {
544 power.to_string()
545 };
546 (
547 format!(
548 "{}^{exponent}",
549 self.format_child(input, ExprPrecedence::Pow, true, decorate)
550 ),
551 ExprPrecedence::Pow,
552 )
553 }
554 }
555 }
556
557 fn format_call_expression(
558 &self,
559 name: &str,
560 input: ExprId,
561 decorate: &dyn Fn(ExprId, &ExprNode, String) -> String,
562 ) -> String {
563 format!("{name}({})", self.format_expression_with(input, decorate))
564 }
565
566 fn format_binary_expression(
567 &self,
568 op: BinaryOp,
569 lhs: ExprId,
570 rhs: ExprId,
571 decorate: &dyn Fn(ExprId, &ExprNode, String) -> String,
572 ) -> (String, ExprPrecedence) {
573 match op {
574 BinaryOp::Add => self.format_sum_expression(&[lhs, rhs], decorate),
575 BinaryOp::Sub => {
576 let lhs = self.format_child(lhs, ExprPrecedence::Add, false, decorate);
577 let rhs = self.format_child(rhs, ExprPrecedence::Add, true, decorate);
578 (format!("{lhs} - {rhs}"), ExprPrecedence::Add)
579 }
580 BinaryOp::Mul => self.format_product_expression(&[lhs, rhs], decorate),
581 BinaryOp::Div => {
582 let lhs = self.format_child(lhs, ExprPrecedence::Mul, false, decorate);
583 let rhs = self.format_child(rhs, ExprPrecedence::Mul, true, decorate);
584 (format!("{lhs} / {rhs}"), ExprPrecedence::Mul)
585 }
586 BinaryOp::Atan2 => (
587 format!(
588 "atan2({}, {})",
589 self.format_expression_with(lhs, decorate),
590 self.format_expression_with(rhs, decorate)
591 ),
592 ExprPrecedence::Atom,
593 ),
594 }
595 }
596
597 fn format_sum_expression(
598 &self,
599 terms: &[ExprId],
600 decorate: &dyn Fn(ExprId, &ExprNode, String) -> String,
601 ) -> (String, ExprPrecedence) {
602 let mut formatted = String::new();
603 for term in terms {
604 let (negative, term) = self.format_signed_term(*term, decorate);
605 if formatted.is_empty() {
606 if negative {
607 formatted.push('-');
608 }
609 formatted.push_str(&term);
610 } else if negative {
611 formatted.push_str(" - ");
612 formatted.push_str(&term);
613 } else {
614 formatted.push_str(" + ");
615 formatted.push_str(&term);
616 }
617 }
618 if formatted.is_empty() {
619 formatted.push('0');
620 }
621 (formatted, ExprPrecedence::Add)
622 }
623
624 fn format_signed_term(
625 &self,
626 id: ExprId,
627 decorate: &dyn Fn(ExprId, &ExprNode, String) -> String,
628 ) -> (bool, String) {
629 match self.node(id) {
630 Some(ExprNode::RealConst(value)) if *value < 0.0 => (
631 true,
632 decorate(
633 id,
634 self.node(id).unwrap(),
635 Self::format_real_number(-*value),
636 ),
637 ),
638 Some(ExprNode::Unary {
639 op: UnaryOp::Neg,
640 input,
641 }) => (
642 true,
643 self.format_child(*input, ExprPrecedence::Add, false, decorate),
644 ),
645 Some(ExprNode::NaryMul { factors }) => {
646 let (negative, product) = self.format_product_parts(factors, decorate);
647 (negative, product)
648 }
649 _ => (
650 false,
651 self.format_child(id, ExprPrecedence::Add, false, decorate),
652 ),
653 }
654 }
655
656 fn format_product_expression(
657 &self,
658 factors: &[ExprId],
659 decorate: &dyn Fn(ExprId, &ExprNode, String) -> String,
660 ) -> (String, ExprPrecedence) {
661 let (negative, product) = self.format_product_parts(factors, decorate);
662 if negative {
663 (format!("-{product}"), ExprPrecedence::Unary)
664 } else {
665 (product, ExprPrecedence::Mul)
666 }
667 }
668
669 fn format_product_parts(
670 &self,
671 factors: &[ExprId],
672 decorate: &dyn Fn(ExprId, &ExprNode, String) -> String,
673 ) -> (bool, String) {
674 let mut negative = false;
675 let mut pieces = Vec::new();
676
677 for factor in factors {
678 match self.node(*factor) {
679 Some(ExprNode::RealConst(value)) if *value < 0.0 => {
680 negative = !negative;
681 if *value != -1.0 || factors.len() == 1 {
682 pieces.push(decorate(
683 *factor,
684 self.node(*factor).unwrap(),
685 Self::format_real_number(-*value),
686 ));
687 }
688 }
689 Some(ExprNode::Unary {
690 op: UnaryOp::Neg,
691 input,
692 }) => {
693 negative = !negative;
694 pieces.push(self.format_child(*input, ExprPrecedence::Mul, false, decorate));
695 }
696 _ => pieces.push(self.format_child(*factor, ExprPrecedence::Mul, false, decorate)),
697 }
698 }
699
700 if pieces.is_empty() {
701 pieces.push("1".to_owned());
702 }
703
704 (negative, pieces.join(" * "))
705 }
706
707 fn format_complex_const(&self, value: Complex64) -> (String, ExprPrecedence) {
708 match (value.re, value.im) {
709 (re, 0.0) => (Self::format_real_number(re), ExprPrecedence::Atom),
710 (0.0, im) => (Self::format_imaginary_unit(im), ExprPrecedence::Atom),
711 (re, im) if im < 0.0 => (
712 format!(
713 "{} - {}",
714 Self::format_real_number(re),
715 Self::format_imaginary_unit(-im)
716 ),
717 ExprPrecedence::Add,
718 ),
719 (re, im) => (
720 format!(
721 "{} + {}",
722 Self::format_real_number(re),
723 Self::format_imaginary_unit(im)
724 ),
725 ExprPrecedence::Add,
726 ),
727 }
728 }
729
730 fn format_real_number(value: f64) -> String {
731 let Some((value, decimals)) = Self::nearby_simple_decimal(value) else {
732 return value.to_string();
733 };
734
735 if decimals == 0 {
736 return value.to_string();
737 }
738
739 let mut formatted = format!("{value:.decimals$}");
740 while formatted.contains('.') && formatted.ends_with('0') {
741 formatted.pop();
742 }
743 if formatted.ends_with('.') {
744 formatted.pop();
745 }
746 formatted
747 }
748
749 fn nearby_simple_decimal(value: f64) -> Option<(f64, usize)> {
750 if !value.is_finite() {
751 return None;
752 }
753
754 for decimals in 0..=12 {
755 let scale = 10_f64.powi(decimals as i32);
756 let rounded = (value * scale).round() / scale;
757 if Self::nearly_equal(value, rounded) {
758 return Some((rounded, decimals));
759 }
760 }
761
762 None
763 }
764
765 fn nearly_equal(lhs: f64, rhs: f64) -> bool {
766 (lhs - rhs).abs() <= f64::EPSILON * lhs.abs().max(rhs.abs()).max(1.0) * 16.0
767 }
768
769 fn format_imaginary_unit(value: f64) -> String {
770 match value {
771 1.0 => "i".to_owned(),
772 -1.0 => "-i".to_owned(),
773 value => format!("{}i", Self::format_real_number(value)),
774 }
775 }
776
777 pub(crate) fn node_label(&self, id: ExprId, node: &ExprNode) -> String {
778 let mut label = match node {
779 ExprNode::RealConst(value) => {
780 format!(
781 "#{} RealConst({})",
782 id.index(),
783 Self::format_real_number(*value)
784 )
785 }
786 ExprNode::ComplexConst(value) => {
787 let (value, _) = self.format_complex_const(*value);
788 format!("#{} ComplexConst({value})", id.index())
789 }
790 ExprNode::ScalarParam(parameter) => {
791 format!("#{} ScalarParam({})", id.index(), parameter.name())
792 }
793 ExprNode::EventScalar(name) => format!("#{} EventScalar({name})", id.index()),
794 ExprNode::EventP4Component { name, component } => {
795 format!(
796 "#{} EventP4Component({name}.{})",
797 id.index(),
798 component.label()
799 )
800 }
801 ExprNode::Unary { op, .. } => format!("#{} Unary({op:?})", id.index()),
802 ExprNode::Binary { op, .. } => format!("#{} Binary({op:?})", id.index()),
803 ExprNode::NaryAdd { terms } => {
804 format!("#{} NaryAdd(len={})", id.index(), terms.len())
805 }
806 ExprNode::NaryMul { factors } => {
807 format!("#{} NaryMul(len={})", id.index(), factors.len())
808 }
809 ExprNode::Complex { .. } => format!("#{} Complex", id.index()),
810 ExprNode::Vector { elements } => {
811 format!("#{} Vector(len={})", id.index(), elements.len())
812 }
813 ExprNode::Matrix { rows, cols, .. } => {
814 format!("#{} Matrix({rows}x{cols})", id.index())
815 }
816 ExprNode::Component { index, .. } => {
817 format!("#{} Component(index={index})", id.index())
818 }
819 ExprNode::MatrixElement { row, col, .. } => {
820 format!("#{} MatrixElement(row={row}, col={col})", id.index())
821 }
822 ExprNode::MatMul { .. } => format!("#{} MatMul", id.index()),
823 ExprNode::MatVec { .. } => format!("#{} MatVec", id.index()),
824 ExprNode::Dot { .. } => format!("#{} Dot", id.index()),
825 ExprNode::Solve { .. } => format!("#{} Solve", id.index()),
826 };
827
828 if let Some(metadata) = self.metadata(id) {
829 if let Some(name) = metadata.name() {
830 label.push_str(&format!(" name=\"{name}\""));
831 }
832 if !metadata.tags().is_empty() {
833 label.push_str(" tags=[");
834 for (index, tag) in metadata.tags().iter().enumerate() {
835 if index != 0 {
836 label.push_str(", ");
837 }
838 label.push_str(tag);
839 }
840 label.push(']');
841 }
842 }
843
844 label
845 }
846}
847
848impl fmt::Display for ExprGraph {
849 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
850 f.write_str(&self.format_expression(self.root()))
851 }
852}
853
854#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
855enum ExprPrecedence {
856 Lowest,
857 Add,
858 Mul,
859 Unary,
860 Pow,
861 Postfix,
862 Atom,
863}
864
865pub struct ExprGraphEquationDisplay<'a> {
867 graph: &'a ExprGraph,
868 options: DisplayOptions,
869}
870
871impl<'a> ExprGraphEquationDisplay<'a> {
872 pub(crate) fn new(graph: &'a ExprGraph) -> Self {
873 Self {
874 graph,
875 options: DisplayOptions::default(),
876 }
877 }
878
879 pub fn with_preset(mut self, preset: ColorPreset) -> Self {
881 self.options.with_preset(preset);
882 self
883 }
884
885 pub fn with_style_rule(mut self, rule: NodeStyleRule) -> Self {
889 self.options.rules.push(rule);
890 self
891 }
892}
893
894impl fmt::Display for ExprGraphEquationDisplay<'_> {
895 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
896 f.write_str(
897 &self
898 .graph
899 .format_expression_with(self.graph.root(), &|id, node, text| {
900 self.options.resolve(self.graph, id, node).ansi(text)
901 }),
902 )
903 }
904}
905
906pub struct ExprGraphLatexDisplay<'a> {
912 graph: &'a ExprGraph,
913 options: DisplayOptions,
914}
915
916impl<'a> ExprGraphLatexDisplay<'a> {
917 pub(crate) fn new(graph: &'a ExprGraph) -> Self {
918 Self {
919 graph,
920 options: DisplayOptions::default(),
921 }
922 }
923
924 pub fn with_preset(mut self, preset: ColorPreset) -> Self {
926 self.options.with_preset(preset);
927 self
928 }
929
930 pub fn with_style_rule(mut self, rule: NodeStyleRule) -> Self {
935 self.options.rules.push(rule);
936 self
937 }
938}
939
940impl fmt::Display for ExprGraphLatexDisplay<'_> {
941 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
942 let decorate = |id: ExprId, node: &ExprNode, text: String| {
943 self.options.resolve(self.graph, id, node).latex(text)
944 };
945 f.write_str(&LatexFormatter::new(self.graph, &decorate).format())
946 }
947}
948
949struct LatexFormatter<'a> {
950 graph: &'a ExprGraph,
951 decorate: &'a dyn Fn(ExprId, &ExprNode, String) -> String,
952}
953
954impl<'a> LatexFormatter<'a> {
955 fn new(
956 graph: &'a ExprGraph,
957 decorate: &'a dyn Fn(ExprId, &ExprNode, String) -> String,
958 ) -> Self {
959 Self { graph, decorate }
960 }
961
962 fn format(&self) -> String {
963 self.format_expression(self.graph.root())
964 }
965
966 fn format_expression(&self, id: ExprId) -> String {
967 self.format_child(id, ExprPrecedence::Lowest, false)
968 }
969
970 fn format_child(
971 &self,
972 id: ExprId,
973 parent_precedence: ExprPrecedence,
974 parenthesize_equal: bool,
975 ) -> String {
976 let (text, precedence) = self.format_node(id);
977 if precedence < parent_precedence || (parenthesize_equal && precedence == parent_precedence)
978 {
979 format!("\\left({text}\\right)")
980 } else {
981 text
982 }
983 }
984
985 fn format_node(&self, id: ExprId) -> (String, ExprPrecedence) {
986 let Some(node) = self.graph.node(id) else {
987 return (
988 format!("\\text{{missing node \\#{}}}", id.index()),
989 ExprPrecedence::Atom,
990 );
991 };
992
993 let (text, precedence) = match node {
994 ExprNode::RealConst(value) => (format_latex_number(*value), ExprPrecedence::Atom),
995 ExprNode::ComplexConst(value) => format_latex_complex(*value),
996 ExprNode::ScalarParam(parameter) => (
997 parameter
998 .latex_label()
999 .map(str::to_owned)
1000 .unwrap_or_else(|| escape_latex(parameter.name())),
1001 ExprPrecedence::Atom,
1002 ),
1003 ExprNode::EventScalar(name) => (escape_latex(name), ExprPrecedence::Atom),
1004 ExprNode::EventP4Component { name, component } => (
1005 format!(
1006 "{}_{{\\mathrm{{{}}}}}",
1007 escape_latex(name),
1008 escape_latex(component.label())
1009 ),
1010 ExprPrecedence::Atom,
1011 ),
1012 ExprNode::Unary { op, input } => self.format_unary(*op, *input),
1013 ExprNode::Binary { op, lhs, rhs } => self.format_binary(*op, *lhs, *rhs),
1014 ExprNode::NaryAdd { terms } => self.format_sum(terms),
1015 ExprNode::NaryMul { factors } => self.format_product(factors),
1016 ExprNode::Complex { re, im } => (
1017 self.format_operator("complex", &[*re, *im]),
1018 ExprPrecedence::Atom,
1019 ),
1020 ExprNode::Vector { elements } => (
1021 format!(
1022 "\\begin{{bmatrix}}{}\\end{{bmatrix}}",
1023 elements
1024 .iter()
1025 .map(|id| self.format_expression(*id))
1026 .collect::<Vec<_>>()
1027 .join(" \\\\ ")
1028 ),
1029 ExprPrecedence::Atom,
1030 ),
1031 ExprNode::Matrix {
1032 rows,
1033 cols,
1034 elements,
1035 } => {
1036 let rows = (0..*rows)
1037 .map(|row| {
1038 let start = row * *cols;
1039 let end = start + *cols;
1040 elements[start..end]
1041 .iter()
1042 .map(|id| self.format_expression(*id))
1043 .collect::<Vec<_>>()
1044 .join(" & ")
1045 })
1046 .collect::<Vec<_>>()
1047 .join(" \\\\ ");
1048 (
1049 format!("\\begin{{bmatrix}}{rows}\\end{{bmatrix}}"),
1050 ExprPrecedence::Atom,
1051 )
1052 }
1053 ExprNode::Component { input, index } => (
1054 format!(
1055 "{}_{{{index}}}",
1056 self.format_child(*input, ExprPrecedence::Postfix, false)
1057 ),
1058 ExprPrecedence::Postfix,
1059 ),
1060 ExprNode::MatrixElement { input, row, col } => (
1061 format!(
1062 "{}_{{{row},{col}}}",
1063 self.format_child(*input, ExprPrecedence::Postfix, false)
1064 ),
1065 ExprPrecedence::Postfix,
1066 ),
1067 ExprNode::MatMul { lhs, rhs } => (
1068 self.format_operator("matmul", &[*lhs, *rhs]),
1069 ExprPrecedence::Atom,
1070 ),
1071 ExprNode::MatVec { matrix, vector } => (
1072 self.format_operator("matvec", &[*matrix, *vector]),
1073 ExprPrecedence::Atom,
1074 ),
1075 ExprNode::Dot { lhs, rhs } => (
1076 self.format_operator("dot", &[*lhs, *rhs]),
1077 ExprPrecedence::Atom,
1078 ),
1079 ExprNode::Solve { matrix, rhs } => (
1080 self.format_operator("solve", &[*matrix, *rhs]),
1081 ExprPrecedence::Atom,
1082 ),
1083 };
1084 ((self.decorate)(id, node, text), precedence)
1085 }
1086
1087 fn format_unary(&self, op: UnaryOp, input: ExprId) -> (String, ExprPrecedence) {
1088 match op {
1089 UnaryOp::Neg => (
1090 format!("-{}", self.format_child(input, ExprPrecedence::Unary, true)),
1091 ExprPrecedence::Unary,
1092 ),
1093 UnaryOp::Real => (self.format_operator("Re", &[input]), ExprPrecedence::Atom),
1094 UnaryOp::Imag => (self.format_operator("Im", &[input]), ExprPrecedence::Atom),
1095 UnaryOp::Conj => (
1096 format!("\\overline{{{}}}", self.format_expression(input)),
1097 ExprPrecedence::Atom,
1098 ),
1099 UnaryOp::NormSqr => (
1100 format!("\\left|{}\\right|^{{2}}", self.format_expression(input)),
1101 ExprPrecedence::Pow,
1102 ),
1103 UnaryOp::Sqrt => (
1104 format!("\\sqrt{{{}}}", self.format_expression(input)),
1105 ExprPrecedence::Atom,
1106 ),
1107 UnaryOp::Exp => (self.format_function("exp", input), ExprPrecedence::Atom),
1108 UnaryOp::Sin => (self.format_function("sin", input), ExprPrecedence::Atom),
1109 UnaryOp::Cos => (self.format_function("cos", input), ExprPrecedence::Atom),
1110 UnaryOp::Log => (self.format_function("log", input), ExprPrecedence::Atom),
1111 UnaryOp::PowI(power) => (
1112 format!(
1113 "{}^{{{power}}}",
1114 self.format_child(input, ExprPrecedence::Pow, true)
1115 ),
1116 ExprPrecedence::Pow,
1117 ),
1118 }
1119 }
1120
1121 fn format_function(&self, name: &str, input: ExprId) -> String {
1122 format!("\\{name}\\left({}\\right)", self.format_expression(input))
1123 }
1124
1125 fn format_operator(&self, name: &str, inputs: &[ExprId]) -> String {
1126 format!(
1127 "\\operatorname{{{name}}}\\left({}\\right)",
1128 inputs
1129 .iter()
1130 .map(|id| self.format_expression(*id))
1131 .collect::<Vec<_>>()
1132 .join(", ")
1133 )
1134 }
1135
1136 fn format_binary(&self, op: BinaryOp, lhs: ExprId, rhs: ExprId) -> (String, ExprPrecedence) {
1137 match op {
1138 BinaryOp::Add => self.format_sum(&[lhs, rhs]),
1139 BinaryOp::Sub => {
1140 let lhs = self.format_child(lhs, ExprPrecedence::Add, false);
1141 let rhs = self.format_child(rhs, ExprPrecedence::Add, true);
1142 (format!("{lhs} - {rhs}"), ExprPrecedence::Add)
1143 }
1144 BinaryOp::Mul => self.format_product(&[lhs, rhs]),
1145 BinaryOp::Div => (
1146 format!(
1147 "\\frac{{{}}}{{{}}}",
1148 self.format_expression(lhs),
1149 self.format_expression(rhs)
1150 ),
1151 ExprPrecedence::Atom,
1152 ),
1153 BinaryOp::Atan2 => (
1154 self.format_operator("atan2", &[lhs, rhs]),
1155 ExprPrecedence::Atom,
1156 ),
1157 }
1158 }
1159
1160 fn format_sum(&self, terms: &[ExprId]) -> (String, ExprPrecedence) {
1161 let mut formatted = String::new();
1162 for term in terms {
1163 let (negative, term) = self.format_signed_term(*term);
1164 if formatted.is_empty() {
1165 if negative {
1166 formatted.push('-');
1167 }
1168 formatted.push_str(&term);
1169 } else if negative {
1170 formatted.push_str(" - ");
1171 formatted.push_str(&term);
1172 } else {
1173 formatted.push_str(" + ");
1174 formatted.push_str(&term);
1175 }
1176 }
1177 if formatted.is_empty() {
1178 formatted.push('0');
1179 }
1180 (formatted, ExprPrecedence::Add)
1181 }
1182
1183 fn format_signed_term(&self, id: ExprId) -> (bool, String) {
1184 match self.graph.node(id) {
1185 Some(node @ ExprNode::RealConst(value)) if *value < 0.0 => (
1186 true,
1187 (self.decorate)(id, node, format_latex_number(-*value)),
1188 ),
1189 Some(ExprNode::Unary {
1190 op: UnaryOp::Neg,
1191 input,
1192 }) => (true, self.format_child(*input, ExprPrecedence::Add, false)),
1193 Some(ExprNode::NaryMul { factors }) => self.format_product_parts(factors),
1194 _ => (false, self.format_child(id, ExprPrecedence::Add, false)),
1195 }
1196 }
1197
1198 fn format_product(&self, factors: &[ExprId]) -> (String, ExprPrecedence) {
1199 let (negative, product) = self.format_product_parts(factors);
1200 if negative {
1201 (format!("-{product}"), ExprPrecedence::Unary)
1202 } else {
1203 (product, ExprPrecedence::Mul)
1204 }
1205 }
1206
1207 fn format_product_parts(&self, factors: &[ExprId]) -> (bool, String) {
1208 let mut negative = false;
1209 let mut pieces = Vec::new();
1210 for factor in factors {
1211 match self.graph.node(*factor) {
1212 Some(node @ ExprNode::RealConst(value)) if *value < 0.0 => {
1213 negative = !negative;
1214 if *value != -1.0 || factors.len() == 1 {
1215 pieces.push((self.decorate)(*factor, node, format_latex_number(-*value)));
1216 }
1217 }
1218 Some(ExprNode::Unary {
1219 op: UnaryOp::Neg,
1220 input,
1221 }) => {
1222 negative = !negative;
1223 pieces.push(self.format_child(*input, ExprPrecedence::Mul, false));
1224 }
1225 _ => pieces.push(self.format_child(*factor, ExprPrecedence::Mul, false)),
1226 }
1227 }
1228 if pieces.is_empty() {
1229 pieces.push("1".to_owned());
1230 }
1231 (negative, pieces.join(" \\cdot "))
1232 }
1233}
1234
1235fn format_latex_complex(value: Complex64) -> (String, ExprPrecedence) {
1236 match (value.re, value.im) {
1237 (re, 0.0) => (format_latex_number(re), ExprPrecedence::Atom),
1238 (0.0, im) => (format_latex_imaginary(im), ExprPrecedence::Atom),
1239 (re, im) if im < 0.0 => (
1240 format!(
1241 "{} - {}",
1242 format_latex_number(re),
1243 format_latex_imaginary(-im)
1244 ),
1245 ExprPrecedence::Add,
1246 ),
1247 (re, im) => (
1248 format!(
1249 "{} + {}",
1250 format_latex_number(re),
1251 format_latex_imaginary(im)
1252 ),
1253 ExprPrecedence::Add,
1254 ),
1255 }
1256}
1257
1258fn format_latex_number(value: f64) -> String {
1259 if value == f64::INFINITY {
1260 "\\infty".to_owned()
1261 } else if value == f64::NEG_INFINITY {
1262 "-\\infty".to_owned()
1263 } else if value.is_nan() {
1264 "\\mathrm{NaN}".to_owned()
1265 } else {
1266 ExprGraph::format_real_number(value)
1267 }
1268}
1269
1270fn format_latex_imaginary(value: f64) -> String {
1271 match value {
1272 1.0 => "\\mathrm{i}".to_owned(),
1273 -1.0 => "-\\mathrm{i}".to_owned(),
1274 value => format!("{}\\mathrm{{i}}", format_latex_number(value)),
1275 }
1276}
1277
1278fn escape_latex(value: &str) -> String {
1279 let mut escaped = String::with_capacity(value.len());
1280 for character in value.chars() {
1281 match character {
1282 '\\' => escaped.push_str("\\backslash "),
1283 '{' => escaped.push_str("\\{"),
1284 '}' => escaped.push_str("\\}"),
1285 '_' => escaped.push_str("\\_"),
1286 '^' => escaped.push_str("\\^{}"),
1287 '#' => escaped.push_str("\\#"),
1288 '$' => escaped.push_str("\\$"),
1289 '%' => escaped.push_str("\\%"),
1290 '&' => escaped.push_str("\\&"),
1291 '~' => escaped.push_str("\\~{}"),
1292 _ => escaped.push(character),
1293 }
1294 }
1295 escaped
1296}
1297
1298pub struct ExprGraphTreeDisplay<'a> {
1300 graph: &'a ExprGraph,
1301 options: DisplayOptions,
1302}
1303
1304impl<'a> ExprGraphTreeDisplay<'a> {
1305 pub(crate) fn new(graph: &'a ExprGraph) -> Self {
1306 Self {
1307 graph,
1308 options: DisplayOptions::default(),
1309 }
1310 }
1311
1312 pub fn expand_repeated(mut self, expand: bool) -> Self {
1314 self.options.expand_repeated = expand;
1315 self
1316 }
1317
1318 pub fn with_preset(mut self, preset: ColorPreset) -> Self {
1320 self.options.with_preset(preset);
1321 self
1322 }
1323
1324 pub fn with_style_rule(mut self, rule: NodeStyleRule) -> Self {
1328 self.options.rules.push(rule);
1329 self
1330 }
1331}
1332
1333impl fmt::Display for ExprGraphTreeDisplay<'_> {
1334 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1335 writeln!(f, "ExprGraph(root=#{})", self.graph.root().index())?;
1336 let mut visited = HashSet::new();
1337 let mut stack = vec![(self.graph.root(), String::new(), None::<(String, bool)>)];
1338 while let Some((id, prefix, edge)) = stack.pop() {
1339 let Some(node) = self.graph.node(id) else {
1340 write_tree_line(
1341 f,
1342 &prefix,
1343 edge.as_ref().map(|(label, last)| (label.as_str(), *last)),
1344 &format!("#{} <missing node>", id.index()),
1345 )?;
1346 continue;
1347 };
1348 let repeated = !visited.insert(id);
1349 let mut line = if repeated && !self.options.expand_repeated {
1350 format!("#{0} <reference to #{0}>", id.index())
1351 } else {
1352 self.graph.node_label(id, node)
1353 };
1354 line = self.options.resolve(self.graph, id, node).ansi(line);
1355 write_tree_line(
1356 f,
1357 &prefix,
1358 edge.as_ref().map(|(label, last)| (label.as_str(), *last)),
1359 &line,
1360 )?;
1361 if repeated && !self.options.expand_repeated {
1362 continue;
1363 }
1364
1365 let children = node_children(node);
1366 let child_prefix = match edge {
1367 Some((_, true)) => format!("{prefix} "),
1368 Some((_, false)) => format!("{prefix}┃ "),
1369 None => prefix,
1370 };
1371 let child_count = children.len();
1372 for (index, (label, child)) in children.into_iter().enumerate().rev() {
1373 stack.push((
1374 child,
1375 child_prefix.clone(),
1376 Some((label, index + 1 == child_count)),
1377 ));
1378 }
1379 }
1380 Ok(())
1381 }
1382}
1383
1384pub struct ExprGraphDotDisplay<'a> {
1386 graph: &'a ExprGraph,
1387 options: DisplayOptions,
1388}
1389
1390impl<'a> ExprGraphDotDisplay<'a> {
1391 pub(crate) fn new(graph: &'a ExprGraph) -> Self {
1392 Self {
1393 graph,
1394 options: DisplayOptions::default(),
1395 }
1396 }
1397
1398 pub fn expand_repeated(mut self, expand: bool) -> Self {
1400 self.options.expand_repeated = expand;
1401 self
1402 }
1403
1404 pub fn with_preset(mut self, preset: ColorPreset) -> Self {
1406 self.options.with_preset(preset);
1407 self
1408 }
1409
1410 pub fn with_style_rule(mut self, rule: NodeStyleRule) -> Self {
1414 self.options.rules.push(rule);
1415 self
1416 }
1417
1418 #[cfg(feature = "svg")]
1419 pub fn render_svg(&self) -> Result<String, GraphRenderError> {
1426 use layout::{backends::svg::SVGWriter, gv};
1427
1428 let dot = self.to_string();
1429 let mut parser = gv::DotParser::new(&dot);
1430 let graph = parser.process().map_err(GraphRenderError::Dot)?;
1431 let mut builder = gv::GraphBuilder::new();
1432 builder.visit_graph(&graph);
1433 let mut graph = builder.get();
1434 let mut svg = SVGWriter::new();
1435 graph.do_it(false, false, false, &mut svg);
1436 Ok(svg.finalize())
1437 }
1438
1439 fn node_attributes(&self, id: ExprId, node: &ExprNode) -> String {
1440 let mut attributes = vec![format!(
1441 "label=\"{}\"",
1442 escape_dot(&self.graph.node_label(id, node))
1443 )];
1444 let style = self.options.resolve(self.graph, id, node);
1445 if let Some(color) = style.foreground {
1446 attributes.push(format!("fontcolor=\"{}\"", color.dot()));
1447 }
1448 if let Some(color) = style.border {
1449 attributes.push(format!("color=\"{}\"", color.dot()));
1450 }
1451 if let Some(color) = style.fill {
1452 attributes.push(format!("fillcolor=\"{}\"", color.dot()));
1453 attributes.push("style=filled".to_owned());
1454 }
1455 attributes.join(", ")
1456 }
1457
1458 fn write_expanded(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1459 enum Frame {
1460 Visit(ExprId),
1461 Children {
1462 parent: usize,
1463 children: Vec<(String, ExprId)>,
1464 index: usize,
1465 },
1466 Edge {
1467 parent: usize,
1468 child: usize,
1469 label: String,
1470 },
1471 }
1472
1473 let mut occurrence = 0;
1474 let mut stack = vec![Frame::Visit(self.graph.root())];
1475 while let Some(frame) = stack.pop() {
1476 match frame {
1477 Frame::Visit(id) => {
1478 let current = occurrence;
1479 occurrence += 1;
1480 let Some(node) = self.graph.node(id) else {
1481 continue;
1482 };
1483 writeln!(f, " n{current} [{}];", self.node_attributes(id, node))?;
1484 stack.push(Frame::Children {
1485 parent: current,
1486 children: node_children(node),
1487 index: 0,
1488 });
1489 }
1490 Frame::Children {
1491 parent,
1492 children,
1493 index,
1494 } => {
1495 if let Some((label, child)) = children.get(index).cloned() {
1496 let child_occurrence = occurrence;
1497 stack.push(Frame::Children {
1498 parent,
1499 children,
1500 index: index + 1,
1501 });
1502 stack.push(Frame::Edge {
1503 parent,
1504 child: child_occurrence,
1505 label,
1506 });
1507 stack.push(Frame::Visit(child));
1508 }
1509 }
1510 Frame::Edge {
1511 parent,
1512 child,
1513 label,
1514 } => writeln!(
1515 f,
1516 " n{parent} -> n{child} [label=\"{}\"];",
1517 escape_dot(&label)
1518 )?,
1519 }
1520 }
1521 Ok(())
1522 }
1523
1524 fn write_shared(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1525 enum Frame {
1526 Visit(ExprId),
1527 Edge(ExprId, ExprId, String),
1528 }
1529
1530 let mut visited = HashSet::new();
1531 let mut stack = vec![Frame::Visit(self.graph.root())];
1532 while let Some(frame) = stack.pop() {
1533 match frame {
1534 Frame::Visit(id) => {
1535 if !visited.insert(id) {
1536 continue;
1537 }
1538 let Some(node) = self.graph.node(id) else {
1539 continue;
1540 };
1541 writeln!(f, " n{} [{}];", id.index(), self.node_attributes(id, node))?;
1542 for (label, child) in node_children(node).into_iter().rev() {
1543 stack.push(Frame::Edge(id, child, label));
1544 stack.push(Frame::Visit(child));
1545 }
1546 }
1547 Frame::Edge(parent, child, label) => writeln!(
1548 f,
1549 " n{} -> n{} [label=\"{}\"];",
1550 parent.index(),
1551 child.index(),
1552 escape_dot(&label)
1553 )?,
1554 }
1555 }
1556 Ok(())
1557 }
1558}
1559
1560#[cfg(feature = "svg")]
1561#[derive(Clone, Debug, thiserror::Error, PartialEq, Eq)]
1563pub enum GraphRenderError {
1564 #[error("failed to parse generated DOT: {0}")]
1566 Dot(String),
1567}
1568
1569impl fmt::Display for ExprGraphDotDisplay<'_> {
1570 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1571 writeln!(f, "digraph ExprGraph {{")?;
1572 if self.options.expand_repeated {
1573 self.write_expanded(f)?;
1574 } else {
1575 self.write_shared(f)?;
1576 }
1577 writeln!(f, "}}")
1578 }
1579}
1580
1581fn write_tree_line(
1582 f: &mut fmt::Formatter<'_>,
1583 prefix: &str,
1584 edge: Option<(&str, bool)>,
1585 text: &str,
1586) -> fmt::Result {
1587 if let Some((label, is_last)) = edge {
1588 let connector = if is_last { "┗" } else { "┣" };
1589 writeln!(f, "{prefix}{connector} {label}: {text}")
1590 } else {
1591 writeln!(f, "{text}")
1592 }
1593}
1594
1595fn escape_dot(value: &str) -> String {
1596 value
1597 .replace('\\', "\\\\")
1598 .replace('"', "\\\"")
1599 .replace('\n', "\\n")
1600 .replace('\r', "\\r")
1601}
1602
1603#[cfg(test)]
1604mod tests {
1605 use std::sync::Arc;
1606
1607 use super::*;
1608 use crate::{ExprSourceKind, event_scalar};
1609
1610 fn shared_graph() -> ExprGraph {
1611 let shared = event_scalar("x").named("shared").tagged("data");
1612 ((shared.clone() + 1.0) * (shared + 2.0)).to_graph()
1613 }
1614
1615 #[test]
1616 fn equation_latex_tree_and_dot_have_no_color_by_default() {
1617 let graph = shared_graph();
1618 let shared_id = graph
1619 .nodes()
1620 .iter()
1621 .position(|node| matches!(node, ExprNode::EventScalar(name) if name.as_ref() == "x"))
1622 .unwrap();
1623 let needle = format!("#{shared_id} EventScalar(x)");
1624 let tree = graph.display_tree().to_string();
1625 let dot = graph.display_dot().to_string();
1626 let equation = graph.display_equation().to_string();
1627 let latex = graph.display_latex().to_string();
1628
1629 assert_eq!(tree.matches(&needle).count(), 2);
1630 assert_eq!(dot.matches(&needle).count(), 2);
1631 assert!(!tree.contains("\x1b["));
1632 assert!(!dot.contains("fontcolor="));
1633 assert!(!dot.contains("fillcolor="));
1634 assert!(!equation.contains("\x1b["));
1635 assert_eq!(equation, graph.to_string());
1636 assert!(!latex.contains("\\color"));
1637 assert!(latex.contains("\\cdot"));
1638 }
1639
1640 #[test]
1641 fn reference_mode_suppresses_repeated_tree_expansion_and_emits_a_shared_dag() {
1642 let graph = shared_graph();
1643 let tree = graph.display_tree().expand_repeated(false).to_string();
1644 let dot = graph.display_dot().expand_repeated(false).to_string();
1645
1646 assert_eq!(tree.matches("EventScalar(x)").count(), 1);
1647 assert_eq!(tree.matches("<reference to #").count(), 1);
1648 assert_eq!(dot.matches("EventScalar(x)").count(), 1);
1649 assert_eq!(dot.matches(" -> ").count(), 6);
1650 }
1651
1652 #[test]
1653 fn later_style_rules_override_matching_preset_fields() {
1654 let graph = shared_graph();
1655 let override_color = DisplayColor::rgb(1, 2, 3);
1656 let rule = NodeStyleRule::new(
1657 NodeSelector::Tag("data".to_owned()),
1658 NodeStyle::new().with_foreground(override_color),
1659 );
1660 let tree = graph
1661 .display_tree()
1662 .with_preset(ColorPreset::Light)
1663 .with_style_rule(rule.clone())
1664 .to_string();
1665 let dot = graph
1666 .display_dot()
1667 .with_preset(ColorPreset::Light)
1668 .with_style_rule(rule)
1669 .to_string();
1670 let equation = graph
1671 .display_equation()
1672 .with_preset(ColorPreset::Light)
1673 .with_style_rule(NodeStyleRule::new(
1674 NodeSelector::Tag("data".to_owned()),
1675 NodeStyle::new().with_foreground(override_color),
1676 ))
1677 .to_string();
1678 let latex = graph
1679 .display_latex()
1680 .with_preset(ColorPreset::Light)
1681 .with_style_rule(NodeStyleRule::new(
1682 NodeSelector::Tag("data".to_owned()),
1683 NodeStyle::new().with_foreground(override_color),
1684 ))
1685 .to_string();
1686
1687 assert!(tree.contains("\x1b[38;2;1;2;3m"));
1688 assert!(dot.contains("fontcolor=\"#010203\""));
1689 assert!(equation.contains("\x1b[38;2;1;2;3m"));
1690 assert!(latex.contains("\\color[RGB]{1,2,3}"));
1691 }
1692
1693 #[test]
1694 fn latex_uses_math_constructs_and_escapes_names() {
1695 let numerator = crate::event_scalar("x_value");
1696 let denominator = crate::event_scalar("y").sqrt();
1697 let latex = (numerator / denominator)
1698 .powi(-2)
1699 .to_graph()
1700 .display_latex()
1701 .to_string();
1702
1703 assert!(latex.contains("\\frac{"));
1704 assert!(latex.contains("x\\_value"));
1705 assert!(latex.contains("\\sqrt{y}"));
1706 assert!(latex.contains("^{-2}"));
1707 }
1708
1709 #[test]
1710 fn latex_uses_parameter_labels_with_name_fallback() {
1711 let labeled = crate::parameter!("alpha_internal", latex: r"\alpha");
1712 let unlabeled = crate::parameter!("beta_internal");
1713 let latex = (labeled + unlabeled).to_graph().display_latex().to_string();
1714
1715 assert!(latex.contains(r"\alpha"));
1716 assert!(!latex.contains("alpha_internal"));
1717 assert!(latex.contains(r"beta\_internal"));
1718 }
1719
1720 #[test]
1721 fn dot_escapes_metadata_and_event_labels() {
1722 let graph = event_scalar("x\\\"y").named("quoted\"name").to_graph();
1723 let dot = graph.display_dot().to_string();
1724
1725 assert!(dot.contains("x\\\\\\\"y"));
1726 assert!(dot.contains("quoted\\\"name"));
1727 }
1728
1729 #[test]
1730 fn iterative_dot_display_handles_deep_graphs() {
1731 let metadata = ExprMetadata::new(ExprSourceKind::Unary);
1732 let mut nodes = vec![ExprNode::EventScalar(Arc::from("x"))];
1733 let mut metadata_nodes = vec![ExprMetadata::new(ExprSourceKind::Event)];
1734 for index in 1..20_000 {
1735 nodes.push(ExprNode::Unary {
1736 op: UnaryOp::Sin,
1737 input: ExprId::from_index(index - 1),
1738 });
1739 metadata_nodes.push(metadata.clone());
1740 }
1741 let graph = ExprGraph::from_parts(ExprId::from_index(19_999), nodes, metadata_nodes)
1742 .expect("deep graph is valid");
1743
1744 let dot = graph.display_dot().expand_repeated(false).to_string();
1745 assert!(dot.starts_with("digraph ExprGraph {\n"));
1746 assert_eq!(dot.matches(" -> ").count(), 19_999);
1747 }
1748
1749 #[cfg(feature = "svg")]
1750 #[test]
1751 fn dot_display_renders_svg_in_process() {
1752 let svg = shared_graph()
1753 .display_dot()
1754 .with_preset(ColorPreset::Light)
1755 .render_svg()
1756 .unwrap();
1757
1758 assert!(svg.contains("<svg"));
1759 assert!(svg.contains("</svg>"));
1760 }
1761}