Skip to main content

helios_fhirpath/
parse_debug.rs

1//! Parse debug tree generation for FHIRPath expressions
2//!
3//! This module provides functionality to convert FHIRPath AST (Abstract Syntax Tree)
4//! into the JSON format expected by fhirpath-lab and other tools. The format includes
5//! expression types, names, arguments, and optional return type information.
6
7use crate::parser::{
8    Expression, Invocation, Literal, SpannedExprKind, SpannedExpression, SpannedInvocation,
9    SpannedTerm, Term, TypeSpecifier,
10};
11use crate::type_inference::{TypeContext, infer_expression_type};
12use serde_json::{Value, json};
13
14/// Convert a FHIRPath expression AST to a JSON debug tree
15///
16/// The output format matches the structure expected by fhirpath-lab:
17/// ```json
18/// {
19///   "ExpressionType": "BinaryExpression",
20///   "Name": "|",
21///   "Arguments": [...],
22///   "ReturnType": "string[]"
23/// }
24/// ```
25pub fn expression_to_debug_tree(expr: &Expression, context: &TypeContext) -> Value {
26    expression_to_debug_tree_inner(expr, context)
27}
28
29fn expression_to_debug_tree_inner(expr: &Expression, context: &TypeContext) -> Value {
30    // Get the inferred type for this expression
31    let return_type = infer_expression_type(expr, context).map(|t| t.to_display_string());
32
33    let mut node = match expr {
34        Expression::Term(term) => term_to_debug_tree(term, context),
35
36        Expression::Invocation(base_expr, invocation) => {
37            // For invocations, we need to handle the structure differently
38            // The invocation is the main node, and the base expression is its first argument
39            let mut inv_node = invocation_to_debug_tree(invocation, context);
40
41            // Get existing arguments or create empty array
42            let mut args = inv_node
43                .get("Arguments")
44                .and_then(|a| a.as_array())
45                .cloned()
46                .unwrap_or_default();
47
48            // Insert the base expression as the first argument (implicit "that")
49            let base_node = expression_to_debug_tree_inner(base_expr, context);
50            args.insert(0, base_node);
51
52            inv_node["Arguments"] = json!(args);
53            inv_node
54        }
55
56        Expression::Indexer(expr, index) => {
57            json!({
58                "ExpressionType": "IndexerExpression",
59                "Name": "[]",
60                "Arguments": vec![
61                    expression_to_debug_tree_inner(expr, context),
62                    expression_to_debug_tree_inner(index, context)
63                ]
64            })
65        }
66
67        Expression::Polarity(op, expr) => {
68            json!({
69                "ExpressionType": "UnaryExpression",
70                "Name": op.to_string(),
71                "Arguments": vec![expression_to_debug_tree_inner(expr, context)]
72            })
73        }
74
75        Expression::Multiplicative(left, op, right)
76        | Expression::Additive(left, op, right)
77        | Expression::Inequality(left, op, right)
78        | Expression::Equality(left, op, right)
79        | Expression::Membership(left, op, right) => {
80            json!({
81                "ExpressionType": "BinaryExpression",
82                "Name": op,
83                "Arguments": vec![
84                    expression_to_debug_tree_inner(left, context),
85                    expression_to_debug_tree_inner(right, context)
86                ]
87            })
88        }
89
90        Expression::Type(expr, op, type_spec) => {
91            json!({
92                "ExpressionType": "TypeExpression",
93                "Name": op,
94                "Arguments": vec![
95                    expression_to_debug_tree_inner(expr, context),
96                    type_specifier_to_debug_tree(type_spec)
97                ]
98            })
99        }
100
101        Expression::Union(left, right) => {
102            json!({
103                "ExpressionType": "BinaryExpression",
104                "Name": "|",
105                "Arguments": vec![
106                    expression_to_debug_tree_inner(left, context),
107                    expression_to_debug_tree_inner(right, context)
108                ]
109            })
110        }
111
112        Expression::And(left, right) => {
113            json!({
114                "ExpressionType": "BinaryExpression",
115                "Name": "and",
116                "Arguments": vec![
117                    expression_to_debug_tree_inner(left, context),
118                    expression_to_debug_tree_inner(right, context)
119                ]
120            })
121        }
122
123        Expression::Or(left, op, right) => {
124            json!({
125                "ExpressionType": "BinaryExpression",
126                "Name": op,
127                "Arguments": vec![
128                    expression_to_debug_tree_inner(left, context),
129                    expression_to_debug_tree_inner(right, context)
130                ]
131            })
132        }
133
134        Expression::Implies(left, right) => {
135            json!({
136                "ExpressionType": "BinaryExpression",
137                "Name": "implies",
138                "Arguments": vec![
139                    expression_to_debug_tree_inner(left, context),
140                    expression_to_debug_tree_inner(right, context)
141                ]
142            })
143        }
144
145        Expression::Lambda(param, expr) => {
146            let mut node = json!({
147                "ExpressionType": "LambdaExpression",
148                "Name": "=>",
149                "Arguments": vec![expression_to_debug_tree_inner(expr, context)]
150            });
151            if let Some(param_name) = param {
152                node["Parameter"] = json!(param_name);
153            }
154            node
155        }
156        Expression::InstanceSelector(type_name, fields) => {
157            let field_nodes: Vec<Value> = fields
158                .iter()
159                .map(|(name, expr)| {
160                    json!({
161                        "FieldName": name,
162                        "Value": expression_to_debug_tree_inner(expr, context)
163                    })
164                })
165                .collect();
166            json!({
167                "ExpressionType": "InstanceSelector",
168                "TypeName": type_name,
169                "Fields": field_nodes
170            })
171        }
172    };
173
174    // Add return type if available
175    if let Some(rt) = return_type {
176        node["ReturnType"] = json!(rt);
177    }
178
179    node
180}
181
182fn term_to_debug_tree(term: &Term, context: &TypeContext) -> Value {
183    match term {
184        Term::Literal(lit) => literal_to_debug_tree(lit),
185
186        Term::Invocation(invocation) => {
187            // For a standalone invocation (e.g., at the start of an expression),
188            // we need to add an implicit "builtin.that" as the context
189            let mut inv_node = invocation_to_debug_tree(invocation, context);
190
191            // Add implicit "that" context as first argument for member access
192            if matches!(invocation, Invocation::Member(_)) {
193                let that_node = json!({
194                    "ExpressionType": "AxisExpression",
195                    "Name": "builtin.that",
196                    "ReturnType": context.current_type.as_ref()
197                        .map(|t| t.to_display_string())
198                        .unwrap_or_else(|| "Any".to_string())
199                });
200
201                let mut args = vec![that_node];
202                if let Some(existing_args) = inv_node.get("Arguments").and_then(|a| a.as_array()) {
203                    args.extend(existing_args.clone());
204                }
205                inv_node["Arguments"] = json!(args);
206            }
207
208            inv_node
209        }
210
211        Term::ExternalConstant(name) => {
212            let mut node = json!({
213                "ExpressionType": "VariableRefExpression",
214                "Name": name
215            });
216
217            // Add type if variable is known
218            if let Some(var_type) = context.variables.get(name) {
219                node["ReturnType"] = json!(var_type.to_display_string());
220            }
221
222            node
223        }
224
225        Term::Parenthesized(expr) => expression_to_debug_tree_inner(expr, context),
226    }
227}
228
229fn literal_to_debug_tree(literal: &Literal) -> Value {
230    match literal {
231        Literal::Null => {
232            json!({
233                "ExpressionType": "ConstantExpression",
234                "Name": "{}",
235                "ReturnType": "null"
236            })
237        }
238
239        Literal::Boolean(b) => {
240            json!({
241                "ExpressionType": "ConstantExpression",
242                "Name": b.to_string(),
243                "ReturnType": "system.Boolean"
244            })
245        }
246
247        Literal::String(s) => {
248            json!({
249                "ExpressionType": "ConstantExpression",
250                "Name": s,
251                "ReturnType": "system.String"
252            })
253        }
254
255        Literal::Number(n) => {
256            json!({
257                "ExpressionType": "ConstantExpression",
258                "Name": n.to_string(),
259                "ReturnType": "system.Decimal"
260            })
261        }
262
263        Literal::Integer(i) => {
264            json!({
265                "ExpressionType": "ConstantExpression",
266                "Name": i.to_string(),
267                "ReturnType": "system.Integer"
268            })
269        }
270
271        Literal::Date(d) => {
272            json!({
273                "ExpressionType": "ConstantExpression",
274                "Name": format!("@{}", d.original_string()),
275                "ReturnType": "system.Date"
276            })
277        }
278
279        Literal::DateTime(dt) => {
280            json!({
281                "ExpressionType": "ConstantExpression",
282                "Name": format!("@{}", dt.original_string()),
283                "ReturnType": "system.DateTime"
284            })
285        }
286
287        Literal::Time(t) => {
288            json!({
289                "ExpressionType": "ConstantExpression",
290                "Name": format!("@T{}", t.original_string()),
291                "ReturnType": "system.Time"
292            })
293        }
294
295        Literal::Quantity(value, unit) => {
296            json!({
297                "ExpressionType": "ConstantExpression",
298                "Name": format!("{} '{}'", value, unit),
299                "ReturnType": "system.Quantity"
300            })
301        }
302    }
303}
304
305fn invocation_to_debug_tree(invocation: &Invocation, context: &TypeContext) -> Value {
306    match invocation {
307        Invocation::Function(name, args) => {
308            let mut node = json!({
309                "ExpressionType": "FunctionCallExpression",
310                "Name": name
311            });
312
313            if !args.is_empty() {
314                node["Arguments"] = json!(
315                    args.iter()
316                        .map(|arg| expression_to_debug_tree_inner(arg, context))
317                        .collect::<Vec<_>>()
318                );
319            } else {
320                node["Arguments"] = json!([]);
321            }
322
323            node
324        }
325
326        Invocation::Member(name) => {
327            json!({
328                "ExpressionType": "ChildExpression",
329                "Name": name,
330                "Arguments": []
331            })
332        }
333
334        Invocation::This => {
335            json!({
336                "ExpressionType": "AxisExpression",
337                "Name": "builtin.this"
338            })
339        }
340
341        Invocation::Index => {
342            json!({
343                "ExpressionType": "AxisExpression",
344                "Name": "builtin.index"
345            })
346        }
347
348        Invocation::Total => {
349            json!({
350                "ExpressionType": "AxisExpression",
351                "Name": "builtin.total"
352            })
353        }
354    }
355}
356
357fn type_specifier_to_debug_tree(type_spec: &TypeSpecifier) -> Value {
358    match type_spec {
359        TypeSpecifier::QualifiedIdentifier(namespace_or_type, type_opt) => {
360            let type_name = match type_opt {
361                Some(t) => format!("{}.{}", namespace_or_type, t),
362                None => namespace_or_type.clone(),
363            };
364            json!({
365                "ExpressionType": "TypeSpecifier",
366                "Name": type_name
367            })
368        }
369    }
370}
371
372/// Convert a spanned FHIRPath expression AST to a JSON debug tree with Position and Length
373///
374/// This produces the same structure as `expression_to_debug_tree` but includes
375/// `Position` (0-based char offset) and `Length` (char count) fields on every node,
376/// matching the fhirpath-lab JsonNode interface.
377pub fn spanned_expression_to_debug_tree(expr: &SpannedExpression, context: &TypeContext) -> Value {
378    spanned_expression_to_debug_tree_inner(expr, context)
379}
380
381fn spanned_expression_to_debug_tree_inner(
382    expr: &SpannedExpression,
383    context: &TypeContext,
384) -> Value {
385    // Get the inferred type using the unspanned expression
386    let unspanned = expr.to_expression();
387    let return_type = infer_expression_type(&unspanned, context).map(|t| t.to_display_string());
388
389    let mut node = match &expr.kind {
390        SpannedExprKind::Term(term) => spanned_term_to_debug_tree(term, expr, context),
391
392        SpannedExprKind::Invocation(base_expr, invocation) => {
393            let mut inv_node = spanned_invocation_to_debug_tree(invocation, context);
394
395            let mut args = inv_node
396                .get("Arguments")
397                .and_then(|a| a.as_array())
398                .cloned()
399                .unwrap_or_default();
400
401            let base_node = spanned_expression_to_debug_tree_inner(base_expr, context);
402            args.insert(0, base_node);
403
404            inv_node["Arguments"] = json!(args);
405            inv_node
406        }
407
408        SpannedExprKind::Indexer(expr_inner, index) => {
409            json!({
410                "ExpressionType": "IndexerExpression",
411                "Name": "[]",
412                "Arguments": vec![
413                    spanned_expression_to_debug_tree_inner(expr_inner, context),
414                    spanned_expression_to_debug_tree_inner(index, context)
415                ]
416            })
417        }
418
419        SpannedExprKind::Polarity(op, expr_inner) => {
420            json!({
421                "ExpressionType": "UnaryExpression",
422                "Name": op.to_string(),
423                "Arguments": vec![spanned_expression_to_debug_tree_inner(expr_inner, context)]
424            })
425        }
426
427        SpannedExprKind::Multiplicative(left, op, right)
428        | SpannedExprKind::Additive(left, op, right)
429        | SpannedExprKind::Inequality(left, op, right)
430        | SpannedExprKind::Equality(left, op, right)
431        | SpannedExprKind::Membership(left, op, right) => {
432            json!({
433                "ExpressionType": "BinaryExpression",
434                "Name": op,
435                "Arguments": vec![
436                    spanned_expression_to_debug_tree_inner(left, context),
437                    spanned_expression_to_debug_tree_inner(right, context)
438                ]
439            })
440        }
441
442        SpannedExprKind::Type(expr_inner, op, type_spec) => {
443            json!({
444                "ExpressionType": "TypeExpression",
445                "Name": op,
446                "Arguments": vec![
447                    spanned_expression_to_debug_tree_inner(expr_inner, context),
448                    type_specifier_to_debug_tree(type_spec)
449                ]
450            })
451        }
452
453        SpannedExprKind::Union(left, right) => {
454            json!({
455                "ExpressionType": "BinaryExpression",
456                "Name": "|",
457                "Arguments": vec![
458                    spanned_expression_to_debug_tree_inner(left, context),
459                    spanned_expression_to_debug_tree_inner(right, context)
460                ]
461            })
462        }
463
464        SpannedExprKind::And(left, right) => {
465            json!({
466                "ExpressionType": "BinaryExpression",
467                "Name": "and",
468                "Arguments": vec![
469                    spanned_expression_to_debug_tree_inner(left, context),
470                    spanned_expression_to_debug_tree_inner(right, context)
471                ]
472            })
473        }
474
475        SpannedExprKind::Or(left, op, right) => {
476            json!({
477                "ExpressionType": "BinaryExpression",
478                "Name": op,
479                "Arguments": vec![
480                    spanned_expression_to_debug_tree_inner(left, context),
481                    spanned_expression_to_debug_tree_inner(right, context)
482                ]
483            })
484        }
485
486        SpannedExprKind::Implies(left, right) => {
487            json!({
488                "ExpressionType": "BinaryExpression",
489                "Name": "implies",
490                "Arguments": vec![
491                    spanned_expression_to_debug_tree_inner(left, context),
492                    spanned_expression_to_debug_tree_inner(right, context)
493                ]
494            })
495        }
496
497        SpannedExprKind::Lambda(param, expr_inner) => {
498            let mut node = json!({
499                "ExpressionType": "LambdaExpression",
500                "Name": "=>",
501                "Arguments": vec![spanned_expression_to_debug_tree_inner(expr_inner, context)]
502            });
503            if let Some(param_name) = param {
504                node["Parameter"] = json!(param_name);
505            }
506            node
507        }
508
509        SpannedExprKind::InstanceSelector(type_name, fields) => {
510            let field_nodes: Vec<Value> = fields
511                .iter()
512                .map(|(name, expr_inner)| {
513                    json!({
514                        "FieldName": name,
515                        "Value": spanned_expression_to_debug_tree_inner(expr_inner, context)
516                    })
517                })
518                .collect();
519            json!({
520                "ExpressionType": "InstanceSelector",
521                "TypeName": type_name,
522                "Fields": field_nodes
523            })
524        }
525    };
526
527    // Add Position and Length from the span
528    node["Position"] = json!(expr.span.position);
529    node["Length"] = json!(expr.span.length);
530
531    // Add return type if available
532    if let Some(rt) = return_type {
533        node["ReturnType"] = json!(rt);
534    }
535
536    node
537}
538
539fn spanned_term_to_debug_tree(
540    term: &SpannedTerm,
541    parent: &SpannedExpression,
542    context: &TypeContext,
543) -> Value {
544    match term {
545        SpannedTerm::Literal(lit) => literal_to_debug_tree(lit),
546
547        SpannedTerm::Invocation(invocation) => {
548            let mut inv_node = spanned_invocation_to_debug_tree(invocation, context);
549
550            // Add implicit "that" context as first argument for member access
551            if matches!(invocation, SpannedInvocation::Member(_)) {
552                let that_node = json!({
553                    "ExpressionType": "AxisExpression",
554                    "Name": "builtin.that",
555                    "Position": parent.span.position,
556                    "Length": 0,
557                    "ReturnType": context.current_type.as_ref()
558                        .map(|t| t.to_display_string())
559                        .unwrap_or_else(|| "Any".to_string())
560                });
561
562                let mut args = vec![that_node];
563                if let Some(existing_args) = inv_node.get("Arguments").and_then(|a| a.as_array()) {
564                    args.extend(existing_args.clone());
565                }
566                inv_node["Arguments"] = json!(args);
567            }
568
569            inv_node
570        }
571
572        SpannedTerm::ExternalConstant(name) => {
573            let mut node = json!({
574                "ExpressionType": "VariableRefExpression",
575                "Name": name
576            });
577            if let Some(var_type) = context.variables.get(name) {
578                node["ReturnType"] = json!(var_type.to_display_string());
579            }
580            node
581        }
582
583        SpannedTerm::Parenthesized(expr) => spanned_expression_to_debug_tree_inner(expr, context),
584    }
585}
586
587fn spanned_invocation_to_debug_tree(
588    invocation: &SpannedInvocation,
589    context: &TypeContext,
590) -> Value {
591    match invocation {
592        SpannedInvocation::Function(name, args) => {
593            let mut node = json!({
594                "ExpressionType": "FunctionCallExpression",
595                "Name": name
596            });
597
598            if !args.is_empty() {
599                node["Arguments"] = json!(
600                    args.iter()
601                        .map(|arg| spanned_expression_to_debug_tree_inner(arg, context))
602                        .collect::<Vec<_>>()
603                );
604            } else {
605                node["Arguments"] = json!([]);
606            }
607
608            node
609        }
610
611        SpannedInvocation::Member(name) => {
612            json!({
613                "ExpressionType": "ChildExpression",
614                "Name": name,
615                "Arguments": []
616            })
617        }
618
619        SpannedInvocation::This => {
620            json!({
621                "ExpressionType": "AxisExpression",
622                "Name": "builtin.this"
623            })
624        }
625
626        SpannedInvocation::Index => {
627            json!({
628                "ExpressionType": "AxisExpression",
629                "Name": "builtin.index"
630            })
631        }
632
633        SpannedInvocation::Total => {
634            json!({
635                "ExpressionType": "AxisExpression",
636                "Name": "builtin.total"
637            })
638        }
639    }
640}
641
642/// Generate parse debug output (textual format) for a FHIRPath expression
643///
644/// This generates a simple text representation of the parse tree with type annotations
645pub fn generate_parse_debug(expr: &Expression) -> String {
646    let mut output = String::new();
647    generate_parse_debug_inner(expr, &mut output, 0);
648    output
649}
650
651fn generate_parse_debug_inner(expr: &Expression, output: &mut String, indent: usize) {
652    let indent_str = "  ".repeat(indent);
653
654    match expr {
655        Expression::Term(term) => match term {
656            Term::Literal(lit) => output.push_str(&format!("{}{:?}\n", indent_str, lit)),
657            Term::Invocation(inv) => output.push_str(&format!("{}{:?}\n", indent_str, inv)),
658            Term::ExternalConstant(name) => output.push_str(&format!("{}%{}\n", indent_str, name)),
659            Term::Parenthesized(expr) => {
660                output.push_str(&format!("{}(\n", indent_str));
661                generate_parse_debug_inner(expr, output, indent + 1);
662                output.push_str(&format!("{})\n", indent_str));
663            }
664        },
665
666        Expression::Invocation(expr, inv) => {
667            generate_parse_debug_inner(expr, output, indent);
668            output.push_str(&format!("{}.{:?}\n", indent_str, inv));
669        }
670
671        Expression::Indexer(expr, index) => {
672            generate_parse_debug_inner(expr, output, indent);
673            output.push_str(&format!("{}[\n", indent_str));
674            generate_parse_debug_inner(index, output, indent + 1);
675            output.push_str(&format!("{}]\n", indent_str));
676        }
677
678        Expression::Polarity(op, expr) => {
679            output.push_str(&format!("{}{}\n", indent_str, op));
680            generate_parse_debug_inner(expr, output, indent + 1);
681        }
682
683        Expression::Multiplicative(left, op, right)
684        | Expression::Additive(left, op, right)
685        | Expression::Inequality(left, op, right)
686        | Expression::Equality(left, op, right)
687        | Expression::Membership(left, op, right) => {
688            generate_parse_debug_inner(left, output, indent);
689            output.push_str(&format!("{}{}\n", indent_str, op));
690            generate_parse_debug_inner(right, output, indent + 1);
691        }
692
693        Expression::Type(expr, op, type_spec) => {
694            generate_parse_debug_inner(expr, output, indent);
695            output.push_str(&format!("{}{} {:?}\n", indent_str, op, type_spec));
696        }
697
698        Expression::Union(left, right) => {
699            generate_parse_debug_inner(left, output, indent);
700            output.push_str(&format!("{}|\n", indent_str));
701            generate_parse_debug_inner(right, output, indent + 1);
702        }
703
704        Expression::And(left, right) => {
705            generate_parse_debug_inner(left, output, indent);
706            output.push_str(&format!("{}and\n", indent_str));
707            generate_parse_debug_inner(right, output, indent + 1);
708        }
709
710        Expression::Or(left, op, right) => {
711            generate_parse_debug_inner(left, output, indent);
712            output.push_str(&format!("{}{}\n", indent_str, op));
713            generate_parse_debug_inner(right, output, indent + 1);
714        }
715
716        Expression::Implies(left, right) => {
717            generate_parse_debug_inner(left, output, indent);
718            output.push_str(&format!("{}implies\n", indent_str));
719            generate_parse_debug_inner(right, output, indent + 1);
720        }
721
722        Expression::Lambda(param, expr) => {
723            if let Some(p) = param {
724                output.push_str(&format!("{}{} =>\n", indent_str, p));
725            } else {
726                output.push_str(&format!("{}=>\n", indent_str));
727            }
728            generate_parse_debug_inner(expr, output, indent + 1);
729        }
730        Expression::InstanceSelector(type_name, fields) => {
731            output.push_str(&format!("{}{} {{\n", indent_str, type_name));
732            for (name, expr) in fields {
733                output.push_str(&format!("{}  {}: ", indent_str, name));
734                generate_parse_debug_inner(expr, output, indent + 2);
735            }
736            output.push_str(&format!("{}}}\n", indent_str));
737        }
738    }
739}