snomed-ecl-engine 0.2.1

Compact SNOMED CT ECL engine and RF2 index builder
Documentation
use super::{Comparison, Expr, ParseErrorKind, Parser, Result, MAX_DEPTH, MAX_NODES};

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ConceptFilter {
    Active(Comparison, Option<bool>),
    Defined(Comparison, Vec<bool>),
    DefinitionStatus(Comparison, Box<Expr>),
    Module(Comparison, Box<Expr>),
    EffectiveTime(Comparison, Vec<Option<u32>>),
}

impl Parser<'_> {
    pub(super) fn active_value(&mut self) -> Result<Option<bool>> {
        if self.take("*") || self.keyword("any") || self.take("\"*\"") {
            Ok(None)
        } else if self.take("1") || self.keyword("true") {
            Ok(Some(true))
        } else if self.take("0") || self.keyword("false") {
            Ok(Some(false))
        } else {
            Err(self.unexpected())
        }
    }
    pub(super) fn concept_filters(&mut self, depth: usize) -> Result<Vec<ConceptFilter>> {
        if depth > MAX_DEPTH {
            return Err(self.error(ParseErrorKind::Limit, "Filter nesting exceeds 64"));
        }
        self.take("{{");
        self.ws()?;
        if !(self.take("C") || self.take("c")) {
            return Err(self.unexpected());
        }
        self.ws()?;
        let mut filters = Vec::new();
        loop {
            let name = self.filter_name();
            self.pos += name.len();
            let comparison = self.comparison()?;
            if name != "effectivetime" && !matches!(comparison, Comparison::Eq | Comparison::Ne) {
                return Err(self.error(
                    ParseErrorKind::Syntax,
                    "This filter requires equality or inequality",
                ));
            }
            let filter = match name.as_str() {
                "active" => ConceptFilter::Active(comparison, self.active_value()?),
                "definitionstatus" => {
                    let set = self.take("(");
                    self.ws()?;
                    let mut values = Vec::new();
                    loop {
                        values.push(if self.keyword("primitive") {
                            false
                        } else if self.keyword("defined") {
                            true
                        } else {
                            return Err(self.unexpected());
                        });
                        let separated = self.ws()?;
                        if !set || self.take(")") {
                            break;
                        }
                        if !separated {
                            return Err(self.unexpected());
                        }
                    }
                    ConceptFilter::Defined(comparison, values)
                }
                "definitionstatusid" => ConceptFilter::DefinitionStatus(
                    comparison,
                    Box::new(self.filter_concepts(depth)?),
                ),
                "moduleid" => {
                    ConceptFilter::Module(comparison, Box::new(self.filter_concepts(depth)?))
                }
                "effectivetime" => {
                    let set = self.take("(");
                    self.ws()?;
                    let mut values = Vec::new();
                    loop {
                        values.push(self.filter_date()?);
                        let separated = self.ws()?;
                        if !set || self.take(")") {
                            break;
                        }
                        if !separated {
                            return Err(self.unexpected());
                        }
                    }
                    ConceptFilter::EffectiveTime(comparison, values)
                }
                _ => return Err(self.error(ParseErrorKind::Syntax, "Unknown concept filter")),
            };
            self.nodes += 1;
            if self.nodes > MAX_NODES {
                return Err(self.error(ParseErrorKind::Limit, "Too many filter nodes"));
            }
            filters.push(filter);
            self.ws()?;
            if self.take("}}") {
                return Ok(filters);
            }
            if !self.take(",") {
                return Err(self.unexpected());
            }
            self.ws()?;
        }
    }

    pub(super) fn filter_concepts(&mut self, depth: usize) -> Result<Expr> {
        let saved = (self.pos, self.nodes);
        if self.take("(") {
            self.ws()?;
            let mut values = Vec::new();
            loop {
                let start = self.pos;
                while self.rest().starts_with(|c: char| c.is_ascii_digit()) {
                    self.pos += 1;
                }
                let digits = &self.text[start..self.pos];
                if !(6..=18).contains(&digits.len()) || digits.starts_with('0') {
                    break;
                }
                let code = digits.parse().map_err(|_| self.unexpected())?;
                values.push(self.node(Expr::Concept(code))?);
                let mut separated = self.ws()?;
                if self.take("|") {
                    self.term()?;
                    separated = self.ws()?;
                }
                if self.take(")") {
                    if values.len() == 1 {
                        // `(x)` is also a bracketed subexpression, which filters may
                        // follow: `moduleId = (x) {{ C active = 1 }}`.
                        break;
                    }
                    return self.node(Expr::Or(values));
                }
                if !separated {
                    break;
                }
            }
        }
        (self.pos, self.nodes) = saved;
        self.subexpression(depth)
    }

    pub(super) fn filter_date(&mut self) -> Result<Option<u32>> {
        if !self.take("\"") {
            return Err(self.unexpected());
        }
        if self.take("\"") {
            return Ok(None);
        }
        let start = self.pos;
        while self.rest().starts_with(|c: char| c.is_ascii_digit()) {
            self.pos += 1;
        }
        let text = &self.text[start..self.pos];
        if text.len() != 8 || text.starts_with('0') || !self.take("\"") {
            return Err(self.error(
                ParseErrorKind::Syntax,
                "Expected YYYYMMDD or an empty effective time",
            ));
        }
        let value: u32 = text.parse().map_err(|_| self.unexpected())?;
        let at = start;
        let year = value / 10000;
        let month = value / 100 % 100;
        let day = value % 100;
        let days = match month {
            1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
            4 | 6 | 9 | 11 => 30,
            2 if year.is_multiple_of(400)
                || year.is_multiple_of(4) && !year.is_multiple_of(100) =>
            {
                29
            }
            2 => 28,
            _ => 0,
        };
        // The grammar bounds the month to 01-12 and the day to 01-31; a day the
        // month lacks, such as 0931, is grammatical but names no date.
        if !(1..=12).contains(&month) || !(1..=31).contains(&day) {
            return Err(self.error(ParseErrorKind::Syntax, "Invalid effective time date"));
        }
        if day > days {
            self.refuse(at, "Effective time is not a calendar date");
        }
        Ok(Some(value))
    }
}