Skip to main content

snomed_ecl_engine/ecl/
descriptions.rs

1use 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}