snomed_ecl_engine/ecl/
descriptions.rs1use super::*;
2
3#[derive(Clone, Debug, PartialEq, Eq)]
4pub enum DescriptionFilter {
5 Term(Comparison, Vec<SearchTerm>),
6 Metadata(ConceptFilter),
7 Language(Comparison, Vec<[u8; 2]>),
8 Type(Comparison, Box<Expr>),
9 Id(Comparison, Vec<u64>),
10 Dialect(Comparison, Vec<Dialect>),
11}
12
13#[derive(Clone, Debug, PartialEq, Eq)]
14pub struct Dialect {
15 pub refsets: Expr,
16 pub acceptability: Vec<u64>,
17}
18
19impl Parser<'_> {
20 pub(super) fn description_filters(&mut self, depth: usize) -> Result<Vec<DescriptionFilter>> {
21 if depth > MAX_DEPTH {
22 return Err(self.error(ParseErrorKind::Limit, "Filter nesting exceeds 64"));
23 }
24 self.take("{{");
25 self.ws()?;
26 if self.rest().starts_with(['d', 'D'])
27 && self.filter_name() != "dialect"
28 && self.filter_name() != "dialectid"
29 {
30 self.pos += 1;
31 self.ws()?;
32 }
33 let mut filters = Vec::new();
34 loop {
35 let name = self.filter_name();
36 self.pos += name.len();
37 let comparison = self.comparison()?;
38 if name != "effectivetime" && !matches!(comparison, Comparison::Eq | Comparison::Ne) {
39 return Err(self.unexpected());
40 }
41 let filter = match name.as_str() {
42 "term" => DescriptionFilter::Term(comparison, self.search_terms()?),
43 "active" => DescriptionFilter::Metadata(ConceptFilter::Active(
44 comparison,
45 self.active_value()?,
46 )),
47 "moduleid" => DescriptionFilter::Metadata(ConceptFilter::Module(
48 comparison,
49 Box::new(self.filter_concepts(depth)?),
50 )),
51 "effectivetime" => DescriptionFilter::Metadata(ConceptFilter::EffectiveTime(
52 comparison,
53 self.description_list(|p| p.filter_date())?,
54 )),
55 "language" => {
56 let values = self.description_list(|p| {
57 let word = p.word();
58 if word.len() != 2 || !word.bytes().all(|b| b.is_ascii_alphabetic()) {
59 return Err(p.unexpected());
60 }
61 let value = word.to_ascii_lowercase().as_bytes().try_into().unwrap();
62 p.pos += 2;
63 Ok(value)
64 })?;
65 DescriptionFilter::Language(comparison, values)
66 }
67 "id" => DescriptionFilter::Id(
68 comparison,
69 self.description_list(|p| p.description_id())?,
70 ),
71 "typeid" => {
72 DescriptionFilter::Type(comparison, Box::new(self.filter_concepts(depth)?))
73 }
74 "type" => {
75 let values = self.description_list(|p| {
76 let code = if p.keyword("syn") || p.keyword("synonym") {
77 900000000000013009
78 } else if p.keyword("fsn") || p.keyword("fullySpecifiedName") {
79 900000000000003001
80 } else if p.keyword("def") || p.keyword("definition") {
81 900000000000550004
82 } else {
83 return Err(p.unexpected());
84 };
85 p.node(Expr::Concept(code))
86 })?;
87 let expr = if values.len() == 1 {
88 values.into_iter().next().unwrap()
89 } else {
90 self.node(Expr::Or(values))?
91 };
92 DescriptionFilter::Type(comparison, Box::new(expr))
93 }
94 "dialectid" | "dialect" => DescriptionFilter::Dialect(
95 comparison,
96 self.description_dialects(name == "dialect", depth)?,
97 ),
98 _ => return Err(self.error(ParseErrorKind::Syntax, "Unknown description filter")),
99 };
100 self.nodes += 1;
101 if self.nodes > MAX_NODES {
102 return Err(self.error(ParseErrorKind::Limit, "Too many filters"));
103 }
104 filters.push(filter);
105 self.ws()?;
106 if self.take("}}") {
107 return Ok(filters);
108 }
109 if !self.take(",") {
110 return Err(self.unexpected());
111 }
112 self.ws()?;
113 }
114 }
115
116 fn description_dialects(&mut self, aliases: bool, depth: usize) -> Result<Vec<Dialect>> {
117 let saved = (self.pos, self.nodes);
118 let mut dialects = if aliases || self.rest().starts_with('(') {
119 let list = self.description_list(|p| {
120 let refsets = if aliases {
121 let alias = p.alias()?;
122 p.node(Expr::DialectAlias(alias))?
123 } else {
124 let id = p.description_id()?;
125 let end = p.pos;
126 p.ws()?;
127 if p.take("|") {
128 p.term()?;
129 } else {
130 p.pos = end;
131 }
132 p.node(Expr::Concept(id))?
133 };
134 let end = p.pos;
135 p.ws()?;
136 let acceptability = if p.rest().starts_with('(') {
137 p.acceptabilities()?
138 } else {
139 p.pos = end;
140 Vec::new()
141 };
142 Ok(Dialect {
143 refsets,
144 acceptability,
145 })
146 });
147 match list {
148 Ok(list) => list,
149 Err(e) if aliases => return Err(e),
150 Err(_) => {
151 (self.pos, self.nodes) = saved;
152 vec![Dialect {
153 refsets: self.subexpression(depth)?,
154 acceptability: Vec::new(),
155 }]
156 }
157 }
158 } else {
159 vec![Dialect {
160 refsets: self.subexpression(depth)?,
161 acceptability: Vec::new(),
162 }]
163 };
164 self.ws()?;
165 if self.rest().starts_with('(') {
166 let shared = self.acceptabilities()?;
167 for dialect in &mut dialects {
168 if dialect.acceptability.is_empty() {
169 dialect.acceptability = shared.clone();
170 }
171 }
172 }
173 Ok(dialects)
174 }
175 fn description_list<T>(
176 &mut self,
177 mut item: impl FnMut(&mut Self) -> Result<T>,
178 ) -> Result<Vec<T>> {
179 let list = self.take("(");
180 self.ws()?;
181 let mut values = Vec::new();
182 loop {
183 values.push(item(self)?);
184 let separated = self.ws()?;
185 if !list || self.take(")") {
186 return Ok(values);
187 }
188 if !separated {
189 return Err(self.unexpected());
190 }
191 }
192 }
193 fn description_id(&mut self) -> Result<u64> {
194 let start = self.pos;
195 while self.rest().starts_with(|c: char| c.is_ascii_digit()) {
196 self.pos += 1;
197 }
198 let digits = &self.text[start..self.pos];
199 if !(6..=18).contains(&digits.len()) || digits.starts_with('0') {
200 return Err(self.unexpected());
201 }
202 digits.parse().map_err(|_| self.unexpected())
203 }
204 fn acceptabilities(&mut self) -> Result<Vec<u64>> {
205 self.description_list(|p| {
206 if p.keyword("prefer") || p.keyword("preferred") {
207 Ok(900000000000548007)
208 } else if p.keyword("accept") || p.keyword("acceptable") {
209 Ok(900000000000549004)
210 } else {
211 let code = p.description_id()?;
212 let end = p.pos;
213 p.ws()?;
214 if p.take("|") {
215 p.term()?;
216 } else {
217 p.pos = end;
218 }
219 Ok(code)
220 }
221 })
222 }
223}