1#[derive(Debug, Clone, Default, PartialEq, Eq)]
19#[non_exhaustive]
20pub struct ParsedSchema {
21 pub name: String,
23 pub entities: Vec<EntityDef>,
25 pub types: Vec<TypeDef>,
27}
28
29impl ParsedSchema {
30 #[must_use]
32 pub fn new(name: impl Into<String>, entities: Vec<EntityDef>, types: Vec<TypeDef>) -> Self {
33 Self {
34 name: name.into(),
35 entities,
36 types,
37 }
38 }
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
43#[non_exhaustive]
44pub enum AggregateKind {
45 List,
47 Set,
49 Bag,
51 Array,
53}
54
55impl AggregateKind {
56 fn from_keyword(keyword: &str) -> Option<Self> {
57 match keyword {
58 "LIST" => Some(Self::List),
59 "SET" => Some(Self::Set),
60 "BAG" => Some(Self::Bag),
61 "ARRAY" => Some(Self::Array),
62 _ => None,
63 }
64 }
65}
66
67#[derive(Debug, Clone, PartialEq, Eq, Hash)]
69#[non_exhaustive]
70pub enum Bound {
71 Integer(u64),
73 Unbounded,
75 Expression(String),
79}
80
81impl Bound {
82 fn parse(text: &str) -> Self {
83 let text = text.trim();
84 if text == "?" {
85 Self::Unbounded
86 } else if let Ok(value) = text.parse() {
87 Self::Integer(value)
88 } else {
89 Self::Expression(text.split_whitespace().collect::<Vec<_>>().join(" "))
90 }
91 }
92}
93
94#[derive(Debug, Clone, PartialEq, Eq, Hash)]
96#[non_exhaustive]
97pub struct Aggregation {
98 pub kind: AggregateKind,
100 pub lower: Bound,
103 pub upper: Bound,
106 pub unique: bool,
108 pub optional_elements: bool,
111}
112
113impl Aggregation {
114 #[must_use]
116 pub const fn new(kind: AggregateKind, lower: Bound, upper: Bound) -> Self {
117 Self {
118 kind,
119 lower,
120 upper,
121 unique: false,
122 optional_elements: false,
123 }
124 }
125
126 #[must_use]
128 pub const fn unique(mut self) -> Self {
129 self.unique = true;
130 self
131 }
132
133 #[must_use]
135 pub const fn optional_elements(mut self) -> Self {
136 self.optional_elements = true;
137 self
138 }
139}
140
141#[derive(Debug, Clone, PartialEq, Eq)]
143#[non_exhaustive]
144pub struct Attribute {
145 pub name: String,
147 pub type_name: String,
149 pub optional: bool,
151 pub aggregate: bool,
154 pub aggregation: Vec<Aggregation>,
157}
158
159impl Attribute {
160 #[must_use]
162 pub fn new(name: impl Into<String>, type_name: impl Into<String>) -> Self {
163 Self {
164 name: name.into(),
165 type_name: type_name.into(),
166 optional: false,
167 aggregate: false,
168 aggregation: Vec::new(),
169 }
170 }
171
172 #[must_use]
174 pub const fn optional(mut self) -> Self {
175 self.optional = true;
176 self
177 }
178
179 #[must_use]
183 pub const fn aggregate(mut self) -> Self {
184 self.aggregate = true;
185 self
186 }
187
188 #[must_use]
191 pub fn with_aggregation(mut self, aggregation: Aggregation) -> Self {
192 self.aggregation.push(aggregation);
193 self.aggregate = true;
194 self
195 }
196}
197
198#[derive(Debug, Clone, PartialEq, Eq)]
200#[non_exhaustive]
201pub struct InverseAttribute {
202 pub name: String,
204 pub redeclares: Option<String>,
207 pub entity: String,
209 pub for_attribute: String,
212 pub aggregation: Option<Aggregation>,
214}
215
216impl InverseAttribute {
217 #[must_use]
219 pub fn new(
220 name: impl Into<String>,
221 entity: impl Into<String>,
222 for_attribute: impl Into<String>,
223 ) -> Self {
224 Self {
225 name: name.into(),
226 redeclares: None,
227 entity: entity.into(),
228 for_attribute: for_attribute.into(),
229 aggregation: None,
230 }
231 }
232
233 #[must_use]
235 pub fn with_aggregation(mut self, aggregation: Aggregation) -> Self {
236 self.aggregation = Some(aggregation);
237 self
238 }
239}
240
241#[derive(Debug, Clone, PartialEq, Eq)]
244#[non_exhaustive]
245pub struct UniqueRule {
246 pub label: Option<String>,
248 pub attributes: Vec<String>,
250}
251
252impl UniqueRule {
253 #[must_use]
255 pub fn new(label: impl Into<String>, attributes: Vec<String>) -> Self {
256 Self {
257 label: Some(label.into()),
258 attributes,
259 }
260 }
261}
262
263#[derive(Debug, Clone, PartialEq, Eq)]
273#[non_exhaustive]
274pub struct WhereRule {
275 pub label: String,
277 pub expression: String,
279}
280
281impl WhereRule {
282 #[must_use]
284 pub fn new(label: impl Into<String>, expression: impl Into<String>) -> Self {
285 Self {
286 label: label.into(),
287 expression: expression.into(),
288 }
289 }
290}
291
292#[derive(Debug, Clone, PartialEq, Eq)]
294#[non_exhaustive]
295pub struct Redeclaration {
296 pub supertype: String,
298 pub name: String,
300 pub type_name: String,
302}
303
304impl Redeclaration {
305 #[must_use]
307 pub fn new(
308 supertype: impl Into<String>,
309 name: impl Into<String>,
310 type_name: impl Into<String>,
311 ) -> Self {
312 Self {
313 supertype: supertype.into(),
314 name: name.into(),
315 type_name: type_name.into(),
316 }
317 }
318}
319
320#[derive(Debug, Clone, PartialEq, Eq)]
322#[non_exhaustive]
323pub struct EntityDef {
324 pub name: String,
326 pub supertypes: Vec<String>,
333 pub abstract_: bool,
335 pub attributes: Vec<Attribute>,
338 pub derived: Vec<String>,
361 pub redeclared: Vec<Redeclaration>,
369 pub where_rules: Vec<WhereRule>,
375 pub inverses: Vec<InverseAttribute>,
377 pub unique_rules: Vec<UniqueRule>,
379}
380
381impl EntityDef {
382 #[must_use]
384 pub fn new(name: impl Into<String>) -> Self {
385 Self {
386 name: name.into(),
387 supertypes: Vec::new(),
388 abstract_: false,
389 attributes: Vec::new(),
390 derived: Vec::new(),
391 redeclared: Vec::new(),
392 where_rules: Vec::new(),
393 inverses: Vec::new(),
394 unique_rules: Vec::new(),
395 }
396 }
397
398 #[must_use]
400 pub const fn abstract_(mut self) -> Self {
401 self.abstract_ = true;
402 self
403 }
404
405 #[must_use]
407 pub fn with_where_rule(mut self, rule: WhereRule) -> Self {
408 self.where_rules.push(rule);
409 self
410 }
411
412 #[must_use]
414 pub fn with_inverse(mut self, inverse: InverseAttribute) -> Self {
415 self.inverses.push(inverse);
416 self
417 }
418
419 #[must_use]
421 pub fn with_unique_rule(mut self, rule: UniqueRule) -> Self {
422 self.unique_rules.push(rule);
423 self
424 }
425
426 #[must_use]
430 pub fn with_supertype(mut self, supertype: impl Into<String>) -> Self {
431 self.supertypes.push(supertype.into());
432 self
433 }
434
435 #[must_use]
440 pub fn supertype(&self) -> Option<&str> {
441 self.supertypes.first().map(String::as_str)
442 }
443
444 #[must_use]
446 pub fn with_redeclared(mut self, redeclaration: Redeclaration) -> Self {
447 self.redeclared.push(redeclaration);
448 self
449 }
450
451 #[must_use]
453 pub fn with_attribute(mut self, attribute: Attribute) -> Self {
454 self.attributes.push(attribute);
455 self
456 }
457
458 #[must_use]
460 pub fn with_derived(mut self, name: impl Into<String>) -> Self {
461 self.derived.push(name.into());
462 self
463 }
464
465 #[must_use]
471 pub fn is_derived(&self, name: &str) -> bool {
472 self.derived
473 .iter()
474 .any(|declared| declared.eq_ignore_ascii_case(name))
475 }
476
477 #[must_use]
481 pub fn is_redeclared(&self, name: &str) -> bool {
482 self.redeclared
483 .iter()
484 .any(|declared| declared.name.eq_ignore_ascii_case(name))
485 }
486}
487
488#[derive(Debug, Clone, PartialEq, Eq)]
490#[non_exhaustive]
491pub enum TypeKind {
492 Defined(String),
494 Enumeration(Vec<String>),
496 Select(Vec<String>),
498}
499
500#[derive(Debug, Clone, PartialEq, Eq)]
502#[non_exhaustive]
503pub struct TypeDef {
504 pub name: String,
506 pub kind: TypeKind,
508}
509
510impl TypeDef {
511 #[must_use]
513 pub fn new(name: impl Into<String>, kind: TypeKind) -> Self {
514 Self {
515 name: name.into(),
516 kind,
517 }
518 }
519
520 #[must_use]
522 pub const fn is_defined(&self) -> bool {
523 matches!(self.kind, TypeKind::Defined(_))
524 }
525}
526
527#[must_use]
533pub fn parse(source: &str) -> ParsedSchema {
534 let cleaned = strip_comments(source);
535 let upper = ascii_uppercase(&cleaned);
536 let name = schema_name(&cleaned, &upper).unwrap_or_default();
537 let entities = blocks(&cleaned, &upper, "ENTITY", "END_ENTITY")
538 .filter_map(parse_entity)
539 .collect();
540 let types = blocks(&cleaned, &upper, "TYPE", "END_TYPE")
541 .filter_map(parse_type)
542 .collect();
543 ParsedSchema {
544 name,
545 entities,
546 types,
547 }
548}
549
550fn strip_comments(source: &str) -> String {
551 let bytes = source.as_bytes();
552 let mut output = bytes.to_vec();
553 let mut position = 0;
554 let mut quoted = false;
555 while position < bytes.len() {
556 if bytes[position] == b'\'' {
557 if quoted && bytes.get(position + 1) == Some(&b'\'') {
558 position += 2;
559 continue;
560 }
561 quoted = !quoted;
562 position += 1;
563 continue;
564 }
565 if !quoted && bytes[position..].starts_with(b"(*") {
566 let start = position;
567 position += 2;
568 while position < bytes.len() && !bytes[position..].starts_with(b"*)") {
569 position += 1;
570 }
571 position = (position + 2).min(bytes.len());
572 blank_non_newlines(&mut output[start..position]);
573 continue;
574 }
575 if !quoted && bytes[position..].starts_with(b"--") {
576 let start = position;
577 position += 2;
578 while position < bytes.len() && bytes[position] != b'\n' {
579 position += 1;
580 }
581 blank_non_newlines(&mut output[start..position]);
582 continue;
583 }
584 position += 1;
585 }
586 String::from_utf8(output).expect("input was valid UTF-8")
587}
588
589fn blank_non_newlines(bytes: &mut [u8]) {
590 for byte in bytes {
591 if *byte != b'\n' && *byte != b'\r' {
592 *byte = b' ';
593 }
594 }
595}
596
597fn ascii_uppercase(source: &str) -> String {
598 let mut bytes = source.as_bytes().to_vec();
599 bytes.make_ascii_uppercase();
600 String::from_utf8(bytes).expect("ASCII case conversion preserves UTF-8")
601}
602
603fn schema_name(source: &str, upper: &str) -> Option<String> {
604 let start = find_keyword(upper, "SCHEMA", 0)? + "SCHEMA".len();
605 let end = source[start..].find(';')? + start;
606 source[start..end]
607 .split_whitespace()
608 .next()
609 .map(ToOwned::to_owned)
610}
611
612fn blocks<'a>(
613 source: &'a str,
614 upper: &'a str,
615 start_keyword: &'static str,
616 end_keyword: &'static str,
617) -> impl Iterator<Item = &'a str> {
618 let mut cursor = 0;
619 std::iter::from_fn(move || {
620 let start = find_keyword(upper, start_keyword, cursor)?;
621 let end_start = find_keyword(upper, end_keyword, start + start_keyword.len())?;
622 let semicolon = source[end_start..]
623 .find(';')
624 .map_or(source.len(), |offset| end_start + offset + 1);
625 cursor = semicolon;
626 Some(&source[start..semicolon])
627 })
628}
629
630fn find_keyword(haystack: &str, needle: &str, from: usize) -> Option<usize> {
631 let bytes = haystack.as_bytes();
632 let mut cursor = from;
633 while let Some(relative) = haystack[cursor..].find(needle) {
634 let position = cursor + relative;
635 let before = position.checked_sub(1).and_then(|index| bytes.get(index));
636 let after = bytes.get(position + needle.len());
637 if before.is_none_or(|byte| !is_identifier_byte(*byte))
638 && after.is_none_or(|byte| !is_identifier_byte(*byte))
639 {
640 return Some(position);
641 }
642 cursor = position + needle.len();
643 }
644 None
645}
646
647fn find_block_keyword(source: &str, upper: &str, needle: &str, from: usize) -> Option<usize> {
660 let mut cursor = from;
661 while let Some(position) = find_keyword(upper, needle, cursor) {
662 let preceding = source[from..position]
663 .rfind(';')
664 .map_or(&source[from..position], |offset| {
665 &source[from + offset + 1..position]
666 });
667 if preceding.trim().is_empty() {
668 return Some(position);
669 }
670 cursor = position + needle.len();
671 }
672 None
673}
674
675fn is_identifier_byte(byte: u8) -> bool {
676 byte.is_ascii_alphanumeric() || byte == b'_'
677}
678
679fn parse_entity(block: &str) -> Option<EntityDef> {
680 let upper = ascii_uppercase(block);
681 let header_end = block.find(';')?;
682 let header = &block[..header_end];
683 let header_upper = &upper[..header_end];
684 let entity_position = find_keyword(header_upper, "ENTITY", 0)? + "ENTITY".len();
685 let name = header[entity_position..]
686 .split_whitespace()
687 .next()?
688 .trim_matches(|character: char| !character.is_alphanumeric() && character != '_')
689 .to_owned();
690 let supertypes = clause_names(header, header_upper, "SUBTYPE OF");
691 let abstract_ = find_keyword(header_upper, "ABSTRACT", 0).is_some();
692
693 let body_end = ["DERIVE", "INVERSE", "UNIQUE", "WHERE", "END_ENTITY"]
694 .into_iter()
695 .filter_map(|keyword| find_block_keyword(block, &upper, keyword, header_end + 1))
696 .min()
697 .unwrap_or(block.len());
698 let mut attributes = Vec::new();
699 let mut redeclared = Vec::new();
700 for statement in block[header_end + 1..body_end].split(';') {
701 if let Some(redeclaration) = parse_redeclaration(statement) {
702 redeclared.push(redeclaration);
703 } else if let Some(attribute) = parse_attribute(statement) {
704 attributes.push(attribute);
705 }
706 }
707 let derived = parse_derive_block(block, &upper, header_end + 1);
708 let where_rules = parse_where_block(block, &upper, header_end + 1);
709 let inverses = block_statements(block, &upper, "INVERSE", header_end + 1)
710 .filter_map(parse_inverse)
711 .collect();
712 let unique_rules = block_statements(block, &upper, "UNIQUE", header_end + 1)
713 .filter_map(parse_unique_rule)
714 .collect();
715
716 Some(EntityDef {
717 name,
718 supertypes,
719 abstract_,
720 attributes,
721 derived,
722 redeclared,
723 where_rules,
724 inverses,
725 unique_rules,
726 })
727}
728
729fn block_statements<'a>(
733 block: &'a str,
734 upper: &str,
735 keyword: &'static str,
736 from: usize,
737) -> impl Iterator<Item = &'a str> {
738 let range = find_block_keyword(block, upper, keyword, from).and_then(|start| {
739 let start = start + keyword.len();
740 let end = ["INVERSE", "UNIQUE", "WHERE", "END_ENTITY"]
741 .into_iter()
742 .filter(|next| *next != keyword)
743 .filter_map(|next| find_block_keyword(block, upper, next, start))
744 .min()
745 .unwrap_or(block.len());
746 (start < end).then_some(start..end)
747 });
748 range
749 .map_or("", |range| &block[range])
750 .split(';')
751 .filter(|statement| !statement.trim().is_empty())
752}
753
754fn parse_inverse(statement: &str) -> Option<InverseAttribute> {
756 let (target, declaration) = statement.split_once(':')?;
757 let target = target.trim();
758 let (redeclares, name) = match target
759 .get(..5)
760 .filter(|prefix| prefix.eq_ignore_ascii_case("SELF\\"))
761 {
762 Some(_) => {
763 let (supertype, name) = target[5..].split_once('.')?;
764 (Some(supertype.trim().to_owned()), name.trim())
765 }
766 None => (None, target),
767 };
768 if name.is_empty() || !name.bytes().all(is_identifier_byte) {
769 return None;
770 }
771 let upper = ascii_uppercase(declaration);
772 let for_position = find_keyword(&upper, "FOR", 0)?;
773 let for_attribute = declaration[for_position + "FOR".len()..].trim();
774 let (aggregation, element) = aggregation_levels(&declaration[..for_position]);
775 let entity = element.split_whitespace().next()?;
776 if for_attribute.is_empty() || aggregation.len() > 1 {
777 return None;
778 }
779 Some(InverseAttribute {
780 name: name.to_owned(),
781 redeclares,
782 entity: entity.to_owned(),
783 for_attribute: for_attribute.split_whitespace().collect(),
784 aggregation: aggregation.into_iter().next(),
785 })
786}
787
788fn parse_unique_rule(statement: &str) -> Option<UniqueRule> {
790 let (label, attributes) = match statement.split_once(':') {
793 Some((label, attributes)) => {
794 let label = label.trim();
795 if label.is_empty() || !label.bytes().all(is_identifier_byte) {
796 return None;
797 }
798 (Some(label.to_owned()), attributes)
799 }
800 None => (None, statement),
801 };
802 let attributes: Vec<String> = attributes
803 .split(',')
804 .map(|attribute| attribute.split_whitespace().collect::<String>())
805 .filter(|attribute| !attribute.is_empty())
806 .collect();
807 (!attributes.is_empty()).then_some(UniqueRule { label, attributes })
808}
809
810fn aggregation_levels(declaration: &str) -> (Vec<Aggregation>, &str) {
817 let mut levels = Vec::new();
818 let mut rest = declaration.trim_start();
819 loop {
820 let word_end = rest
821 .find(|character: char| !(character.is_ascii_alphanumeric() || character == '_'))
822 .unwrap_or(rest.len());
823 let Some(kind) = AggregateKind::from_keyword(&rest[..word_end].to_ascii_uppercase()) else {
824 return (levels, rest);
825 };
826 let mut after = rest[word_end..].trim_start();
827 let (lower, upper) = if let Some(inner) = after.strip_prefix('[') {
828 let Some(close) = inner.find(']') else {
829 return (levels, rest);
830 };
831 let bounds = &inner[..close];
832 after = inner[close + 1..].trim_start();
833 match bounds.split_once(':') {
834 Some((lower, upper)) => (Bound::parse(lower), Bound::parse(upper)),
835 None => (Bound::parse(bounds), Bound::parse(bounds)),
836 }
837 } else {
838 (Bound::Integer(0), Bound::Unbounded)
839 };
840 let Some(of) = after
841 .get(..2)
842 .filter(|word| word.eq_ignore_ascii_case("OF"))
843 .map(|_| after[2..].trim_start())
844 else {
845 return (levels, rest);
846 };
847 let mut level = Aggregation::new(kind, lower, upper);
848 rest = of;
849 for (keyword, mark) in [
850 (
851 "OPTIONAL",
852 Aggregation::optional_elements as fn(Aggregation) -> Aggregation,
853 ),
854 ("UNIQUE", Aggregation::unique),
855 ] {
856 if rest
857 .get(..keyword.len())
858 .is_some_and(|word| word.eq_ignore_ascii_case(keyword))
859 && !rest[keyword.len()..]
860 .starts_with(|c: char| c.is_ascii_alphanumeric() || c == '_')
861 {
862 level = mark(level);
863 rest = rest[keyword.len()..].trim_start();
864 }
865 }
866 levels.push(level);
867 }
868}
869
870fn parse_where_rule(statement: &str) -> Option<WhereRule> {
887 let (label, expression) = statement.split_once(':')?;
888 let label = label.trim();
889 if label.is_empty() || !label.bytes().all(is_identifier_byte) {
890 return None;
891 }
892 let expression = expression.split_whitespace().collect::<Vec<_>>().join(" ");
893 if expression.is_empty() {
894 return None;
895 }
896 Some(WhereRule {
897 label: label.to_owned(),
898 expression,
899 })
900}
901
902fn parse_where_block(block: &str, upper: &str, from: usize) -> Vec<WhereRule> {
903 let Some(start) = find_block_keyword(block, upper, "WHERE", from) else {
904 return Vec::new();
905 };
906 let start = start + "WHERE".len();
907 let end = find_keyword(upper, "END_ENTITY", start).unwrap_or(block.len());
908 if end <= start {
909 return Vec::new();
910 }
911 block[start..end]
912 .split(';')
913 .filter_map(parse_where_rule)
914 .collect()
915}
916
917fn parse_derive_block(block: &str, upper: &str, from: usize) -> Vec<String> {
927 let Some(start) = find_keyword(upper, "DERIVE", from) else {
928 return Vec::new();
929 };
930 let start = start + "DERIVE".len();
931 let end = ["INVERSE", "UNIQUE", "WHERE", "END_ENTITY"]
932 .into_iter()
933 .filter_map(|keyword| find_keyword(upper, keyword, start))
934 .min()
935 .unwrap_or(block.len());
936 if end <= start {
937 return Vec::new();
938 }
939
940 block[start..end]
941 .split(';')
942 .filter_map(derived_attribute_name)
943 .collect()
944}
945
946fn derived_attribute_name(statement: &str) -> Option<String> {
952 let (target, _) = statement.split_once(':')?;
953 let target = target.trim();
954 let name = target.rsplit('.').next()?.trim();
957 let name = name.rsplit('\\').next()?.trim();
958 if name.is_empty() || !name.bytes().all(is_identifier_byte) {
959 return None;
960 }
961 Some(name.to_owned())
962}
963
964fn clause_names(header: &str, upper: &str, clause: &str) -> Vec<String> {
966 let Some(position) = find_keyword(upper, clause, 0).map(|p| p + clause.len()) else {
967 return Vec::new();
968 };
969 let Some(open) = header[position..].find('(').map(|o| position + o + 1) else {
970 return Vec::new();
971 };
972 let Some(close) = header[open..].find(')').map(|o| open + o) else {
973 return Vec::new();
974 };
975 header[open..close]
976 .split(',')
977 .map(str::trim)
978 .filter(|name| !name.is_empty())
979 .map(ToOwned::to_owned)
980 .collect()
981}
982
983fn parse_redeclaration(statement: &str) -> Option<Redeclaration> {
986 let (target, _) = statement.split_once(':')?;
987 let target = target.trim();
988 let qualified = target
989 .get(..5)
990 .filter(|prefix| prefix.eq_ignore_ascii_case("SELF\\"))
991 .map(|_| &target[5..])?;
992 let (supertype, name) = qualified.split_once('.')?;
993 let (supertype, name) = (supertype.trim(), name.trim());
994 if supertype.is_empty()
995 || name.is_empty()
996 || !supertype.bytes().all(is_identifier_byte)
997 || !name.bytes().all(is_identifier_byte)
998 {
999 return None;
1000 }
1001 let type_name =
1003 parse_attribute(&format!("{name}{}", &statement[statement.find(':')?..]))?.type_name;
1004 Some(Redeclaration {
1005 supertype: supertype.to_owned(),
1006 name: name.to_owned(),
1007 type_name,
1008 })
1009}
1010
1011fn parse_attribute(statement: &str) -> Option<Attribute> {
1012 let (name, declaration) = statement.split_once(':')?;
1013 let name = name.trim();
1014 if name.is_empty() {
1015 return None;
1016 }
1017 let mut declaration = declaration.trim();
1018 let optional = declaration
1019 .get(.."OPTIONAL".len())
1020 .is_some_and(|word| word.eq_ignore_ascii_case("OPTIONAL"))
1021 && !declaration["OPTIONAL".len()..]
1022 .starts_with(|c: char| c.is_ascii_alphanumeric() || c == '_');
1023 if optional {
1024 declaration = declaration["OPTIONAL".len()..].trim_start();
1025 }
1026 let (aggregation, element) = aggregation_levels(declaration);
1027 let type_name = element
1028 .split_whitespace()
1029 .next()?
1030 .trim_matches(|character: char| matches!(character, '(' | ')' | ';'))
1031 .to_owned();
1032 Some(Attribute {
1033 name: name.to_owned(),
1034 type_name,
1035 optional,
1036 aggregate: !aggregation.is_empty(),
1037 aggregation,
1038 })
1039}
1040
1041fn parse_type(block: &str) -> Option<TypeDef> {
1042 let upper = ascii_uppercase(block);
1043 let statement_end = block.find(';')?;
1044 let statement = &block[..statement_end];
1045 let statement_upper = &upper[..statement_end];
1046 let type_position = find_keyword(statement_upper, "TYPE", 0)? + "TYPE".len();
1047 let equals = statement[type_position..].find('=')? + type_position;
1048 let name = statement[type_position..equals].trim().to_owned();
1049 let right = statement[equals + 1..].trim();
1050 let right_upper = ascii_uppercase(right);
1051 let kind = if let Some(position) = find_keyword(&right_upper, "ENUMERATION", 0) {
1052 TypeKind::Enumeration(parenthesized_names(right, position + "ENUMERATION".len()))
1053 } else if let Some(position) = find_keyword(&right_upper, "SELECT", 0) {
1054 TypeKind::Select(parenthesized_names(right, position + "SELECT".len()))
1055 } else {
1056 TypeKind::Defined(right.to_owned())
1057 };
1058 Some(TypeDef { name, kind })
1059}
1060
1061fn parenthesized_names(source: &str, from: usize) -> Vec<String> {
1062 let Some(open) = source[from..].find('(').map(|offset| from + offset + 1) else {
1063 return Vec::new();
1064 };
1065 let close = source[open..]
1066 .find(')')
1067 .map_or(source.len(), |offset| open + offset);
1068 source[open..close]
1069 .split(',')
1070 .map(str::trim)
1071 .filter(|name| !name.is_empty())
1072 .map(ToOwned::to_owned)
1073 .collect()
1074}