snomed_ecl_engine/ecl/
filters.rs1use super::{Comparison, Expr, ParseErrorKind, Parser, Result, MAX_DEPTH, MAX_NODES};
2
3#[derive(Clone, Debug, PartialEq, Eq)]
4pub enum ConceptFilter {
5 Active(Comparison, Option<bool>),
6 Defined(Comparison, Vec<bool>),
7 DefinitionStatus(Comparison, Box<Expr>),
8 Module(Comparison, Box<Expr>),
9 EffectiveTime(Comparison, Vec<Option<u32>>),
10}
11
12impl Parser<'_> {
13 pub(super) fn active_value(&mut self) -> Result<Option<bool>> {
14 if self.take("*") || self.keyword("any") || self.take("\"*\"") {
15 Ok(None)
16 } else if self.take("1") || self.keyword("true") {
17 Ok(Some(true))
18 } else if self.take("0") || self.keyword("false") {
19 Ok(Some(false))
20 } else {
21 Err(self.unexpected())
22 }
23 }
24 pub(super) fn concept_filters(&mut self, depth: usize) -> Result<Vec<ConceptFilter>> {
25 if depth > MAX_DEPTH {
26 return Err(self.error(ParseErrorKind::Limit, "Filter nesting exceeds 64"));
27 }
28 self.take("{{");
29 self.ws()?;
30 if !(self.take("C") || self.take("c")) {
31 return Err(self.unexpected());
32 }
33 self.ws()?;
34 let mut filters = Vec::new();
35 loop {
36 let name = self.filter_name();
37 self.pos += name.len();
38 let comparison = self.comparison()?;
39 if name != "effectivetime" && !matches!(comparison, Comparison::Eq | Comparison::Ne) {
40 return Err(self.error(
41 ParseErrorKind::Syntax,
42 "This filter requires equality or inequality",
43 ));
44 }
45 let filter = match name.as_str() {
46 "active" => ConceptFilter::Active(comparison, self.active_value()?),
47 "definitionstatus" => {
48 let set = self.take("(");
49 self.ws()?;
50 let mut values = Vec::new();
51 loop {
52 values.push(if self.keyword("primitive") {
53 false
54 } else if self.keyword("defined") {
55 true
56 } else {
57 return Err(self.unexpected());
58 });
59 let separated = self.ws()?;
60 if !set || self.take(")") {
61 break;
62 }
63 if !separated {
64 return Err(self.unexpected());
65 }
66 }
67 ConceptFilter::Defined(comparison, values)
68 }
69 "definitionstatusid" => ConceptFilter::DefinitionStatus(
70 comparison,
71 Box::new(self.filter_concepts(depth)?),
72 ),
73 "moduleid" => {
74 ConceptFilter::Module(comparison, Box::new(self.filter_concepts(depth)?))
75 }
76 "effectivetime" => {
77 let set = self.take("(");
78 self.ws()?;
79 let mut values = Vec::new();
80 loop {
81 values.push(self.filter_date()?);
82 let separated = self.ws()?;
83 if !set || self.take(")") {
84 break;
85 }
86 if !separated {
87 return Err(self.unexpected());
88 }
89 }
90 ConceptFilter::EffectiveTime(comparison, values)
91 }
92 _ => return Err(self.error(ParseErrorKind::Syntax, "Unknown concept filter")),
93 };
94 self.nodes += 1;
95 if self.nodes > MAX_NODES {
96 return Err(self.error(ParseErrorKind::Limit, "Too many filter nodes"));
97 }
98 filters.push(filter);
99 self.ws()?;
100 if self.take("}}") {
101 return Ok(filters);
102 }
103 if !self.take(",") {
104 return Err(self.unexpected());
105 }
106 self.ws()?;
107 }
108 }
109
110 pub(super) fn filter_concepts(&mut self, depth: usize) -> Result<Expr> {
111 let saved = (self.pos, self.nodes);
112 if self.take("(") {
113 self.ws()?;
114 let mut values = Vec::new();
115 loop {
116 let start = self.pos;
117 while self.rest().starts_with(|c: char| c.is_ascii_digit()) {
118 self.pos += 1;
119 }
120 let digits = &self.text[start..self.pos];
121 if !(6..=18).contains(&digits.len()) || digits.starts_with('0') {
122 break;
123 }
124 let code = digits.parse().map_err(|_| self.unexpected())?;
125 values.push(self.node(Expr::Concept(code))?);
126 let mut separated = self.ws()?;
127 if self.take("|") {
128 self.term()?;
129 separated = self.ws()?;
130 }
131 if self.take(")") {
132 if values.len() == 1 {
133 break;
136 }
137 return self.node(Expr::Or(values));
138 }
139 if !separated {
140 break;
141 }
142 }
143 }
144 (self.pos, self.nodes) = saved;
145 self.subexpression(depth)
146 }
147
148 pub(super) fn filter_date(&mut self) -> Result<Option<u32>> {
149 if !self.take("\"") {
150 return Err(self.unexpected());
151 }
152 if self.take("\"") {
153 return Ok(None);
154 }
155 let start = self.pos;
156 while self.rest().starts_with(|c: char| c.is_ascii_digit()) {
157 self.pos += 1;
158 }
159 let text = &self.text[start..self.pos];
160 if text.len() != 8 || text.starts_with('0') || !self.take("\"") {
161 return Err(self.error(
162 ParseErrorKind::Syntax,
163 "Expected YYYYMMDD or an empty effective time",
164 ));
165 }
166 let value: u32 = text.parse().map_err(|_| self.unexpected())?;
167 let at = start;
168 let year = value / 10000;
169 let month = value / 100 % 100;
170 let day = value % 100;
171 let days = match month {
172 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
173 4 | 6 | 9 | 11 => 30,
174 2 if year.is_multiple_of(400)
175 || year.is_multiple_of(4) && !year.is_multiple_of(100) =>
176 {
177 29
178 }
179 2 => 28,
180 _ => 0,
181 };
182 if !(1..=12).contains(&month) || !(1..=31).contains(&day) {
185 return Err(self.error(ParseErrorKind::Syntax, "Invalid effective time date"));
186 }
187 if day > days {
188 self.refuse(at, "Effective time is not a calendar date");
189 }
190 Ok(Some(value))
191 }
192}