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