1use fastobo_derive_internal::FromStr;
2use fastobo_derive_internal::OboClause;
3
4use crate::ast::*;
5use crate::error::SyntaxError;
6use crate::parser::Cache;
7use crate::parser::FromPair;
8use crate::semantics::OboClause;
9use crate::syntax::pest::iterators::Pair;
10use crate::syntax::Rule;
11
12#[derive(Clone, Debug, Eq, Hash, FromStr, Ord, OboClause, PartialEq, PartialOrd)]
20pub enum TermClause {
21 #[clause(cardinality = "ZeroOrOne")]
22 IsAnonymous(bool),
23 #[clause(cardinality = "ZeroOrOne")]
24 Name(Box<UnquotedString>),
25 #[clause(cardinality = "One")]
26 Namespace(Box<NamespaceIdent>),
27 AltId(Box<Ident>),
28 #[clause(cardinality = "ZeroOrOne")]
29 Def(Box<Definition>),
30 #[clause(cardinality = "ZeroOrOne")]
31 Comment(Box<UnquotedString>),
32 Subset(Box<SubsetIdent>),
33 Synonym(Box<Synonym>),
34 Xref(Box<Xref>),
35 #[clause(cardinality = "ZeroOrOne")]
36 Builtin(bool),
37 PropertyValue(Box<PropertyValue>),
38 IsA(Box<ClassIdent>),
39 #[clause(cardinality = "NotOne")]
40 IntersectionOf(Option<Box<RelationIdent>>, Box<ClassIdent>),
41 #[clause(cardinality = "NotOne")]
42 UnionOf(Box<ClassIdent>),
43 EquivalentTo(Box<ClassIdent>),
44 DisjointFrom(Box<ClassIdent>),
45 Relationship(Box<RelationIdent>, Box<ClassIdent>),
46 #[clause(cardinality = "ZeroOrOne")]
47 CreatedBy(Box<UnquotedString>),
48 #[clause(cardinality = "ZeroOrOne")]
49 CreationDate(Box<CreationDate>),
50 #[clause(cardinality = "ZeroOrOne")]
51 IsObsolete(bool),
52 ReplacedBy(Box<ClassIdent>),
53 Consider(Box<ClassIdent>),
54}
55
56clause_impl_from!(TermClause);
57
58impl<'i> FromPair<'i> for Line<TermClause> {
59 const RULE: Rule = Rule::TermClauseLine;
60 unsafe fn from_pair_unchecked(
61 pair: Pair<'i, Rule>,
62 cache: &Cache,
63 ) -> Result<Self, SyntaxError> {
64 let mut inner = pair.into_inner();
65 let clause = TermClause::from_pair_unchecked(inner.next().unwrap(), cache)?;
66 let eol = inner.next().unwrap();
67 Ok(Eol::from_pair_unchecked(eol, cache)?.and_inner(clause))
68 }
69}
70
71impl<'i> FromPair<'i> for TermClause {
72 const RULE: Rule = Rule::TermClause;
73 unsafe fn from_pair_unchecked(pair: Pair<Rule>, cache: &Cache) -> Result<Self, SyntaxError> {
74 let mut inner = pair.into_inner();
75 match inner.next().unwrap().as_rule() {
76 Rule::IsAnonymousTag => {
77 let b = bool::from_pair_unchecked(inner.next().unwrap(), cache)?;
78 Ok(TermClause::IsAnonymous(b))
79 }
80 Rule::NameTag => {
81 let name = UnquotedString::from_pair_unchecked(inner.next().unwrap(), cache)?;
82 Ok(TermClause::Name(Box::new(name)))
83 }
84 Rule::NamespaceTag => {
85 let ns = NamespaceIdent::from_pair_unchecked(inner.next().unwrap(), cache)?;
86 Ok(TermClause::Namespace(Box::new(ns)))
87 }
88 Rule::AltIdTag => {
89 let id = Ident::from_pair_unchecked(inner.next().unwrap(), cache)?;
90 Ok(TermClause::AltId(Box::new(id)))
91 }
92 Rule::DefTag => {
93 let def = Definition::from_pair_unchecked(inner.next().unwrap(), cache)?;
94 Ok(TermClause::Def(Box::new(def)))
95 }
96 Rule::CommentTag => {
97 let comment = UnquotedString::from_pair_unchecked(inner.next().unwrap(), cache)?;
98 Ok(TermClause::Comment(Box::new(comment)))
99 }
100 Rule::SubsetTag => {
101 let id = SubsetIdent::from_pair_unchecked(inner.next().unwrap(), cache)?;
102 Ok(TermClause::Subset(Box::new(id)))
103 }
104 Rule::SynonymTag => {
105 let syn = Synonym::from_pair_unchecked(inner.next().unwrap(), cache)?;
106 Ok(TermClause::Synonym(Box::new(syn)))
107 }
108 Rule::XrefTag => {
109 let xref = Xref::from_pair_unchecked(inner.next().unwrap(), cache)?;
110 Ok(TermClause::Xref(Box::new(xref)))
111 }
112 Rule::BuiltinTag => {
113 let b = bool::from_pair_unchecked(inner.next().unwrap(), cache)?;
114 Ok(TermClause::Builtin(b))
115 }
116 Rule::PropertyValueTag => {
117 let pv = PropertyValue::from_pair_unchecked(inner.next().unwrap(), cache)?;
118 Ok(TermClause::PropertyValue(Box::new(pv)))
119 }
120 Rule::IsATag => {
121 let id = ClassIdent::from_pair_unchecked(inner.next().unwrap(), cache)?;
122 Ok(TermClause::IsA(Box::new(id)))
123 }
124 Rule::IntersectionOfTag => {
125 let id = inner.next().unwrap();
126 if id.as_rule() == Rule::ClassId {
127 let classid = ClassIdent::from_pair_unchecked(id, cache)?;
128 Ok(TermClause::IntersectionOf(None, Box::new(classid)))
129 } else {
130 let relid = RelationIdent::from_pair_unchecked(id, cache)?;
131 let classid = ClassIdent::from_pair_unchecked(inner.next().unwrap(), cache)?;
132 Ok(TermClause::IntersectionOf(
133 Some(Box::new(relid)),
134 Box::new(classid),
135 ))
136 }
137 }
138 Rule::UnionOfTag => {
139 let id = ClassIdent::from_pair_unchecked(inner.next().unwrap(), cache)?;
140 Ok(TermClause::UnionOf(Box::new(id)))
141 }
142 Rule::EquivalentToTag => {
143 let id = ClassIdent::from_pair_unchecked(inner.next().unwrap(), cache)?;
144 Ok(TermClause::EquivalentTo(Box::new(id)))
145 }
146 Rule::DisjointFromTag => {
147 let id = ClassIdent::from_pair_unchecked(inner.next().unwrap(), cache)?;
148 Ok(TermClause::DisjointFrom(Box::new(id)))
149 }
150 Rule::RelationshipTag => {
151 let rel = RelationIdent::from_pair_unchecked(inner.next().unwrap(), cache)?;
152 let id = ClassIdent::from_pair_unchecked(inner.next().unwrap(), cache)?;
153 Ok(TermClause::Relationship(Box::new(rel), Box::new(id)))
154 }
155 Rule::IsObsoleteTag => {
156 let b = bool::from_pair_unchecked(inner.next().unwrap(), cache)?;
157 Ok(TermClause::IsObsolete(b))
158 }
159 Rule::ReplacedByTag => {
160 let id = ClassIdent::from_pair_unchecked(inner.next().unwrap(), cache)?;
161 Ok(TermClause::ReplacedBy(Box::new(id)))
162 }
163 Rule::ConsiderTag => {
164 let id = ClassIdent::from_pair_unchecked(inner.next().unwrap(), cache)?;
165 Ok(TermClause::Consider(Box::new(id)))
166 }
167 Rule::CreatedByTag => {
168 let s = UnquotedString::from_pair_unchecked(inner.next().unwrap(), cache)?;
169 Ok(TermClause::CreatedBy(Box::new(s)))
170 }
171 Rule::CreationDateTag => {
172 let dt = CreationDate::from_pair_unchecked(inner.next().unwrap(), cache)?;
173 Ok(TermClause::CreationDate(Box::new(dt)))
174 }
175 _ => unreachable!(),
176 }
177 }
178}
179
180#[cfg(test)]
181mod tests {
182
183 use super::*;
184 use pretty_assertions::assert_eq;
185 use std::str::FromStr;
186
187 mod is_anonymous {
188 use super::*;
189
190 #[test]
191 fn from_str() {
192 let actual = TermClause::from_str("is_anonymous: true").unwrap();
193 let expected = TermClause::IsAnonymous(true);
194 self::assert_eq!(actual, expected);
195 }
196
197 #[test]
198 fn to_string() {
199 let clause = TermClause::Name(Box::new(UnquotedString::new("sample name")));
200 self::assert_eq!(clause.to_string(), "name: sample name")
201 }
202 }
203
204 mod name {
205 use super::*;
206
207 #[test]
208 fn from_str() {
209 let actual = TermClause::from_str("name: sample name").unwrap();
210 let expected = TermClause::Name(Box::new(UnquotedString::new("sample name")));
211 self::assert_eq!(actual, expected);
212 }
213
214 #[test]
215 fn to_string() {
216 let clause = TermClause::Name(Box::new(UnquotedString::new("sample name")));
217 self::assert_eq!(clause.to_string(), "name: sample name")
218 }
219 }
220
221 mod namespace {}
222
223 mod alt_id {}
224
225 mod def {
226 use super::*;
227
228 #[test]
229 fn from_str() {
230 let actual = TermClause::from_str(
231 "def: \"A reference string relevant to the sample under study.\" [PSI:MS]",
232 )
233 .unwrap();
234 let expected = TermClause::Def(Box::new(Definition::with_xrefs(
235 QuotedString::new(String::from(
236 "A reference string relevant to the sample under study.",
237 )),
238 XrefList::from(vec![Xref::new(PrefixedIdent::new("PSI", "MS"))]),
239 )));
240 self::assert_eq!(actual, expected);
241
242 let actual = TermClause::from_str("def: \"OBSOLETE: There is Phenyx:ScoringModel for Phenyx! Scoring model (more detailed granularity). TODO: add some child terms.\" [PSI:PI]").unwrap();
243 let expected = TermClause::Def(Box::new(Definition::with_xrefs(
244 QuotedString::new("OBSOLETE: There is Phenyx:ScoringModel for Phenyx! Scoring model (more detailed granularity). TODO: add some child terms."),
245 XrefList::from(vec![Xref::new(PrefixedIdent::new("PSI", "PI"))])
246 )));
247 self::assert_eq!(actual, expected);
248 }
249 }
250
251 mod comment {}
252
253 mod subset {}
254
255 mod synonym {
256 use super::*;
257
258 #[test]
259 fn from_str() {
260 let actual =
261 TermClause::from_str("synonym: \"chemical entity\" EXACT [UniProt]").unwrap();
262 let expected = TermClause::Synonym(Box::new(Synonym::with_xrefs(
263 QuotedString::new("chemical entity"),
264 SynonymScope::Exact,
265 XrefList::from(vec![Xref::new(UnprefixedIdent::new("UniProt"))]),
266 )));
267 self::assert_eq!(actual, expected);
268 }
269 }
270
271 mod xref {
272 use super::*;
273
274 #[test]
275 fn from_str() {
276 let actual =
277 TermClause::from_str("xref: CAS:22325-47-9 \"NIST Chemistry WebBook\"").unwrap();
278 let expected = TermClause::Xref(Box::new(Xref::with_desc(
279 Ident::from(PrefixedIdent::new("CAS", "22325-47-9")),
280 QuotedString::new("NIST Chemistry WebBook"),
281 )));
282 self::assert_eq!(actual, expected);
283
284 let actual =
285 TermClause::from_str("xref: Wikipedia:https\\://en.wikipedia.org/wiki/Gas")
286 .unwrap();
287 let expected = TermClause::Xref(Box::new(Xref::new(PrefixedIdent::new(
288 "Wikipedia",
289 "https://en.wikipedia.org/wiki/Gas",
290 ))));
291 self::assert_eq!(actual, expected);
292 }
293 }
294
295 mod builtin {}
296
297 mod property_value {}
298
299 mod is_a {}
300
301 mod intersection_of {
302 use super::*;
303 use textwrap_macros::dedent;
304
305 #[test]
306 fn from_str() {
307 let actual = TermFrame::from_str(dedent!(
308 "[Term]
309 id: TST:001
310 intersection_of: part_of PO:0020039 ! leaf lamina
311 "
312 ))
313 .unwrap()
314 .into_iter()
315 .next()
316 .unwrap();
317
318 let expected = Line::from(TermClause::IntersectionOf(
319 Some(Box::new(RelationIdent::from(UnprefixedIdent::new(
320 "part_of",
321 )))),
322 Box::new(ClassIdent::from(PrefixedIdent::new("PO", "0020039"))),
323 ))
324 .and_comment(Comment::new("leaf lamina"));
325 self::assert_eq!(actual, expected);
326
327 let actual = TermFrame::from_str(dedent!(
328 "[Term]
329 id: TST:001
330 intersection_of: PO:0006016 ! leaf epidermis
331 "
332 ))
333 .unwrap()
334 .into_iter()
335 .next()
336 .unwrap();
337 let expected = Line::with_comment(Comment::new("leaf epidermis")).and_inner(
338 TermClause::IntersectionOf(
339 None,
340 Box::new(ClassIdent::from(PrefixedIdent::new("PO", "0006016"))),
341 ),
342 );
343 self::assert_eq!(actual, expected);
344 }
345 }
346
347 mod union_of {}
348
349 mod equivalent_to {}
350
351 mod disjoint_from {}
352
353 mod relationship {}
354
355 mod is_obsolete {}
356
357 mod replaced_by {}
358
359 mod consider {}
360
361 mod created_by {}
362
363 mod creation_date {}
364}