use winnow::combinator::{alt, cut_err, eof, opt, repeat};
use winnow::error::ErrMode;
use winnow::prelude::*;
use winnow::stream::Stream;
use crate::ast::{
Attribute, AttributeGroup, AttributeValue, ConceptReference, DefinitionStatus, Expression,
Refinement, SubExpression,
};
use crate::front::{
Expecting, Failure, PResult, Token, Tokens, backtrack, kind, one_of_kinds, refuse,
};
use crate::lexer::Kind;
fn inner<'i>(token: &Token<'i, Kind>, delimiter: char) -> PResult<&'i str> {
token
.text
.strip_prefix(delimiter)
.and_then(|t| t.strip_suffix(delimiter))
.map_or_else(backtrack, Ok)
}
fn adjacent(previous: &Token<'_, Kind>, next: &Token<'_, Kind>) -> bool {
previous.span.end == next.span.start
}
const fn non_ws_non_pipe(c: char) -> bool {
matches!(c, '\u{21}'..='\u{7b}' | '\u{7d}' | '\u{7e}') || !c.is_ascii()
}
fn is_term(text: &str) -> bool {
!text.is_empty() && text.chars().all(|c| c == ' ' || non_ws_non_pipe(c))
}
fn string_value(text: &str) -> Option<String> {
let mut out = String::with_capacity(text.len());
let mut chars = text.chars();
while let Some(c) = chars.next() {
match c {
'\\' => match chars.next() {
Some(next @ ('"' | '\\')) => out.push(next),
_ => return None,
},
'\t' | '\r' | '\n' => out.push(c),
'"' => return None,
c if c.is_ascii_control() => return None,
c => out.push(c),
}
}
(!out.is_empty()).then_some(out)
}
fn sctid(i: &mut Tokens<'_, Kind>) -> PResult<String> {
kind(Kind::Integer)
.verify(|t: &Token<'_, Kind>| (6..=18).contains(&t.text.len()) && !t.text.starts_with('0'))
.map(|t: &Token<'_, Kind>| t.text.to_owned())
.expecting("a SNOMED CT identifier")
.parse_next(i)
}
fn concept_reference(i: &mut Tokens<'_, Kind>) -> PResult<ConceptReference> {
let concept_id = sctid.parse_next(i)?;
let start = i.checkpoint();
let Some(token) = opt(kind(Kind::Term)).parse_next(i)? else {
return Ok(ConceptReference {
concept_id,
term: None,
});
};
let text = inner(token, '|')?.trim_matches([' ', '\t', '\r', '\n']);
if !is_term(text) {
return refuse(i, &start, "a term");
}
Ok(ConceptReference {
concept_id,
term: Some(text.to_owned()),
})
}
fn integer_value(i: &mut Tokens<'_, Kind>) -> PResult<(String, usize)> {
let start = i.checkpoint();
let sign = opt(one_of_kinds([Kind::Dash, Kind::Plus])).parse_next(i)?;
let digits = kind(Kind::Integer).parse_next(i)?;
if let Some(sign) = sign {
if !adjacent(sign, digits) {
return refuse(i, &start, "a number with no space after its sign");
}
if digits.text.starts_with('0') {
return refuse(i, &start, "a signed number with a non-zero first digit");
}
} else if digits.text.starts_with('0') && digits.text != "0" {
return refuse(i, &start, "a number with no leading zero");
}
let mut out = String::new();
if let Some(sign) = sign {
out.push_str(sign.text);
}
out.push_str(digits.text);
Ok((out, digits.span.end))
}
fn numeric_value(i: &mut Tokens<'_, Kind>) -> PResult<String> {
let start = i.checkpoint();
let (integer, end) = integer_value.parse_next(i)?;
let Some(period) = opt(kind(Kind::Period)).parse_next(i)? else {
return Ok(integer);
};
if end != period.span.start {
return refuse(i, &start, "a decimal with no space around its point");
}
let fraction = kind(Kind::Integer).parse_next(i)?;
if !adjacent(period, fraction) {
return refuse(i, &start, "a decimal with no space around its point");
}
Ok(format!("{integer}.{}", fraction.text))
}
fn expression_value(i: &mut Tokens<'_, Kind>) -> PResult<AttributeValue> {
alt((
concept_reference.map(AttributeValue::Concept),
(
kind(Kind::LeftParen),
cut_err(sub_expression).expecting("a nested sub-expression"),
cut_err(kind(Kind::RightParen)),
)
.map(|(_, sub, _)| AttributeValue::Nested(Box::new(sub))),
))
.parse_next(i)
}
fn attribute_value(i: &mut Tokens<'_, Kind>) -> PResult<AttributeValue> {
let start = i.checkpoint();
if let Some(token) = opt(kind(Kind::String)).parse_next(i)? {
let text = inner(token, '"')?;
return match string_value(text) {
Some(text) => Ok(AttributeValue::Text(text)),
None => refuse(i, &start, "a string value"),
};
}
if let Some(hash) = opt(kind(Kind::Hash)).parse_next(i)? {
let next = i.peek_token().ok_or(ErrMode::Cut(Failure {
expected: Some("a number after '#'"),
}))?;
if !adjacent(hash, next) {
return refuse(i, &start, "a number touching its '#'");
}
return numeric_value.parse_next(i).map(AttributeValue::Number);
}
expression_value
.expecting("a concept reference, a nested expression, a string, or a number")
.parse_next(i)
}
fn attribute(i: &mut Tokens<'_, Kind>) -> PResult<Attribute> {
let name = concept_reference.parse_next(i)?;
kind(Kind::Equal).parse_next(i)?;
let value = cut_err(attribute_value).parse_next(i)?;
Ok(Attribute { name, value })
}
fn attribute_set(i: &mut Tokens<'_, Kind>) -> PResult<Vec<Attribute>> {
let first = attribute.parse_next(i)?;
let mut attributes = vec![first];
loop {
let start = i.checkpoint();
if opt(kind(Kind::Comma)).parse_next(i)?.is_none() {
break;
}
let Some(next) = opt(attribute).parse_next(i)? else {
i.reset(&start);
break;
};
attributes.push(next);
}
Ok(attributes)
}
fn attribute_group(i: &mut Tokens<'_, Kind>) -> PResult<AttributeGroup> {
kind(Kind::LeftBrace).parse_next(i)?;
let attributes = cut_err(attribute_set)
.expecting("an attribute")
.parse_next(i)?;
cut_err(kind(Kind::RightBrace)).parse_next(i)?;
Ok(AttributeGroup { attributes })
}
fn refinement(i: &mut Tokens<'_, Kind>) -> PResult<Refinement> {
let (ungrouped, first) = match opt(attribute_set).parse_next(i)? {
Some(set) => (set, None),
None => (Vec::new(), Some(attribute_group.parse_next(i)?)),
};
let rest: Vec<AttributeGroup> = repeat(0.., group_after_optional_comma).parse_next(i)?;
let mut groups = Vec::with_capacity(rest.len().saturating_add(1));
groups.extend(first);
groups.extend(rest);
Ok(Refinement { ungrouped, groups })
}
fn group_after_optional_comma(i: &mut Tokens<'_, Kind>) -> PResult<AttributeGroup> {
let start = i.checkpoint();
opt(kind(Kind::Comma)).parse_next(i)?;
let Some(group) = opt(attribute_group).parse_next(i)? else {
i.reset(&start);
return backtrack();
};
Ok(group)
}
fn focus_concept(i: &mut Tokens<'_, Kind>) -> PResult<Vec<ConceptReference>> {
let first = concept_reference.parse_next(i)?;
let mut focus = vec![first];
while opt(kind(Kind::Plus)).parse_next(i)?.is_some() {
focus.push(cut_err(concept_reference).parse_next(i)?);
}
Ok(focus)
}
fn sub_expression(i: &mut Tokens<'_, Kind>) -> PResult<SubExpression> {
let focus = focus_concept.parse_next(i)?;
let refinement = match opt(kind(Kind::Colon)).parse_next(i)? {
Some(_) => Some(
cut_err(refinement)
.expecting("a refinement")
.parse_next(i)?,
),
None => None,
};
Ok(SubExpression { focus, refinement })
}
fn definition_status(i: &mut Tokens<'_, Kind>) -> PResult<DefinitionStatus> {
alt((
kind(Kind::EquivalentTo).value(DefinitionStatus::EquivalentTo),
kind(Kind::SubtypeOf).value(DefinitionStatus::SubtypeOf),
))
.parse_next(i)
}
pub fn whole(i: &mut Tokens<'_, Kind>) -> PResult<Expression> {
let definition_status = opt(definition_status).parse_next(i)?;
let sub_expression = sub_expression.expecting("an expression").parse_next(i)?;
eof.expecting("the end of the expression").parse_next(i)?;
Ok(Expression {
definition_status,
sub_expression,
})
}