fml_engine/
expressions.rs1use std::collections::HashSet;
7use std::future::Future;
8
9use fml_syntax::{AstNode, SyntaxKind, SyntaxNode, TextSize, parse};
10use serde_json::Value;
11
12#[derive(Debug, Clone, PartialEq)]
15pub struct Item {
16 pub json: Value,
17 pub type_name: Option<String>,
18}
19
20pub trait Expressions {
22 fn evaluate(
25 &self,
26 expression: &str,
27 focus: Option<&Item>,
28 variables: &[(String, Item)],
29 ) -> impl Future<Output = Result<Vec<Item>, String>>;
30}
31
32#[derive(Debug, Default, Clone, Copy)]
34pub struct NoExpressions;
35
36impl Expressions for NoExpressions {
37 async fn evaluate(
38 &self,
39 expression: &str,
40 _focus: Option<&Item>,
41 _variables: &[(String, Item)],
42 ) -> Result<Vec<Item>, String> {
43 Err(format!(
44 "`{expression}` needs FHIRPath; build fml-engine with the `fhirpath` feature or supply an evaluator"
45 ))
46 }
47}
48
49pub fn bind_variables(expression: &str, variables: &HashSet<String>) -> String {
53 const PREFIX: &str = "let _ = ";
54 let source = format!("{PREFIX}{expression};");
55 let parse = parse(&source);
56 let Some(constant) = parse.tree().constants().next() else {
57 return expression.to_owned();
58 };
59 let Some(root) = constant.expression() else {
60 return expression.to_owned();
61 };
62 let mut positions: Vec<TextSize> = root
63 .syntax()
64 .descendants()
65 .filter(|node| node.kind() == SyntaxKind::NameRef && is_path_start(node))
66 .filter_map(|node| {
67 let token = node.first_token()?;
68 (token.kind() == SyntaxKind::Ident && variables.contains(token.text()))
69 .then(|| token.text_range().start())
70 })
71 .collect();
72 positions.sort();
73 let mut out = String::with_capacity(expression.len() + positions.len());
74 let mut last = PREFIX.len();
75 for position in positions {
76 let position = usize::from(position);
77 out.push_str(&source[last..position]);
78 out.push('%');
79 last = position;
80 }
81 out.push_str(&source[last..source.len() - 1]);
82 out
83}
84
85pub fn path_start_names(expression: &str) -> HashSet<String> {
88 let source = format!("let _ = {expression};");
89 let parse = parse(&source);
90 let Some(root) = parse
91 .tree()
92 .constants()
93 .next()
94 .and_then(|constant| constant.expression())
95 else {
96 return HashSet::new();
97 };
98 let names = root
99 .syntax()
100 .descendants()
101 .filter(|node| node.kind() == SyntaxKind::NameRef && is_path_start(node))
102 .filter_map(|node| node.first_token())
103 .filter(|token| token.kind() == SyntaxKind::Ident)
104 .map(|token| token.text().to_owned());
105 let constants = root
107 .syntax()
108 .descendants()
109 .filter(|node| node.kind() == SyntaxKind::ExternalConstant)
110 .filter_map(|node| {
111 node.children_with_tokens()
112 .filter_map(|e| e.into_token())
113 .find(|t| t.kind() == SyntaxKind::Ident)
114 .map(|t| t.text().to_owned())
115 });
116 names.chain(constants).collect()
117}
118
119fn is_path_start(name: &SyntaxNode) -> bool {
121 match name.parent() {
122 Some(parent) if parent.kind() == SyntaxKind::MemberExpr => {
123 parent.children().next().as_ref() == Some(name)
124 }
125 _ => true,
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132
133 #[test]
134 fn binds_variables_at_path_starts() {
135 let variables: HashSet<String> = ["item", "src"].into_iter().map(String::from).collect();
136 assert_eq!(
137 bind_variables("item.answer.valueString", &variables),
138 "%item.answer.valueString"
139 );
140 assert_eq!(
141 bind_variables("linkId.value = 'x' and src.item.exists()", &variables),
142 "linkId.value = 'x' and %src.item.exists()"
143 );
144 assert_eq!(
145 bind_variables("answer.where(item = 1).item", &variables),
146 "answer.where(%item = 1).item"
147 );
148 assert_eq!(bind_variables("%item", &variables), "%item");
149 }
150
151 #[test]
152 fn finds_the_names_that_can_be_variables() {
153 let names = path_start_names("item.answer.where(x > 1) and %s1.substring(0) = y.z");
154 let mut names: Vec<_> = names.into_iter().collect();
155 names.sort();
156 assert_eq!(names, vec!["item", "s1", "x", "y"]);
157 }
158}