1use codespan_reporting::diagnostic::Diagnostic;
18use codespan_reporting::diagnostic::Label;
19use nom::character::complete::line_ending;
20use nom::character::complete::not_line_ending;
21use nom_supreme::error::BaseErrorKind;
22use nom_supreme::error::ErrorTree;
23
24use std::collections::HashSet;
25use std::fmt;
26use std::str;
27
28use crate::logic;
29use crate::logic::parser::Span;
30use crate::logic::Predicate;
31use crate::logic::SpannedPosition;
32use crate::sld;
33
34use self::parser::process_raw_string;
35
36#[derive(Clone, PartialEq, Debug)]
40pub enum Expression {
41 Literal(Literal),
42
43 OperatorApplication(Option<SpannedPosition>, Box<Expression>, Operator),
45
46 And(
48 Option<SpannedPosition>,
49 bool,
50 Box<Expression>,
51 Box<Expression>,
52 ),
53
54 Or(
56 Option<SpannedPosition>,
57 bool,
58 Box<Expression>,
59 Box<Expression>,
60 ),
61}
62
63impl Expression {
64 #[cfg(test)]
65 fn eq_ignoring_position(&self, other: &Expression) -> bool {
66 match (self, other) {
67 (Expression::Literal(l), Expression::Literal(r)) => l.eq_ignoring_position(&r),
68 (
69 Expression::OperatorApplication(_, e1, op1),
70 Expression::OperatorApplication(_, e2, op2),
71 ) => e1.eq_ignoring_position(e2) && op1.eq_ignoring_position(op2),
72 (Expression::And(_, p1, l1, r1), Expression::And(_, p2, l2, r2))
73 | (Expression::Or(_, p1, l1, r1), Expression::Or(_, p2, l2, r2)) => {
74 p1 == p2 && l1.eq_ignoring_position(l2) && r1.eq_ignoring_position(r2)
75 }
76 (s, o) => s.eq(o),
77 }
78 }
79
80 pub fn get_spanned_position(&self) -> &Option<SpannedPosition> {
81 match self {
82 Expression::Literal(lit) => &lit.position,
83 Expression::OperatorApplication(s, ..) => &s,
84 Expression::And(s, ..) => &s,
85 Expression::Or(s, ..) => &s,
86 }
87 }
88
89 pub fn without_position(&self) -> Self {
90 match self {
91 Expression::Literal(lit) => Expression::Literal(Literal {
92 position: None,
93 ..lit.clone()
94 }),
95 Expression::OperatorApplication(_, e, op) => Expression::OperatorApplication(
96 None,
97 Box::new(e.without_position()),
98 op.clone().with_position(None),
99 ),
100 Expression::And(_, positive, e1, e2) => Expression::And(
101 None,
102 positive.clone(),
103 Box::new(e1.without_position()),
104 Box::new(e2.without_position()),
105 ),
106 Expression::Or(_, positive, e1, e2) => Expression::Or(
107 None,
108 positive.clone(),
109 Box::new(e1.without_position()),
110 Box::new(e2.without_position()),
111 ),
112 }
113 }
114
115 pub fn literals(&self) -> HashSet<Literal> {
116 match self {
117 Expression::Literal(lit) => vec![lit.clone()].into_iter().collect(),
118 Expression::OperatorApplication(_, e, _) => e.literals(),
119 Expression::And(_, _, e1, e2) | Expression::Or(_, _, e1, e2) => e1
120 .literals()
121 .into_iter()
122 .chain(e2.literals().into_iter())
123 .collect(),
124 }
125 }
126
127 pub fn negate_current(&self) -> Expression {
130 match &self {
131 Expression::Literal(lit) => Expression::Literal(Literal {
132 positive: !lit.positive,
133 ..lit.clone()
134 }),
135 Expression::And(s, curr_p, l, r) => {
136 Expression::And(s.clone(), !curr_p, l.clone(), r.clone())
137 }
138 Expression::Or(s, curr_p, l, r) => {
139 Expression::Or(s.clone(), !curr_p, l.clone(), r.clone())
140 }
141 Expression::OperatorApplication(..) => {
142 panic!("Attempted to negate at operator application level.")
143 }
144 }
145 }
146}
147
148#[derive(Clone, PartialEq, Debug)]
149pub struct ModusClause {
150 pub head: Literal,
151 pub body: Option<Expression>,
153}
154
155#[cfg(test)]
156impl ModusClause {
157 fn eq_ignoring_position(&self, other: &ModusClause) -> bool {
158 if let (Some(expr1), Some(expr2)) = (&self.body, &other.body) {
159 self.head.eq_ignoring_position(&other.head) && expr1.eq_ignoring_position(&expr2)
160 } else {
161 self.head.eq_ignoring_position(&other.head) && self.body.eq(&other.body)
162 }
163 }
164}
165
166#[derive(Clone, PartialEq, Eq, Hash, Debug)]
167pub enum FormatStringFragment {
168 StringContent(SpannedPosition, String),
170 InterpolatedVariable(SpannedPosition, String),
172}
173
174impl fmt::Display for FormatStringFragment {
175 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
176 match self {
177 FormatStringFragment::StringContent(_, s) => write!(f, "{s}"),
178 FormatStringFragment::InterpolatedVariable(_, v) => write!(f, "${{{v}}}"),
179 }
180 }
181}
182
183#[derive(Clone, PartialEq, Eq, Hash, Debug)]
184pub enum ModusTerm {
185 Constant(String),
186 FormatString {
189 position: SpannedPosition,
191 fragments: Vec<FormatStringFragment>,
192 },
193 UserVariable(String),
194 AnonymousVariable,
195 Array(SpannedPosition, Vec<ModusTerm>),
196}
197
198impl ModusTerm {
199 pub fn is_variable(&self) -> bool {
200 match self {
201 ModusTerm::FormatString { .. } | ModusTerm::UserVariable(_) => true,
202 _ => false,
203 }
204 }
205
206 pub fn is_format_string(&self) -> bool {
210 matches!(self, Self::FormatString { .. })
211 }
212}
213
214impl fmt::Display for ModusTerm {
215 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
216 match self {
217 ModusTerm::Constant(s) => write!(f, "\"{}\"", s),
218 ModusTerm::UserVariable(s) => write!(f, "{}", s),
219 ModusTerm::FormatString {
220 position: _,
221 fragments,
222 } => write!(
223 f,
224 "\"{}\"",
225 fragments
226 .iter()
227 .map(|x| x.to_string())
228 .collect::<Vec<_>>()
229 .join("")
230 ),
231 ModusTerm::AnonymousVariable => write!(f, "_"),
232 ModusTerm::Array(_, ts) => write!(
233 f,
234 "[{}]",
235 ts.iter()
236 .map(|t| t.to_string())
237 .collect::<Vec<_>>()
238 .join(", ")
239 ),
240 }
241 }
242}
243
244impl str::FromStr for ModusTerm {
245 type Err = Vec<Diagnostic<()>>;
246
247 fn from_str(s: &str) -> Result<Self, Self::Err> {
248 let span = Span::new(s);
249 match parser::modus_term(span) {
250 Result::Ok((_, o)) => Ok(o),
251 Result::Err(nom::Err::Error(e) | nom::Err::Failure(e)) => Err(better_convert_error(e)),
252 _ => unimplemented!(),
253 }
254 }
255}
256
257impl From<ModusTerm> for logic::IRTerm {
258 fn from(modus_term: ModusTerm) -> Self {
259 match modus_term {
260 ModusTerm::Constant(c) => logic::IRTerm::Constant(process_raw_string(&c)),
261 ModusTerm::FormatString { .. } => {
262 unreachable!("BUG: analysis should've handled this case.")
263 }
264 ModusTerm::UserVariable(v) => logic::IRTerm::UserVariable(v),
265 ModusTerm::AnonymousVariable => sld::Auxiliary::aux(true),
266 ModusTerm::Array(_, _) => unimplemented!(),
267 }
268 }
269}
270
271impl fmt::Display for logic::Literal<ModusTerm> {
272 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
273 match &*self.args {
274 [] => write!(f, "{}", self.predicate),
275 _ => write!(
276 f,
277 "{}({})",
278 self.predicate,
279 self.args
280 .iter()
281 .map(|a| a.to_string())
282 .collect::<Vec<String>>()
283 .join(", ")
284 ),
285 }
286 }
287}
288
289type Literal = logic::Literal<ModusTerm>;
290
291impl From<Literal> for logic::Literal {
292 fn from(modus_literal: Literal) -> Self {
293 Self {
294 positive: modus_literal.positive,
295 position: modus_literal.position,
296 predicate: modus_literal.predicate,
297 args: modus_literal
298 .args
299 .into_iter()
300 .map(|arg| arg.into())
301 .collect(),
302 }
303 }
304}
305
306#[derive(Clone, PartialEq, Eq, Hash, Debug)]
307pub struct Operator {
308 pub position: Option<SpannedPosition>,
309 pub predicate: Predicate,
310 pub args: Vec<ModusTerm>,
311}
312
313impl Operator {
314 #[cfg(test)]
315 pub fn eq_ignoring_position(&self, other: &Operator) -> bool {
316 self.predicate == other.predicate && self.args == other.args
317 }
318
319 pub fn with_position(self, position: Option<SpannedPosition>) -> Operator {
320 Operator { position, ..self }
321 }
322}
323
324impl fmt::Display for Operator {
325 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
326 match &*self.args {
327 [] => write!(f, "{}", self.predicate),
328 _ => write!(
329 f,
330 "{}({})",
331 self.predicate,
332 self.args
333 .iter()
334 .map(|t| t.to_string())
335 .collect::<Vec<_>>()
336 .join(", ")
337 ),
338 }
339 }
340}
341
342#[derive(Clone, PartialEq, Debug)]
343pub struct Modusfile(pub Vec<ModusClause>);
344
345#[derive(Clone, PartialEq, Debug)]
346pub struct Version {
347 major: u32,
348 minor: u32,
349 patch: u32,
350 pre_release: String,
351 build: String,
352}
353
354fn better_convert_error(e: ErrorTree<Span>) -> Vec<Diagnostic<()>> {
357 fn generate_base_label(span: &Span, kind: &BaseErrorKind) -> Label<()> {
358 let length = if let BaseErrorKind::Expected(nom_supreme::error::Expectation::Tag(t)) = kind
359 {
360 t.len()
361 } else {
362 1
365 };
366 Label::primary((), span.location_offset()..span.location_offset() + length)
367 }
368
369 let mut diags = Vec::new();
370 match e {
371 ErrorTree::Base { location, kind } => {
372 let diag = Diagnostic::error()
373 .with_message(kind.to_string())
374 .with_labels(vec![generate_base_label(&location, &kind)]);
375 diags.push(diag);
376 }
377 ErrorTree::Stack { base, contexts } => {
378 let mut labels = Vec::new();
379 let diag;
380
381 let base_range;
382 match *base {
383 ErrorTree::Base { location, kind } => {
384 labels.push(generate_base_label(&location, &kind));
385 base_range = labels[0].range.clone();
386 diag = Diagnostic::error().with_message(kind.to_string());
387 }
388 ErrorTree::Stack { .. } => panic!("base of an error stack was a stack"),
389 ErrorTree::Alt(alts) => {
390 return alts
391 .into_iter()
392 .flat_map(|a| better_convert_error(a))
393 .collect()
394 }
395 }
396
397 for (span, stack_context) in contexts.iter() {
398 labels.push(
399 Label::secondary((), span.location_offset()..base_range.end)
400 .with_message(stack_context.to_string()),
401 );
402 }
403 diags.push(diag.with_labels(labels))
404 }
405 ErrorTree::Alt(alts) => {
406 diags.extend(
407 alts.into_iter()
408 .flat_map(|alt_tree| better_convert_error(alt_tree)),
409 );
410 }
411 }
412 diags
413}
414
415impl str::FromStr for Modusfile {
416 type Err = Vec<Diagnostic<()>>;
417
418 fn from_str(s: &str) -> Result<Self, Self::Err> {
419 let span = Span::new(s);
420 match parser::modusfile(span) {
421 Result::Ok((_, o)) => Ok(o),
422 Result::Err(nom::Err::Error(e) | nom::Err::Failure(e)) => Err(better_convert_error(e)),
423 _ => unimplemented!(),
424 }
425 }
426}
427
428impl str::FromStr for Expression {
429 type Err = Vec<Diagnostic<()>>;
430
431 fn from_str(s: &str) -> Result<Self, Self::Err> {
432 let span = Span::new(s);
433 match parser::body(span) {
434 Ok((_, o)) => Ok(o),
435 Err(nom::Err::Error(e) | nom::Err::Failure(e)) => Err(better_convert_error(e)),
436 _ => unimplemented!(),
437 }
438 }
439}
440
441impl fmt::Display for Expression {
442 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
443 match self {
444 Expression::OperatorApplication(_, expr, op) => {
445 write!(f, "({})::{}", expr.to_string(), op)
446 }
447 Expression::Literal(l) => write!(f, "{}", l.to_string()),
448 Expression::And(_, positive, expr1, expr2) => {
449 write!(
452 f,
453 "{}({}, {})",
454 if *positive { "" } else { "!" },
455 expr1,
456 expr2
457 )
458 }
459 Expression::Or(_, positive, expr1, expr2) => {
460 write!(
461 f,
462 "{}({}; {})",
463 if *positive { "" } else { "!" },
464 expr1,
465 expr2
466 )
467 }
468 }
469 }
470}
471
472impl From<Literal> for Expression {
474 fn from(l: Literal) -> Self {
475 Expression::Literal(l)
476 }
477}
478
479impl str::FromStr for ModusClause {
480 type Err = String;
481
482 fn from_str(s: &str) -> Result<Self, Self::Err> {
483 let span = Span::new(s);
484 match parser::modus_clause(span) {
485 Result::Ok((_, o)) => Ok(o),
486 Result::Err(e) => Result::Err(format!("{}", e)),
487 }
488 }
489}
490
491impl fmt::Display for ModusClause {
492 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
493 if let Some(e) = &self.body {
494 write!(f, "{} :- {}.", self.head, e.to_string(),)
495 } else {
496 write!(f, "{}.", self.head)
497 }
498 }
499}
500
501impl str::FromStr for Literal {
502 type Err = String;
503
504 fn from_str(s: &str) -> Result<Self, Self::Err> {
505 let span = Span::new(s);
506 match logic::parser::literal(parser::modus_term, parser::token_sep0)(span) {
507 Result::Ok((_, o)) => Ok(o),
508 Result::Err(e) => Result::Err(format!("{}", e)),
509 }
510 }
511}
512
513pub mod parser {
514 use crate::logic::parser::{literal, literal_identifier, recognized_span, IResult};
515 use crate::logic::Predicate;
516
517 use super::*;
518
519 use nom::bytes::complete::{escaped, is_a};
520 use nom::character::complete::{multispace0, none_of, one_of};
521 use nom::combinator::{cut, opt, recognize};
522 use nom::error::context;
523 use nom::multi::{many0_count, many1, separated_list0, separated_list1};
524 use nom::sequence::{pair, tuple};
525 use nom::{
526 branch::alt,
527 combinator::{eof, map},
528 multi::many0,
529 sequence::{delimited, preceded, separated_pair, terminated},
530 };
531 use nom_supreme::tag::complete::tag;
532
533 fn comment(s: Span) -> IResult<Span, Span> {
534 recognize(delimited(
535 tag("#"),
536 opt(not_line_ending),
537 alt((line_ending, eof)),
538 ))(s)
539 }
540
541 #[test]
542 fn test_comment_oneline() {
543 let input = Span::new("# comment");
544 let (rest, _) = comment(input).unwrap();
545 assert!(rest.is_empty());
546 }
547
548 fn comments(s: Span) -> IResult<Span, Vec<Span>> {
549 delimited(
550 multispace0,
551 many0(terminated(comment, multispace0)),
552 multispace0,
553 )(s)
554 }
555
556 #[test]
557 fn test_comments() {
558 let s = Span::new("# comment\n# comment\n");
559 let (rest, _) = comments(s).unwrap();
560 assert!(rest.is_empty());
561 }
562
563 #[test]
564 fn test_comments_oneline() {
565 let s = Span::new("# comment");
566 let (rest, _) = comments(s).unwrap();
567 assert!(rest.is_empty());
568 }
569
570 #[test]
571 fn test_comments_empty() {
572 let s = Span::new("");
573 comments(s).unwrap();
574 }
575
576 pub fn token_sep0(s: Span) -> IResult<Span, ()> {
579 map(comments, |_| ())(s)
580 }
581
582 #[test]
583 fn test_token_sep0_empty() {
584 let s = Span::new("");
585 token_sep0(s).unwrap();
586 }
587
588 #[test]
589 fn test_token_sep0() {
590 token_sep0(Span::new(" ")).unwrap();
591 token_sep0(Span::new(" \n#")).unwrap();
592 token_sep0(Span::new("#\n\n")).unwrap();
593 token_sep0(Span::new(" ")).unwrap();
594 }
595
596 fn head(i: Span) -> IResult<Span, Literal> {
597 context(stringify!(head), literal(modus_term, token_sep0))(i)
598 }
599
600 fn unification_sugar(i: Span) -> IResult<Span, Literal> {
603 map(
604 recognized_span(tuple((
605 modus_term,
606 delimited(token_sep0, alt((tag("!="), tag("="))), token_sep0),
607 cut(modus_term),
608 ))),
609 |(spanned_pos, (t1, op, t2))| Literal {
610 positive: op.fragment().len() == 1,
611 position: Some(spanned_pos),
612 predicate: Predicate("string_eq".to_owned()),
613 args: vec![t1, t2],
614 },
615 )(i)
616 }
617
618 fn modus_literal(i: Span) -> IResult<Span, Expression> {
619 map(literal(modus_term, token_sep0), Expression::Literal)(i)
620 }
621
622 fn parenthesized_expr(i: Span) -> IResult<Span, Expression> {
624 let l_paren_with_comments = |i| terminated(tag("("), comments)(i);
625 let r_paren_with_comments = |i| preceded(comments, cut(tag(")")))(i);
626
627 map(
628 pair(
629 many0_count(terminated(nom::character::complete::char('!'), token_sep0)),
630 delimited(l_paren_with_comments, body, r_paren_with_comments),
631 ),
632 |(neg_count, expr)| {
633 if neg_count % 2 == 0 {
634 expr
635 } else {
636 expr.negate_current()
638 }
639 },
640 )(i)
641 }
642
643 fn operator(i: Span) -> IResult<Span, Operator> {
645 map(
646 recognized_span(pair(
647 terminated(literal_identifier, token_sep0),
648 opt(delimited(
649 terminated(tag("("), token_sep0),
650 separated_list1(
651 terminated(tag(","), token_sep0),
652 terminated(modus_term, token_sep0),
653 ),
654 cut(terminated(tag(")"), token_sep0)),
655 )),
656 )),
657 |(spanned_pos, (name, args))| Operator {
658 position: Some(spanned_pos),
659 predicate: Predicate(name.fragment().to_string()),
660 args: args.unwrap_or(Vec::new()),
661 },
662 )(i)
663 }
664
665 fn expression_inner(i: Span) -> IResult<Span, Expression> {
666 let unification_expr_parser = map(unification_sugar, Expression::Literal);
667 let op_application_parser = map(
669 pair(
670 alt((modus_literal, parenthesized_expr)),
671 many1(recognized_span(preceded(
673 delimited(token_sep0, tag("::"), token_sep0),
674 cut(operator),
675 ))),
676 ),
677 |(expr, ops_with_span)| {
678 ops_with_span.into_iter().fold(expr, |acc, (span, op)| {
679 let new_span: Option<SpannedPosition> = acc
681 .get_spanned_position()
682 .as_ref()
683 .map(|s| SpannedPosition {
684 offset: s.offset,
685 length: span.offset + span.length - s.offset,
686 });
687 Expression::OperatorApplication(new_span, Box::new(acc), op)
688 })
689 },
690 );
691 alt((
692 unification_expr_parser,
693 op_application_parser,
694 modus_literal,
695 parenthesized_expr,
696 ))(i)
697 }
698
699 pub fn body(i: Span) -> IResult<Span, Expression> {
700 let comma_separated_exprs = map(
701 separated_list1(delimited(comments, tag(","), comments), expression_inner),
702 |es| {
703 es.into_iter()
704 .reduce(|e1, e2| {
705 let s1 = e1.get_spanned_position().as_ref().unwrap();
707 let s2 = e2.get_spanned_position().as_ref().unwrap();
708
709 let computed_span = SpannedPosition {
711 offset: s1.offset,
712 length: s2.offset + s2.length - s1.offset,
713 };
714 Expression::And(Some(computed_span), true, Box::new(e1), Box::new(e2))
715 })
716 .expect("Converting list to expression pairs.")
717 },
718 );
719 let semi_separated_exprs = map(
720 separated_list1(
721 delimited(comments, tag(";"), comments),
722 comma_separated_exprs,
723 ),
724 |es| {
725 es.into_iter()
726 .reduce(|e1, e2| {
727 let s1 = e1.get_spanned_position().as_ref().unwrap();
728 let s2 = e2.get_spanned_position().as_ref().unwrap();
729
730 let computed_span = SpannedPosition {
731 offset: s1.offset,
732 length: s2.offset + s2.length - s1.offset,
733 };
734 Expression::Or(Some(computed_span), true, Box::new(e1), Box::new(e2))
735 })
736 .expect("Converting list to expression pairs.")
737 },
738 );
739 preceded(comments, semi_separated_exprs)(i)
742 }
743
744 fn fact(i: Span) -> IResult<Span, ModusClause> {
745 context(
748 stringify!(fact),
749 map(
750 terminated(
751 head,
752 cut(terminated(nom::character::complete::char('.'), token_sep0)),
756 ),
757 |h| ModusClause {
758 head: h,
759 body: None,
760 },
761 ),
762 )(i)
763 }
764
765 fn rule(i: Span) -> IResult<Span, ModusClause> {
766 context(
767 stringify!(rule),
768 map(
769 separated_pair(
770 head,
771 delimited(token_sep0, tag(":-"), token_sep0),
772 context(
773 "rule_body",
774 cut(terminated(
775 body,
776 terminated(nom::character::complete::char('.'), token_sep0),
777 )),
778 ),
779 ),
780 |(head, body)| ModusClause {
781 head,
782 body: Some(body),
783 },
784 ),
785 )(i)
786 }
787
788 pub fn process_raw_string(s: &str) -> String {
794 let mut processed = String::new();
795
796 let mut chars = s.chars().peekable();
797 while let Some(c) = chars.next() {
798 if c == '\\' {
799 match chars.next() {
800 Some('"') => processed.push('"'),
801 Some('\\') => processed.push('\\'),
802 Some('n') => processed.push('\n'),
803 Some('r') => processed.push('\r'),
804 Some('t') => processed.push('\t'),
805 Some('0') => processed.push('\0'),
806 Some('\n') => {
807 while let Some(c) = chars.peek() {
809 if !c.is_whitespace() {
810 break;
811 }
812 chars.next();
813 }
814 }
815 Some(c) => {
816 processed.push('\\');
818 processed.push(c);
819 }
820 None => panic!("given string ends with an escape character"),
821 }
822 } else {
823 processed.push(c);
824 }
825 }
826 processed
827 }
828
829 const STRING_ESCAPE_CHARS: &str = "\"\\nrt0\n";
830 const FORMAT_STRING_ESCAPE_CHARS: &str = "$\"\\nrt0\n";
831
832 fn string_content(i: Span) -> IResult<Span, String> {
835 let escape_parser = escaped(none_of("\\\""), '\\', one_of(STRING_ESCAPE_CHARS));
836 let (i, o) = opt(escape_parser)(i)?;
837 let parsed_str: &str = o.map(|span| *span.fragment()).unwrap_or("");
838 Ok((i, parsed_str.to_owned()))
839 }
840
841 fn format_string_content(i: Span) -> IResult<Span, String> {
843 let (i, o) = escaped(
848 none_of("\\\"$"),
849 '\\',
850 cut(one_of(FORMAT_STRING_ESCAPE_CHARS)),
851 )(i)?;
852 let parsed_str: &str = o.fragment();
853 Ok((i, parsed_str.to_owned()))
854 }
855
856 pub fn modus_const(i: Span) -> IResult<Span, String> {
857 context(
858 stringify!(modus_const),
859 delimited(tag("\""), string_content, cut(tag("\""))),
860 )(i)
861 }
862
863 fn format_string_fragment(i: Span) -> IResult<Span, FormatStringFragment> {
864 let interpolation = delimited(
865 terminated(tag("${"), token_sep0),
866 cut(modus_var),
867 cut(preceded(token_sep0, tag("}"))),
868 );
869
870 alt((
871 map(interpolation, |v_span| {
872 FormatStringFragment::InterpolatedVariable(
873 v_span.into(),
874 v_span.fragment().to_string(),
875 )
876 }),
877 map(tag("$"), |span: Span| {
880 FormatStringFragment::StringContent(span.into(), span.fragment().to_string())
881 }),
882 map(recognized_span(format_string_content), |(span, content)| {
883 FormatStringFragment::StringContent(span, content)
884 }),
885 ))(i)
886 }
887
888 pub fn modus_format_string(
889 i: Span,
890 ) -> IResult<Span, (SpannedPosition, Vec<FormatStringFragment>)> {
891 context(
892 stringify!(modus_format_string),
893 recognized_span(delimited(
894 tag("f\""),
895 cut(many0(format_string_fragment)),
896 cut(tag("\"")),
897 )),
898 )(i)
899 }
900
901 pub fn variable_identifier(i: Span) -> IResult<Span, Span> {
902 literal_identifier(i)
903 }
904
905 fn modus_var(i: Span) -> IResult<Span, Span> {
906 context(stringify!(modus_var), variable_identifier)(i)
907 }
908
909 pub fn string_interpolation(i: Span) -> IResult<Span, Span> {
910 delimited(
911 terminated(tag("${"), token_sep0),
912 modus_var,
913 cut(preceded(token_sep0, tag("}"))),
914 )(i)
915 }
916
917 fn modus_array_term(i: Span) -> IResult<Span, Vec<ModusTerm>> {
918 delimited(
919 terminated(tag("["), token_sep0),
920 separated_list0(delimited(token_sep0, tag(","), token_sep0), modus_term),
921 cut(preceded(token_sep0, tag("]"))),
922 )(i)
923 }
924
925 pub fn modus_term(i: Span) -> IResult<Span, ModusTerm> {
926 alt((
927 map(modus_const, ModusTerm::Constant),
928 map(recognized_span(modus_array_term), |(span, terms)| {
929 ModusTerm::Array(span, terms)
930 }),
931 map(modus_format_string, |(position, fragments)| {
932 ModusTerm::FormatString {
933 position,
934 fragments,
935 }
936 }),
937 map(is_a("_"), |_| ModusTerm::AnonymousVariable),
938 map(modus_var, |s| {
939 ModusTerm::UserVariable(s.fragment().to_string())
940 }),
941 ))(i)
942 }
943
944 pub fn modus_clause(i: Span) -> IResult<Span, ModusClause> {
945 alt((rule, fact))(i)
946 }
947
948 pub fn modusfile(i: Span) -> IResult<Span, Modusfile> {
949 map(
950 terminated(
951 many0(preceded(token_sep0, modus_clause)),
952 terminated(token_sep0, eof),
953 ),
954 Modusfile,
955 )(i)
956 }
957}
958
959#[cfg(test)]
960mod tests {
961 use rand::Rng;
962 use serial_test::serial;
963
964 use crate::modusfile::parser::modus_term;
965
966 use super::*;
967
968 type Rule = ModusClause;
969
970 #[test]
971 fn fact() {
972 let l1 = Literal {
973 positive: true,
974 position: None,
975 predicate: logic::Predicate("l1".into()),
976 args: Vec::new(),
977 };
978 let c = ModusClause {
979 head: l1,
980 body: None,
981 };
982
983 assert_eq!("l1.", c.to_string());
984
985 let actual: ModusClause = "l1.".parse().unwrap();
986 assert!(c.eq_ignoring_position(&actual));
987 }
988
989 #[test]
990 fn rule() {
991 let l1 = Literal {
992 positive: true,
993 position: None,
994 predicate: logic::Predicate("l1".into()),
995 args: Vec::new(),
996 };
997 let l2 = Literal {
998 positive: true,
999 position: None,
1000 predicate: logic::Predicate("l2".into()),
1001 args: Vec::new(),
1002 };
1003 let l3 = Literal {
1004 positive: true,
1005 position: None,
1006 predicate: logic::Predicate("l3".into()),
1007 args: Vec::new(),
1008 };
1009 let c = Rule {
1010 head: l1,
1011 body: Expression::And(None, true, Box::new(l2.into()), Box::new(l3.into())).into(),
1012 };
1013
1014 assert_eq!("l1 :- (l2, l3).", c.to_string());
1015
1016 let actual1: Rule = "l1 :- l2, l3.".parse().unwrap();
1017 assert!(c.eq_ignoring_position(&actual1));
1018 let actual2: Rule = "l1 :- l2,\n\tl3.".parse().unwrap();
1019 assert!(c.eq_ignoring_position(&actual2));
1020 }
1021
1022 #[test]
1023 fn rule_with_or() {
1024 let l1: Literal = "l1".parse().unwrap();
1025 let l2: Literal = "l2".parse().unwrap();
1026 let c = Rule {
1027 head: "foo".parse().unwrap(),
1028 body: Expression::Or(None, true, Box::new(l1.into()), Box::new(l2.into())).into(),
1029 };
1030
1031 assert_eq!("foo :- (l1; l2).", c.to_string());
1032
1033 let actual: Rule = "foo :- l1; l2.".parse().unwrap();
1034 assert!(c.eq_ignoring_position(&actual));
1035 }
1036
1037 #[test]
1038 fn rule_with_operator() {
1039 let foo = Literal {
1040 positive: true,
1041 position: None,
1042 predicate: logic::Predicate("foo".into()),
1043 args: Vec::new(),
1044 };
1045 let a = Literal {
1046 positive: true,
1047 position: None,
1048 predicate: logic::Predicate("a".into()),
1049 args: Vec::new(),
1050 };
1051 let b = Literal {
1052 positive: true,
1053 position: None,
1054 predicate: logic::Predicate("b".into()),
1055 args: Vec::new(),
1056 };
1057 let merge = Operator {
1058 position: None,
1059 predicate: logic::Predicate("merge".into()),
1060 args: Vec::new(),
1061 };
1062 let r1 = Rule {
1063 head: foo.clone(),
1064 body: Expression::OperatorApplication(
1065 None,
1066 Expression::And(None, true, Box::new(a.clone().into()), Box::new(b.into())).into(),
1067 merge.clone(),
1068 )
1069 .into(),
1070 };
1071 let r2 = Rule {
1072 head: foo,
1073 body: Expression::OperatorApplication(None, Box::new(Expression::Literal(a)), merge)
1074 .into(),
1075 };
1076
1077 assert_eq!("foo :- ((a, b))::merge.", r1.to_string());
1078 let actual1 = "foo :- ((a, b))::merge.".parse().unwrap();
1079 assert!(r1.eq_ignoring_position(&actual1));
1080 let actual2 = "foo :- (a, b)::merge.".parse().unwrap();
1081 assert!(r1.eq_ignoring_position(&actual2));
1082
1083 assert_eq!("foo :- (a)::merge.", r2.to_string());
1084 let actual3 = "foo :- a::merge.".parse().unwrap();
1085 assert!(r2.eq_ignoring_position(&actual3));
1086 let actual3 = "foo :- ( a )::merge.".parse().unwrap();
1087 assert!(r2.eq_ignoring_position(&actual3));
1088 }
1089
1090 #[test]
1091 #[serial]
1092 fn modusclause_to_clause() {
1093 crate::translate::reset_operator_pair_id();
1094 let foo = Literal {
1095 positive: true,
1096 position: None,
1097 predicate: logic::Predicate("foo".into()),
1098 args: Vec::new(),
1099 };
1100 let a = Literal {
1101 positive: true,
1102 position: None,
1103 predicate: logic::Predicate("a".into()),
1104 args: Vec::new(),
1105 };
1106 let b = Literal {
1107 positive: true,
1108 position: None,
1109 predicate: logic::Predicate("b".into()),
1110 args: Vec::new(),
1111 };
1112 let merge = Operator {
1113 position: None,
1114 predicate: logic::Predicate("merge".into()),
1115 args: Vec::new(),
1116 };
1117 let r = Rule {
1118 head: foo,
1119 body: Expression::OperatorApplication(
1120 None,
1121 Expression::And(None, true, Box::new(a.into()), Box::new(b.into())).into(),
1122 merge,
1123 )
1124 .into(),
1125 };
1126 assert_eq!("foo :- ((a, b))::merge.", r.to_string());
1127
1128 let c: Vec<logic::Clause> = (&r).into();
1130 assert_eq!(1, c.len());
1131 assert_eq!(
1132 r#"foo :- _operator_merge_begin("0"), a, b, _operator_merge_end("0")"#,
1133 c[0].to_string()
1134 );
1135 }
1136
1137 #[test]
1138 #[serial]
1139 fn modusclause_to_clause_with_or() {
1140 crate::translate::reset_operator_pair_id();
1141 let foo: Literal = "foo".parse().unwrap();
1142 let a: Literal = "a".parse().unwrap();
1143 let b: Literal = "b".parse().unwrap();
1144 let merge = Operator {
1145 position: None,
1146 predicate: logic::Predicate("merge".into()),
1147 args: Vec::new(),
1148 };
1149 let r1 = Rule {
1150 head: foo.clone(),
1151 body: Expression::OperatorApplication(
1152 None,
1153 Expression::Or(
1154 None,
1155 true,
1156 Box::new(a.clone().into()),
1157 Box::new(b.clone().into()),
1158 )
1159 .into(),
1160 merge,
1161 )
1162 .into(),
1163 };
1164 let r2 = Rule {
1165 head: foo.clone(),
1166 body: Expression::And(
1167 None,
1168 true,
1169 Box::new(a.clone().into()),
1170 Box::new(Expression::And(
1171 None,
1172 true,
1173 Box::new(b.clone().into()),
1174 Box::new(Expression::Or(
1175 None,
1176 true,
1177 Box::new(a.clone().into()),
1178 Box::new(b.clone().into()),
1179 )),
1180 )),
1181 )
1182 .into(),
1183 };
1184 assert_eq!("foo :- ((a; b))::merge.", r1.to_string());
1185 assert_eq!("foo :- (a, (b, (a; b))).", r2.to_string());
1186
1187 let c1: Vec<logic::Clause> = (&r1).into();
1188 assert_eq!(2, c1.len());
1189 assert_eq!(
1190 r#"foo :- _operator_merge_begin("0"), a, _operator_merge_end("0")"#,
1191 c1[0].to_string()
1192 );
1193 assert_eq!(
1194 r#"foo :- _operator_merge_begin("1"), b, _operator_merge_end("1")"#,
1195 c1[1].to_string()
1196 );
1197
1198 let c2: Vec<logic::Clause> = (&r2).into();
1199 assert_eq!(2, c2.len());
1200 assert_eq!("foo :- a, b, a", c2[0].to_string());
1201 assert_eq!("foo :- a, b, b", c2[1].to_string());
1202 }
1203
1204 #[test]
1205 fn modus_constant() {
1206 let inp1 = r#""Hello\nWorld""#;
1208 let inp2 = r#""Tabs\tare\tbetter\tthan\tspaces""#;
1209 let inp3 = r#""Testing \
1210 multiline.""#;
1211 let (_, s1) = parser::modus_const(Span::new(inp1.into())).unwrap();
1212 let (_, s2) = parser::modus_const(Span::new(inp2.into())).unwrap();
1213 let (_, s3) = parser::modus_const(Span::new(inp3.into())).unwrap();
1214
1215 assert_eq!(s1, r#"Hello\nWorld"#);
1216 assert_eq!(s2, r#"Tabs\tare\tbetter\tthan\tspaces"#);
1217 assert_eq!(
1218 s3,
1219 r#"Testing \
1220 multiline."#
1221 );
1222 }
1223
1224 #[test]
1225 fn anonymous_variables() {
1226 let expected = Literal {
1227 positive: true,
1228 position: None,
1229 predicate: Predicate("l".into()),
1230 args: vec![
1231 ModusTerm::Constant("foo".to_string()),
1232 ModusTerm::AnonymousVariable,
1233 ],
1234 };
1235 let actual: Literal = "l(\"foo\", _)".parse().unwrap();
1236 assert!(expected.eq_ignoring_position(&actual));
1237 }
1238
1239 #[test]
1240 fn modus_expression() {
1241 let a: Literal = "a".parse().unwrap();
1242 let b: Literal = "b".parse().unwrap();
1243 let c: Literal = "c".parse().unwrap();
1244 let d: Literal = "d".parse().unwrap();
1245
1246 let e1 = Expression::And(
1247 None,
1248 true,
1249 Expression::Literal(a).into(),
1250 Expression::Literal(b).into(),
1251 );
1252 let e2 = Expression::And(
1253 None,
1254 true,
1255 Expression::Literal(c).into(),
1256 Expression::Literal(d).into(),
1257 );
1258
1259 let expr = Expression::Or(None, false, e1.into(), e2.into());
1260
1261 let expr_str = "!((a, b); (c, d))";
1262 assert_eq!(expr_str, expr.to_string());
1263
1264 let rule: ModusClause = format!("foo :- {}.", expr_str).parse().unwrap();
1265 assert!(expr.eq_ignoring_position(&rule.body.unwrap()))
1266 }
1267
1268 #[test]
1269 fn modus_unification() {
1270 let modus_clause: ModusClause = "foo(X, Y, A, B) :- X = Y, A != B.".parse().unwrap();
1271 let expected_body = Expression::And(
1272 None,
1273 true,
1274 Box::new(Expression::Literal("string_eq(X, Y)".parse().unwrap())),
1275 Box::new(Expression::Literal("!string_eq(A, B)".parse().unwrap())),
1276 );
1277 assert!(expected_body.eq_ignoring_position(&modus_clause.body.unwrap()));
1278 }
1279
1280 #[test]
1281 fn modus_negated_unification() {
1282 let inp = "foo(X, Y) :- X != Y.";
1283 let expected_lit: Literal = "!string_eq(X, Y)".parse().unwrap();
1284 let actual: Expression = inp.parse().map(|r: ModusClause| r.body).unwrap().unwrap();
1285 assert!(Expression::Literal(expected_lit).eq_ignoring_position(&actual));
1286 }
1287
1288 #[test]
1289 fn multiple_clause_with_different_ops() {
1290 let foo = Literal {
1291 positive: true,
1292 position: None,
1293 predicate: logic::Predicate("foo".into()),
1294 args: vec![ModusTerm::UserVariable("x".to_owned())],
1295 };
1296 let bar = Literal {
1297 positive: true,
1298 position: None,
1299 predicate: logic::Predicate("bar".into()),
1300 args: Vec::new(),
1301 };
1302 let baz = Literal {
1303 positive: true,
1304 position: None,
1305 predicate: logic::Predicate("baz".into()),
1306 args: Vec::new(),
1307 };
1308 let a = Rule {
1309 head: logic::Literal {
1310 positive: true,
1311 position: None,
1312 predicate: logic::Predicate("a".to_owned()),
1313 args: vec![],
1314 },
1315 body: Some(Expression::And(
1316 None,
1317 true,
1318 Box::new(Expression::OperatorApplication(
1319 None,
1320 Box::new(Expression::And(
1321 None,
1322 true,
1323 Box::new(foo.into()),
1324 Box::new(bar.into()),
1325 )),
1326 Operator {
1327 position: None,
1328 predicate: logic::Predicate("setenv".into()),
1329 args: vec![
1330 ModusTerm::Constant("a".to_owned()),
1331 ModusTerm::Constant("foobar".to_owned()),
1332 ],
1333 },
1334 )),
1335 Box::new(Expression::OperatorApplication(
1336 None,
1337 Box::new(baz.into()),
1338 Operator {
1339 position: None,
1340 predicate: logic::Predicate("setenv".into()),
1341 args: vec![
1342 ModusTerm::Constant("a".to_owned()),
1343 ModusTerm::Constant("baz".to_owned()),
1344 ],
1345 },
1346 )),
1347 )),
1348 };
1349
1350 let actual: Rule = r#"a:-(foo(x),bar)::setenv("a","foobar"), (baz)::setenv("a", "baz")."#
1351 .parse()
1352 .unwrap();
1353 assert!(a.eq_ignoring_position(&actual));
1354 }
1355
1356 #[test]
1357 fn op_application_chained_with_spaces() {
1358 let r1: Rule = "a :- foo::set_env::in_env.".parse().unwrap();
1359 let r2: Rule = "a :- foo :: set_env :: in_env.".parse().unwrap();
1360 let r3: Rule = "a :- foo\n::\nset_env\n::\nin_env.".parse().unwrap();
1361
1362 let expected = Rule {
1363 head: logic::Literal {
1364 positive: true,
1365 position: None,
1366 predicate: logic::Predicate("a".to_owned()),
1367 args: vec![],
1368 },
1369 body: Some(Expression::OperatorApplication(
1370 None,
1371 Box::new(Expression::OperatorApplication(
1372 None,
1373 Box::new(Expression::Literal(logic::Literal {
1374 positive: true,
1375 position: None,
1376 predicate: logic::Predicate("foo".into()),
1377 args: Vec::new(),
1378 })),
1379 Operator {
1380 position: None,
1381 predicate: logic::Predicate("set_env".into()),
1382 args: Vec::new(),
1383 },
1384 )),
1385 Operator {
1386 position: None,
1387 predicate: logic::Predicate("in_env".into()),
1388 args: Vec::new(),
1389 },
1390 )),
1391 };
1392
1393 assert!(expected.eq_ignoring_position(&r1));
1394 assert!(expected.eq_ignoring_position(&r2));
1395 assert!(expected.eq_ignoring_position(&r3));
1396 }
1397
1398 #[test]
1399 fn test_spaces() {
1400 fn add_spaces(tokens: &[&str], spaces: &[&str]) -> String {
1401 let mut s = String::new();
1402 let mut rng = rand::thread_rng();
1403 for token in tokens {
1404 if token.is_empty() {
1405 continue;
1406 }
1407 if spaces.len() == 1 {
1408 s.push_str(spaces[0]);
1409 } else {
1410 s.push_str(spaces[rng.gen_range(0..spaces.len())]);
1411 }
1412 s.push_str(token.trim());
1413 }
1414 s
1415 }
1416
1417 fn do_test(lines: &str) {
1418 let tokens = lines.lines().collect::<Vec<_>>();
1419 fn should_parse(s: &str) {
1420 if let Err(e) = s.parse::<Modusfile>() {
1421 panic!(
1422 "Failed to parse: Error:\n{e:?}\nModusfile:\n{s}",
1423 e = e,
1424 s = s
1425 );
1426 }
1427 }
1428 should_parse(&add_spaces(&tokens, &[""]));
1429 should_parse(&add_spaces(&tokens, &[" "]));
1430 should_parse(&add_spaces(&tokens, &["\n"]));
1431 should_parse(&add_spaces(&tokens, &["# Comment\n"]));
1432 should_parse(&add_spaces(&tokens, &["\n# Comment\n"]));
1433 should_parse(&add_spaces(&tokens, &["", " ", "\n", "\n# Comment\n"]));
1434 }
1435
1436 do_test(
1437 r#"
1438 final
1439 :-
1440 from
1441 (
1442 "alpine"
1443 )
1444 .
1445 "#,
1446 );
1447
1448 do_test(
1449 r#"
1450 final
1451 :-
1452 from
1453 (
1454 "alpine"
1455 )
1456 ::
1457 set_workdir
1458 (
1459 "/tmp"
1460 )
1461 ,
1462 run
1463 (
1464 "pwd"
1465 )
1466 .
1467 "#,
1468 );
1469
1470 do_test(
1471 r#"
1472 final
1473 :-
1474 a
1475 ,
1476 b
1477 ,
1478 c
1479 ;
1480 d
1481 ,
1482 (
1483 e
1484 ,
1485 f
1486 (
1487 V1
1488 ,
1489 V2
1490 )
1491 ,
1492 V1
1493 =
1494 V2
1495 )
1496 ;
1497 g
1498 ,
1499 h
1500 ::i
1501 ,
1502 j
1503 .
1504 "#,
1505 )
1506 }
1507
1508 #[test]
1509 fn reports_error_in_rule() {
1510 let modus_file: Result<Modusfile, Vec<Diagnostic<()>>> =
1511 "foo(X) :- bar(X), baz(X), .".parse();
1512 assert!(modus_file.is_err());
1513
1514 let diags = modus_file.err().unwrap();
1515 assert_eq!(1, diags.len());
1516 assert_eq!(
1517 diags[0].severity,
1518 codespan_reporting::diagnostic::Severity::Error
1519 );
1520 assert!(diags[0].labels[1].message.contains("body"));
1521 assert!(diags[0].labels[2].message.contains("rule"));
1522 }
1523
1524 #[test]
1525 fn format_string() {
1526 let case = "f\"foo ${ X }\"";
1527
1528 let expected: ModusTerm = ModusTerm::FormatString {
1529 position: SpannedPosition {
1530 offset: 0,
1531 length: 10 + 3,
1532 },
1533 fragments: vec![
1534 FormatStringFragment::StringContent(
1535 SpannedPosition {
1536 offset: 2,
1537 length: 4,
1538 },
1539 "foo ".to_string(),
1540 ),
1541 FormatStringFragment::InterpolatedVariable(
1542 SpannedPosition {
1543 offset: 9,
1544 length: 1,
1545 },
1546 "X".to_string(),
1547 ),
1548 ],
1549 };
1550
1551 assert_eq!(expected, modus_term(Span::new(case)).unwrap().1);
1552 }
1553
1554 #[test]
1555 fn format_string_starts_with_variable() {
1556 let case = r#"f"${var1} foo bar\tbaz""#;
1557
1558 let expected = ModusTerm::FormatString {
1559 position: SpannedPosition {
1560 offset: 0,
1561 length: 20 + 3,
1562 },
1563 fragments: vec![
1564 FormatStringFragment::InterpolatedVariable(
1565 SpannedPosition {
1566 offset: 4,
1567 length: 4,
1568 },
1569 "var1".to_string(),
1570 ),
1571 FormatStringFragment::StringContent(
1572 SpannedPosition {
1573 offset: 9,
1574 length: 13,
1575 },
1576 r#" foo bar\tbaz"#.to_string(),
1577 ),
1578 ],
1579 };
1580
1581 assert_eq!(expected, modus_term(Span::new(case)).unwrap().1);
1582 }
1583
1584 #[test]
1585 fn empty_format_string() {
1586 let case = "f\"\"";
1587
1588 let expected: ModusTerm = ModusTerm::FormatString {
1589 position: SpannedPosition {
1590 offset: 0,
1591 length: 0 + 3,
1592 },
1593 fragments: vec![],
1594 };
1595
1596 assert_eq!(expected, modus_term(Span::new(case)).unwrap().1);
1597 }
1598
1599 #[test]
1600 fn format_string_errors_with_multiple_vars() {
1601 let case = "f\"bar ${X X2}\"";
1602
1603 let actual = modus_term(Span::new(case));
1604 assert!(actual.is_err());
1605 }
1606
1607 #[test]
1608 fn format_string_dollar_without_interpolation() {
1609 let case = "f\"bar $ baz\"";
1610
1611 let expected = ModusTerm::FormatString {
1612 position: SpannedPosition {
1613 offset: 0,
1614 length: 9 + 3,
1615 },
1616 fragments: vec![
1617 FormatStringFragment::StringContent(
1618 SpannedPosition {
1619 offset: 2,
1620 length: 4,
1621 },
1622 r#"bar "#.to_string(),
1623 ),
1624 FormatStringFragment::StringContent(
1625 SpannedPosition {
1626 offset: 6,
1627 length: 1,
1628 },
1629 "$".to_string(),
1630 ),
1631 FormatStringFragment::StringContent(
1632 SpannedPosition {
1633 offset: 7,
1634 length: 4,
1635 },
1636 " baz".to_string(),
1637 ),
1638 ],
1639 };
1640
1641 assert_eq!(expected, modus_term(Span::new(case)).unwrap().1);
1642 }
1643
1644 #[test]
1645 fn f_string_escaped_interpolation() {
1646 let case = r#"f"\${var} foo""#;
1647
1648 let expected = ModusTerm::FormatString {
1649 position: SpannedPosition {
1650 offset: 0,
1651 length: 11 + 3,
1652 },
1653 fragments: vec![FormatStringFragment::StringContent(
1654 SpannedPosition {
1655 offset: 2,
1656 length: 11,
1657 },
1658 r#"\${var} foo"#.to_string(),
1659 )],
1660 };
1661
1662 assert_eq!(expected, modus_term(Span::new(case)).unwrap().1);
1663 }
1664
1665 #[test]
1666 fn modus_empty_array_term() {
1667 let case = "[]";
1668 let expected = ModusTerm::Array(
1669 SpannedPosition {
1670 offset: 0,
1671 length: 2,
1672 },
1673 Vec::new(),
1674 );
1675 assert_eq!(expected, modus_term(Span::new(case)).unwrap().1);
1676 }
1677
1678 #[test]
1679 fn modus_simple_array_term() {
1680 let case = r#"[ "javac", javacParam ]"#;
1681 let expected = ModusTerm::Array(
1682 SpannedPosition {
1683 offset: 0,
1684 length: 23,
1685 },
1686 vec![
1687 ModusTerm::Constant("javac".to_string()),
1688 ModusTerm::UserVariable("javacParam".to_string()),
1689 ],
1690 );
1691 assert_eq!(expected, modus_term(Span::new(case)).unwrap().1);
1692 }
1693}