Skip to main content

helios_fhirpath/
debug_trace.rs

1//! Debug trace support for step-by-step FHIRPath evaluation tracing.
2//!
3//! When enabled via `FHIRPATH_DEBUG_TRACE=1`, this module records every intermediate
4//! evaluation step with position, length, and function name from the source expression.
5
6use crate::parser::{
7    ExprSpan, Expression, SpannedExprKind, SpannedExpression, SpannedInvocation, SpannedTerm,
8};
9use helios_fhirpath_support::EvaluationResult;
10use std::collections::HashMap;
11
12/// A single step in the debug trace, recording what was evaluated and the result.
13#[derive(Debug, Clone)]
14pub struct DebugTraceStep {
15    pub position: usize,
16    pub length: usize,
17    pub function_name: String,
18    pub result: EvaluationResult,
19}
20
21/// Collects debug trace steps during evaluation.
22#[derive(Debug)]
23pub struct DebugTracer {
24    pub steps: Vec<DebugTraceStep>,
25    pub span_map: HashMap<*const Expression, ExprSpan>,
26}
27
28// SAFETY: The raw pointers in span_map are only used as lookup keys during
29// evaluation while the Expression tree they point into is alive and immutable.
30// They are never dereferenced through the DebugTracer.
31unsafe impl Send for DebugTracer {}
32unsafe impl Sync for DebugTracer {}
33
34impl DebugTracer {
35    pub fn new(span_map: HashMap<*const Expression, ExprSpan>) -> Self {
36        Self {
37            steps: Vec::new(),
38            span_map,
39        }
40    }
41
42    /// Record a trace step if we have span info for this expression.
43    pub fn record(&mut self, expr: &Expression, result: &EvaluationResult) {
44        let ptr = expr as *const Expression;
45        if let Some(span) = self.span_map.get(&ptr) {
46            self.steps.push(DebugTraceStep {
47                position: span.position,
48                length: span.length,
49                function_name: expression_debug_name(expr),
50                result: result.clone(),
51            });
52        }
53    }
54}
55
56/// Build a map from Expression pointer addresses to their spans.
57///
58/// This walks the SpannedExpression and Expression trees in parallel (they have
59/// identical structure since Expression was produced by `to_expression()`) and
60/// records the mapping from each `&Expression` address to its span.
61pub fn build_span_map(
62    spanned: &SpannedExpression,
63    expr: &Expression,
64) -> HashMap<*const Expression, ExprSpan> {
65    let mut map = HashMap::new();
66    build_span_map_inner(spanned, expr, &mut map);
67    map
68}
69
70fn build_span_map_inner(
71    spanned: &SpannedExpression,
72    expr: &Expression,
73    map: &mut HashMap<*const Expression, ExprSpan>,
74) {
75    // Map this expression's pointer to its span
76    map.insert(expr as *const Expression, spanned.span.clone());
77
78    // Recursively map children
79    match (&spanned.kind, expr) {
80        (SpannedExprKind::Term(st), Expression::Term(t)) => match (st, t) {
81            (SpannedTerm::Parenthesized(se), crate::parser::Term::Parenthesized(e)) => {
82                build_span_map_inner(se, e, map);
83            }
84            (
85                SpannedTerm::Invocation(SpannedInvocation::Function(_, sargs)),
86                crate::parser::Term::Invocation(crate::parser::Invocation::Function(_, args)),
87            ) => {
88                for (sa, a) in sargs.iter().zip(args.iter()) {
89                    build_span_map_inner(sa, a, map);
90                }
91            }
92            _ => {}
93        },
94        (SpannedExprKind::Invocation(sb, sinv), Expression::Invocation(eb, _einv)) => {
95            build_span_map_inner(sb, eb, map);
96            // Map function args in the invocation
97            if let (
98                SpannedInvocation::Function(_, sargs),
99                crate::parser::Invocation::Function(_, args),
100            ) = (sinv, _einv)
101            {
102                for (sa, a) in sargs.iter().zip(args.iter()) {
103                    build_span_map_inner(sa, a, map);
104                }
105            }
106        }
107        (SpannedExprKind::Indexer(se, si), Expression::Indexer(ee, ei)) => {
108            build_span_map_inner(se, ee, map);
109            build_span_map_inner(si, ei, map);
110        }
111        (SpannedExprKind::Polarity(_, se), Expression::Polarity(_, ee)) => {
112            build_span_map_inner(se, ee, map);
113        }
114        (SpannedExprKind::Multiplicative(sl, _, sr), Expression::Multiplicative(el, _, er))
115        | (SpannedExprKind::Additive(sl, _, sr), Expression::Additive(el, _, er))
116        | (SpannedExprKind::Inequality(sl, _, sr), Expression::Inequality(el, _, er))
117        | (SpannedExprKind::Equality(sl, _, sr), Expression::Equality(el, _, er))
118        | (SpannedExprKind::Membership(sl, _, sr), Expression::Membership(el, _, er))
119        | (SpannedExprKind::Or(sl, _, sr), Expression::Or(el, _, er)) => {
120            build_span_map_inner(sl, el, map);
121            build_span_map_inner(sr, er, map);
122        }
123        (SpannedExprKind::Type(se, _, _), Expression::Type(ee, _, _)) => {
124            build_span_map_inner(se, ee, map);
125        }
126        (SpannedExprKind::Union(sl, sr), Expression::Union(el, er))
127        | (SpannedExprKind::And(sl, sr), Expression::And(el, er))
128        | (SpannedExprKind::Implies(sl, sr), Expression::Implies(el, er)) => {
129            build_span_map_inner(sl, el, map);
130            build_span_map_inner(sr, er, map);
131        }
132        (SpannedExprKind::Lambda(_, se), Expression::Lambda(_, ee)) => {
133            build_span_map_inner(se, ee, map);
134        }
135        _ => {}
136    }
137}
138
139/// Return a human-readable debug name for an expression variant.
140fn expression_debug_name(expr: &Expression) -> String {
141    match expr {
142        Expression::Term(t) => match t {
143            crate::parser::Term::Literal(lit) => format!("{}", lit),
144            crate::parser::Term::Invocation(inv) => match inv {
145                crate::parser::Invocation::Member(name) => name.clone(),
146                crate::parser::Invocation::Function(name, _) => format!("{}()", name),
147                crate::parser::Invocation::This => "$this".to_string(),
148                crate::parser::Invocation::Index => "$index".to_string(),
149                crate::parser::Invocation::Total => "$total".to_string(),
150            },
151            crate::parser::Term::ExternalConstant(name) => format!("%{}", name),
152            crate::parser::Term::Parenthesized(_) => "()".to_string(),
153        },
154        Expression::Invocation(_, inv) => match inv {
155            crate::parser::Invocation::Member(name) => name.clone(),
156            crate::parser::Invocation::Function(name, _) => format!("{}()", name),
157            crate::parser::Invocation::This => "$this".to_string(),
158            crate::parser::Invocation::Index => "$index".to_string(),
159            crate::parser::Invocation::Total => "$total".to_string(),
160        },
161        Expression::Indexer(_, _) => "[]".to_string(),
162        Expression::Polarity(op, _) => format!("unary {}", op),
163        Expression::Multiplicative(_, op, _) => op.clone(),
164        Expression::Additive(_, op, _) => op.clone(),
165        Expression::Type(_, op, _) => op.clone(),
166        Expression::Union(_, _) => "|".to_string(),
167        Expression::Inequality(_, op, _) => op.clone(),
168        Expression::Equality(_, op, _) => op.clone(),
169        Expression::Membership(_, op, _) => op.clone(),
170        Expression::And(_, _) => "and".to_string(),
171        Expression::Or(_, op, _) => op.clone(),
172        Expression::Implies(_, _) => "implies".to_string(),
173        Expression::Lambda(_, _) => "=>".to_string(),
174        Expression::InstanceSelector(type_name, _) => format!("{} {{...}}", type_name),
175    }
176}