1use super::*;
2use crate::decimal::Decimal;
3use std::cmp::Ordering;
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq)]
6pub struct Cardinality {
7 pub min: u64,
8 pub max: Option<u64>,
9}
10impl Default for Cardinality {
11 fn default() -> Self {
12 Self { min: 1, max: None }
13 }
14}
15impl Cardinality {
16 pub fn contains(self, count: usize) -> bool {
17 count as u64 >= self.min && self.max.is_none_or(|max| count as u64 <= max)
18 }
19}
20
21#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22pub enum Comparison {
23 Eq,
24 Ne,
25 Lt,
26 Le,
27 Gt,
28 Ge,
29}
30impl Comparison {
31 pub fn matches(self, order: Ordering) -> bool {
32 match self {
33 Self::Eq => order == Ordering::Equal,
34 Self::Ne => order != Ordering::Equal,
35 Self::Lt => order == Ordering::Less,
36 Self::Le => order != Ordering::Greater,
37 Self::Gt => order == Ordering::Greater,
38 Self::Ge => order != Ordering::Less,
39 }
40 }
41}
42#[derive(Clone, Debug, PartialEq, Eq)]
43pub enum AttributeValue {
44 Concepts(Box<Expr>),
45 Number(Decimal),
46 Strings(Vec<String>),
47 Boolean(bool),
48}
49#[derive(Clone, Debug, PartialEq, Eq)]
50pub struct AttributeConstraint {
51 pub cardinality: Cardinality,
52 pub reverse: bool,
53 pub name: Box<Expr>,
54 pub comparison: Comparison,
55 pub value: AttributeValue,
56}
57#[derive(Clone, Debug, PartialEq, Eq)]
58pub enum Refinement {
59 Attribute(AttributeConstraint),
60 Group(Cardinality, Box<Refinement>),
61 And(Vec<Refinement>),
62 Or(Vec<Refinement>),
63}
64
65impl Parser<'_> {
66 pub(super) fn keyword(&mut self, word: &str) -> bool {
67 if self.word().eq_ignore_ascii_case(word) {
68 self.pos += word.len();
69 true
70 } else {
71 false
72 }
73 }
74 pub(super) fn value_keyword(&mut self, word: &str) -> bool {
78 let found = self.word();
79 let rest = found.get(word.len()..).unwrap_or("");
80 let joined = found.len() > word.len()
81 && found[..word.len()].eq_ignore_ascii_case(word)
82 && (["and", "or", "minus"]
83 .iter()
84 .any(|op| rest.eq_ignore_ascii_case(op))
85 || rest.eq_ignore_ascii_case("not")
86 && skip_space(&self.rest()[found.len()..]).starts_with('='));
87 if found.eq_ignore_ascii_case(word) || joined {
88 self.pos += word.len();
89 true
90 } else {
91 false
92 }
93 }
94 pub(super) fn filter_name(&self) -> String {
98 const KNOWN: &[&str] = &[
99 "term",
100 "language",
101 "type",
102 "typeid",
103 "dialect",
104 "dialectid",
105 "id",
106 "moduleid",
107 "effectivetime",
108 "active",
109 "definitionstatus",
110 "definitionstatusid",
111 ];
112 let name = self.word().to_ascii_lowercase();
113 if let Some(keyword) = name.strip_suffix("not") {
114 let after = skip_space(&self.rest()[name.len()..]);
115 if KNOWN.contains(&keyword) && after.starts_with('=') {
116 return keyword.to_owned();
117 }
118 }
119 name
120 }
121 fn natural(&mut self) -> Result<(u64, std::ops::Range<usize>)> {
122 let start = self.pos;
123 while self.rest().starts_with(|c: char| c.is_ascii_digit()) {
124 self.pos += 1;
125 }
126 let text = &self.text[start..self.pos];
127 if text.is_empty() || text.len() > 1 && text.starts_with('0') {
128 return Err(self.error(ParseErrorKind::Syntax, "Leading zero in cardinality"));
129 }
130 Ok((text.parse().unwrap_or(u64::MAX), start..self.pos))
132 }
133 fn cardinality(&mut self) -> Result<Cardinality> {
134 if !self.take("[") {
135 return Ok(Cardinality::default());
136 }
137 let (min, min_text) = self.natural()?;
138 if !self.take("..") {
139 self.required_ws()?;
140 if !self.keyword("to") {
141 return Err(self.unexpected());
142 }
143 self.required_ws()?;
144 }
145 let max = if self.take("*") || self.keyword("many") {
146 None
147 } else {
148 Some(self.natural()?)
149 };
150 let reversed = max.as_ref().is_some_and(|(_, max_text)| {
151 let min = &self.text[min_text.clone()];
152 let max = &self.text[max_text.clone()];
153 min.len().cmp(&max.len()).then_with(|| min.cmp(max)).is_gt()
154 });
155 if !self.take("]") {
156 return Err(self.error(ParseErrorKind::Syntax, "Invalid cardinality range"));
157 }
158 if reversed {
159 self.refuse(min_text.start, "Cardinality minimum exceeds its maximum");
161 }
162 self.ws()?;
163 Ok(Cardinality {
164 min,
165 max: max.map(|(value, _)| value),
166 })
167 }
168 pub(super) fn comparison(&mut self) -> Result<Comparison> {
169 self.ws()?;
170 for (text, op) in [
171 ("!=", Comparison::Ne),
172 ("<>", Comparison::Ne),
173 ("<=", Comparison::Le),
174 (">=", Comparison::Ge),
175 ("=", Comparison::Eq),
176 ("<", Comparison::Lt),
177 (">", Comparison::Gt),
178 ] {
179 if self.take(text) {
180 self.ws()?;
181 return Ok(op);
182 }
183 }
184 if self.keyword("not") {
185 self.ws()?;
186 if self.take("=") {
187 self.ws()?;
188 return Ok(Comparison::Ne);
189 }
190 }
191 Err(self.error(ParseErrorKind::Syntax, "Expected comparison operator"))
192 }
193 pub(super) fn quoted(&mut self) -> Result<String> {
194 if !self.take("\"") {
195 return Err(self.unexpected());
196 }
197 let mut value = String::new();
198 loop {
199 let Some(c) = self.rest().chars().next() else {
200 return Err(self.error(ParseErrorKind::Syntax, "Unclosed string"));
201 };
202 self.pos += c.len_utf8();
203 if c == '"' {
204 return Ok(value);
205 }
206 if c == '\\' {
207 let Some(escaped) = self.rest().chars().next() else {
208 return Err(self.unexpected());
209 };
210 if !matches!(escaped, '\\' | '"' | '*') {
212 return Err(self.error(ParseErrorKind::Syntax, "Invalid string escape"));
213 }
214 self.pos += escaped.len_utf8();
215 value.push(escaped);
216 } else if c.is_ascii_control() && !matches!(c, '\r' | '\n' | '\t') {
217 return Err(self.unexpected());
218 } else {
219 value.push(c);
220 }
221 }
222 }
223 fn attribute(
224 &mut self,
225 depth: usize,
226 cardinality: Cardinality,
227 grouped: bool,
228 ) -> Result<Refinement> {
229 let flag = self.pos;
230 let reverse = if self.starts_alternate() {
232 false
233 } else if self.text[self.pos..]
234 .get(..9)
235 .is_some_and(|prefix| prefix.eq_ignore_ascii_case("reverseof"))
236 {
237 self.pos += 9;
238 true
239 } else if self.word().eq_ignore_ascii_case("refsetcontainingany") {
240 false
241 } else {
242 self.take("R") || self.take("r")
243 };
244 if reverse && grouped {
245 self.refuse(
248 flag,
249 "Reverse flag inside an attribute group has no defined ECL semantics",
250 );
251 }
252 self.ws()?;
253 let name = Box::new(self.subexpression(depth + 1)?);
254 let comparison = self.comparison()?;
255 let value = if self.take("#") {
256 let start = self.pos;
257 self.take("-");
258 self.take("+");
259 let integer_start = self.pos;
260 while self.rest().starts_with(|c: char| c.is_ascii_digit()) {
261 self.pos += 1;
262 }
263 if self.pos - integer_start > 1 && self.text[integer_start..].starts_with('0') {
264 return Err(self.unexpected());
265 }
266 if self.take(".") {
267 while self.rest().starts_with(|c: char| c.is_ascii_digit()) {
268 self.pos += 1;
269 }
270 }
271 let number = Decimal::parse(&self.text[start..self.pos])
272 .ok_or_else(|| self.error(ParseErrorKind::Syntax, "Invalid decimal"))?;
273 AttributeValue::Number(number)
274 } else if self.rest().starts_with('"') && !self.starts_alternate() {
275 let value = self.quoted()?;
276 if value.is_empty() {
277 return Err(self.error(ParseErrorKind::Syntax, "Empty concrete string"));
278 }
279 AttributeValue::Strings(vec![value])
280 } else if !self.starts_alternate() && self.value_keyword("true") {
281 AttributeValue::Boolean(true)
282 } else if !self.starts_alternate() && self.value_keyword("false") {
283 AttributeValue::Boolean(false)
284 } else {
285 let saved = self.pos;
286 if self.take("(") {
287 self.ws()?;
288 }
289 if self.pos != saved && self.rest().starts_with('"') && !self.starts_alternate() {
290 let mut values = vec![self.quoted()?];
291 loop {
292 let spaced = self.ws()?;
293 if self.take(")") {
294 break;
295 }
296 if !spaced {
297 return Err(self.unexpected());
298 }
299 values.push(self.quoted()?);
300 }
301 if values.iter().any(String::is_empty) {
302 return Err(self.unexpected());
303 }
304 AttributeValue::Strings(values)
305 } else {
306 self.pos = saved;
307 AttributeValue::Concepts(Box::new(self.subexpression(depth + 1)?))
308 }
309 };
310 if !matches!(value, AttributeValue::Number(_))
311 && !matches!(comparison, Comparison::Eq | Comparison::Ne)
312 {
313 return Err(self.error(
314 ParseErrorKind::Syntax,
315 "Ordered comparison requires a numeric attribute value",
316 ));
317 }
318 if reverse && !matches!(value, AttributeValue::Concepts(_)) {
319 self.refuse(
322 flag,
323 "Reverse flag with a concrete value has no defined ECL semantics",
324 );
325 }
326 Ok(Refinement::Attribute(AttributeConstraint {
327 cardinality,
328 reverse,
329 name,
330 comparison,
331 value,
332 }))
333 }
334 fn operand(&mut self, depth: usize) -> Result<(Refinement, bool)> {
338 if depth > MAX_DEPTH {
339 return Err(self.error(ParseErrorKind::Limit, "Refinement nesting exceeds 64"));
340 }
341 self.nodes += 1;
342 if self.nodes > MAX_NODES {
343 return Err(self.error(ParseErrorKind::Limit, "Too many nodes"));
344 }
345 self.ws()?;
346 let start = self.mark();
347 let cardinality = self.cardinality()?;
348 self.ws()?;
349 if self.take("{") {
350 let inner = self.attribute_set(depth + 1, true)?;
351 self.ws()?;
352 if !self.take("}") {
353 return Err(self.unexpected());
354 }
355 return Ok((Refinement::Group(cardinality, Box::new(inner)), false));
356 }
357 self.reset(start.clone());
358 let attribute = self.subattribute(depth, false);
359 if attribute.is_ok() || !self.text[start.pos..].starts_with('(') {
360 return attribute.map(|a| (a, true));
361 }
362 self.reset(start);
363 self.take("(");
364 let inner = self.refinement(depth + 1)?;
365 self.ws()?;
366 if !self.take(")") {
367 return Err(self.unexpected());
368 }
369 Ok((inner, false))
370 }
371
372 fn attribute_set(&mut self, depth: usize, grouped: bool) -> Result<Refinement> {
375 let mut parts = vec![self.subattribute(depth, grouped)?];
376 let mut operator = None;
377 loop {
378 let before = self.mark();
379 let Some(next) = self.boolean()? else { break };
380 if next == Boolean::Minus || operator.is_some_and(|op| op != next) {
381 self.reset(before);
382 break;
383 }
384 operator = Some(next);
385 parts.push(self.subattribute(depth, grouped)?);
386 }
387 Ok(match operator {
388 None => parts.pop().expect("one part"),
389 Some(Boolean::And) => Refinement::And(parts),
390 Some(_) => Refinement::Or(parts),
391 })
392 }
393
394 fn subattribute(&mut self, depth: usize, grouped: bool) -> Result<Refinement> {
396 if depth > MAX_DEPTH {
397 return Err(self.error(ParseErrorKind::Limit, "Refinement nesting exceeds 64"));
398 }
399 self.nodes += 1;
400 if self.nodes > MAX_NODES {
401 return Err(self.error(ParseErrorKind::Limit, "Too many nodes"));
402 }
403 self.ws()?;
404 let start = self.mark();
405 let has_cardinality = self.rest().starts_with('[');
406 let cardinality = self.cardinality()?;
407 let attribute = self.attribute(depth, cardinality, grouped);
408 if attribute.is_ok() || has_cardinality || !self.text[start.pos..].starts_with('(') {
409 return attribute;
410 }
411 self.reset(start);
412 self.take("(");
413 let inner = self.attribute_set(depth + 1, grouped)?;
414 self.ws()?;
415 if !self.take(")") {
416 return Err(self.unexpected());
417 }
418 Ok(inner)
419 }
420
421 pub(super) fn refinement(&mut self, depth: usize) -> Result<Refinement> {
432 let start = self.pos;
433 let (first, attribute) = self.operand(depth)?;
434 let mut parts = vec![first];
435 let mut attributes = vec![attribute];
436 let mut operators = Vec::new();
437 while let Some(operator) = self.boolean()? {
438 if operator == Boolean::Minus {
439 return Err(self.error(
440 ParseErrorKind::Syntax,
441 "Exclusion is not a refinement operator",
442 ));
443 }
444 let (part, attribute) = self.operand(depth)?;
445 parts.push(part);
446 attributes.push(attribute);
447 operators.push(operator);
448 }
449 let Some(&operator) = operators.first() else {
450 return Ok(parts.pop().expect("one part"));
451 };
452 if operators.iter().any(|&op| op != operator) {
453 let mut beside_groups = operators
454 .iter()
455 .enumerate()
456 .filter(|&(i, _)| !attributes[i] || !attributes[i + 1])
457 .map(|(_, &op)| op);
458 let first = beside_groups.next();
459 if beside_groups.any(|op| Some(op) != first) {
460 return Err(self.error(
461 ParseErrorKind::Syntax,
462 "Mixed refinement operators require parentheses",
463 ));
464 }
465 self.refuse(
466 start,
467 "Conjunction and disjunction together require brackets (6.4)",
468 );
469 }
470 Ok(if operator == Boolean::And {
471 Refinement::And(parts)
472 } else {
473 Refinement::Or(parts)
474 })
475 }
476}
477
478fn skip_space(mut text: &str) -> &str {
480 loop {
481 text = text.trim_start_matches([' ', '\t', '\r', '\n']);
482 match text
483 .strip_prefix("/*")
484 .and_then(|c| c.find("*/").map(|end| &c[end + 2..]))
485 {
486 Some(rest) => text = rest,
487 None => return text,
488 }
489 }
490}