1use std::fmt;
17
18use crate::defaults::{FloatType, IntegerType, UnsignedIntegerType};
19use crate::error::{Error, Result};
20
21#[derive(Clone, Debug, Eq, PartialEq)]
37#[must_use]
38pub struct Sieve {
39 root: Node,
40}
41
42#[derive(Clone, Debug, Eq, PartialEq)]
43enum Node {
44 Residual {
46 modulus: UnsignedIntegerType,
47 shift: UnsignedIntegerType,
48 },
49 Not(Box<Node>),
50 And(Box<Node>, Box<Node>),
51 Or(Box<Node>, Box<Node>),
52 Xor(Box<Node>, Box<Node>),
53 Group(Box<Node>),
60}
61
62impl Node {
63 fn contains(&self, z: IntegerType) -> bool {
64 match self {
65 Self::Residual { modulus, shift } => {
66 z.rem_euclid(*modulus as IntegerType) == *shift as IntegerType
67 }
68 Self::Group(inner) => inner.contains(z),
69 Self::Not(inner) => !inner.contains(z),
70 Self::And(left, right) => left.contains(z) && right.contains(z),
71 Self::Or(left, right) => left.contains(z) || right.contains(z),
72 Self::Xor(left, right) => left.contains(z) != right.contains(z),
73 }
74 }
75
76 fn shifted(&self, n: IntegerType) -> Node {
82 match self {
83 Self::Residual { modulus, shift } => Self::Residual {
84 modulus: *modulus,
85 shift: (*shift as IntegerType + n).rem_euclid(*modulus as IntegerType)
86 as UnsignedIntegerType,
87 },
88 Self::Group(inner) => Self::Group(Box::new(inner.shifted(n))),
89 Self::Not(inner) => Self::Not(Box::new(inner.shifted(n))),
90 Self::And(left, right) => {
91 Self::And(Box::new(left.shifted(n)), Box::new(right.shifted(n)))
92 }
93 Self::Or(left, right) => {
94 Self::Or(Box::new(left.shifted(n)), Box::new(right.shifted(n)))
95 }
96 Self::Xor(left, right) => {
97 Self::Xor(Box::new(left.shifted(n)), Box::new(right.shifted(n)))
98 }
99 }
100 }
101
102 fn collect_moduli(&self, out: &mut Vec<UnsignedIntegerType>) {
103 match self {
104 Self::Residual { modulus, .. } => out.push(*modulus),
105 Self::Group(inner) | Self::Not(inner) => inner.collect_moduli(out),
106 Self::And(left, right) | Self::Or(left, right) | Self::Xor(left, right) => {
107 left.collect_moduli(out);
108 right.collect_moduli(out);
109 }
110 }
111 }
112}
113
114impl Sieve {
115 pub fn parse(expression: &str) -> Result<Self> {
122 let tokens = tokenize(expression)?;
123 let mut parser = Parser {
124 tokens: &tokens,
125 position: 0,
126 };
127 let root = parser.parse_or()?;
128 if parser.position != tokens.len() {
129 return Err(Error::Sieve(format!(
130 "trailing input in sieve {expression:?} at token {}",
131 parser.position
132 )));
133 }
134 Ok(Self { root })
135 }
136
137 pub fn contains(&self, z: IntegerType) -> bool {
139 self.root.contains(z)
140 }
141
142 pub fn period(&self) -> UnsignedIntegerType {
146 let mut moduli = Vec::new();
147 self.root.collect_moduli(&mut moduli);
148 moduli.into_iter().fold(1, lcm)
149 }
150
151 pub fn segment(&self, low: IntegerType, high: IntegerType) -> Vec<IntegerType> {
153 (low..=high).filter(|z| self.contains(*z)).collect()
154 }
155
156 pub fn interval_widths(&self) -> Result<Vec<IntegerType>> {
163 let period = self.period();
164 let members = self.segment(0, period as IntegerType);
165 if members.len() < 2 {
166 return Err(Error::Sieve(format!(
167 "sieve has {} member(s) in its period of {period}, so it defines no intervals",
168 members.len()
169 )));
170 }
171 Ok(members.windows(2).map(|pair| pair[1] - pair[0]).collect())
172 }
173
174 pub fn shifted(&self, n: IntegerType) -> Self {
178 Self {
179 root: self.root.shifted(n),
180 }
181 }
182
183 pub fn segment_binary(&self, low: IntegerType, high: IntegerType) -> Vec<IntegerType> {
188 (low..=high)
189 .map(|z| IntegerType::from(self.contains(z)))
190 .collect()
191 }
192
193 pub fn segment_widths(&self, low: IntegerType, high: IntegerType) -> Vec<IntegerType> {
200 let members = self.segment(low, high);
201 members.windows(2).map(|pair| pair[1] - pair[0]).collect()
202 }
203
204 pub fn segment_unit(&self, low: IntegerType, high: IntegerType) -> Vec<FloatType> {
210 let members = self.segment(low, high);
211 if members.len() < 2 {
212 return vec![0.0; members.len().min(1)];
213 }
214 let span = FloatType::from(high - low);
215 if span == 0.0 {
216 return vec![0.0; members.len()];
217 }
218 members
219 .into_iter()
220 .map(|member| FloatType::from(member - low) / span)
221 .collect()
222 }
223
224 pub fn collect(
232 &self,
233 n: IntegerType,
234 z_minimum: IntegerType,
235 length: usize,
236 ) -> Result<Vec<IntegerType>> {
237 const STEP: IntegerType = 100;
238 const ROUNDS: usize = 10_000;
239
240 let shifted = self.shifted(n);
241 let mut found = Vec::with_capacity(length);
242 let mut low = z_minimum;
243 for _ in 0..ROUNDS {
244 found.extend(shifted.segment(low, low + STEP - 1));
245 if found.len() >= length {
246 found.truncate(length);
247 return Ok(found);
248 }
249 low += STEP;
250 }
251 Err(Error::Sieve(format!(
252 "desired length of {length} cannot be found in sieve {self}"
253 )))
254 }
255
256 pub fn intersection(&self, other: &Self) -> Self {
263 self.combined(other, Node::And)
264 }
265
266 pub fn union(&self, other: &Self) -> Self {
269 self.combined(other, Node::Or)
270 }
271
272 pub fn symmetric_difference(&self, other: &Self) -> Self {
275 self.combined(other, Node::Xor)
276 }
277
278 fn combined(&self, other: &Self, join: fn(Box<Node>, Box<Node>) -> Node) -> Self {
279 Self {
280 root: join(
281 Box::new(Node::Group(Box::new(self.root.clone()))),
282 Box::new(Node::Group(Box::new(other.root.clone()))),
283 ),
284 }
285 }
286}
287
288impl fmt::Display for Node {
289 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
290 match self {
291 Self::Residual { modulus, shift } => write!(f, "{modulus}@{shift}"),
292 Self::Group(inner) => write!(f, "{{{inner}}}"),
293 Self::Not(inner) => write!(f, "-{inner}"),
294 Self::And(left, right) => write!(f, "{left}&{right}"),
295 Self::Or(left, right) => write!(f, "{left}|{right}"),
296 Self::Xor(left, right) => write!(f, "{left}^{right}"),
297 }
298 }
299}
300
301impl fmt::Display for Sieve {
313 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
314 write!(f, "{}", self.root)
315 }
316}
317
318fn lcm(a: UnsignedIntegerType, b: UnsignedIntegerType) -> UnsignedIntegerType {
320 if a == 0 || b == 0 {
321 return 0;
322 }
323 num::integer::lcm(a, b)
324}
325
326#[derive(Clone, Copy, Debug, Eq, PartialEq)]
327enum Token {
328 Number(UnsignedIntegerType),
329 At,
330 Not,
331 And,
332 Or,
333 Xor,
334 Open,
335 Close,
336}
337
338fn tokenize(expression: &str) -> Result<Vec<Token>> {
339 let mut tokens = Vec::new();
340 let mut chars = expression.chars().peekable();
341
342 while let Some(&character) = chars.peek() {
343 match character {
344 ' ' | '\t' | '\n' | '\r' => {
345 chars.next();
346 }
347 '0'..='9' => {
348 let mut value: UnsignedIntegerType = 0;
349 while let Some(&digit) = chars.peek() {
350 let Some(digit) = digit.to_digit(10) else {
351 break;
352 };
353 value = value
354 .checked_mul(10)
355 .and_then(|value| value.checked_add(digit))
356 .ok_or_else(|| {
357 Error::Sieve(format!("number overflows in sieve {expression:?}"))
358 })?;
359 chars.next();
360 }
361 tokens.push(Token::Number(value));
362 }
363 '@' => {
364 chars.next();
365 tokens.push(Token::At);
366 }
367 '-' => {
368 chars.next();
369 tokens.push(Token::Not);
370 }
371 '&' => {
372 chars.next();
373 tokens.push(Token::And);
374 }
375 '|' => {
376 chars.next();
377 tokens.push(Token::Or);
378 }
379 '^' => {
380 chars.next();
381 tokens.push(Token::Xor);
382 }
383 '{' | '(' => {
384 chars.next();
385 tokens.push(Token::Open);
386 }
387 '}' | ')' => {
388 chars.next();
389 tokens.push(Token::Close);
390 }
391 other => {
392 return Err(Error::Sieve(format!(
393 "unexpected character {other:?} in sieve {expression:?}"
394 )));
395 }
396 }
397 }
398
399 if tokens.is_empty() {
400 return Err(Error::Sieve("sieve expression is empty".to_string()));
401 }
402 Ok(tokens)
403}
404
405struct Parser<'a> {
406 tokens: &'a [Token],
407 position: usize,
408}
409
410impl Parser<'_> {
411 fn peek(&self) -> Option<Token> {
412 self.tokens.get(self.position).copied()
413 }
414
415 fn eat(&mut self, token: Token) -> bool {
416 if self.peek() == Some(token) {
417 self.position += 1;
418 return true;
419 }
420 false
421 }
422
423 fn parse_or(&mut self) -> Result<Node> {
424 let mut left = self.parse_xor()?;
425 while self.eat(Token::Or) {
426 let right = self.parse_xor()?;
427 left = Node::Or(Box::new(left), Box::new(right));
428 }
429 Ok(left)
430 }
431
432 fn parse_xor(&mut self) -> Result<Node> {
433 let mut left = self.parse_and()?;
434 while self.eat(Token::Xor) {
435 let right = self.parse_and()?;
436 left = Node::Xor(Box::new(left), Box::new(right));
437 }
438 Ok(left)
439 }
440
441 fn parse_and(&mut self) -> Result<Node> {
442 let mut left = self.parse_unary()?;
443 while self.eat(Token::And) {
444 let right = self.parse_unary()?;
445 left = Node::And(Box::new(left), Box::new(right));
446 }
447 Ok(left)
448 }
449
450 fn parse_unary(&mut self) -> Result<Node> {
451 if self.eat(Token::Not) {
452 return Ok(Node::Not(Box::new(self.parse_unary()?)));
453 }
454 self.parse_primary()
455 }
456
457 fn parse_primary(&mut self) -> Result<Node> {
458 if self.eat(Token::Open) {
459 let inner = self.parse_or()?;
460 if !self.eat(Token::Close) {
461 return Err(Error::Sieve("unclosed group in sieve".to_string()));
462 }
463 return Ok(Node::Group(Box::new(inner)));
464 }
465
466 let Some(Token::Number(modulus)) = self.peek() else {
467 return Err(Error::Sieve(format!(
468 "expected a modulus in sieve at token {}",
469 self.position
470 )));
471 };
472 self.position += 1;
473
474 if modulus == 0 {
475 return Err(Error::Sieve("sieve modulus must be non-zero".to_string()));
476 }
477
478 let shift = if self.eat(Token::At) {
480 let Some(Token::Number(shift)) = self.peek() else {
481 return Err(Error::Sieve(
482 "expected a shift after `@` in sieve".to_string(),
483 ));
484 };
485 self.position += 1;
486 shift
487 } else {
488 0
489 };
490
491 Ok(Node::Residual {
492 modulus,
493 shift: shift % modulus,
494 })
495 }
496}
497
498pub fn eratosthenes(first_candidate: u64) -> impl Iterator<Item = u64> {
504 let mut composites: std::collections::HashMap<u64, u64> = std::collections::HashMap::new();
505 let mut candidate: u64 = 2;
506 std::iter::from_fn(move || {
507 loop {
508 let found = candidate;
509 candidate += 1;
510 match composites.remove(&found) {
511 Some(prime) => {
512 let mut next = found + prime;
513 while composites.contains_key(&next) {
514 next += prime;
515 }
516 composites.insert(next, prime);
517 }
518 None => {
519 composites.insert(found * found, found);
520 if found >= first_candidate {
521 return Some(found);
522 }
523 }
524 }
525 }
526 })
527}
528
529const MILLER_RABIN_WITNESSES: [u64; 12] = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37];
532
533fn power_mod(mut base: u64, mut exponent: u64, modulus: u64) -> u64 {
534 let mut result: u64 = 1;
535 base %= modulus;
536 while exponent > 0 {
537 if exponent & 1 == 1 {
538 result = ((u128::from(result) * u128::from(base)) % u128::from(modulus)) as u64;
539 }
540 base = ((u128::from(base) * u128::from(base)) % u128::from(modulus)) as u64;
541 exponent >>= 1;
542 }
543 result
544}
545
546pub fn rabin_miller(n: i64) -> bool {
552 let n = n.unsigned_abs();
553 if n < 2 {
554 return false;
555 }
556 if n < 4 {
557 return true;
558 }
559 if !matches!(n % 6, 1 | 5) {
560 return false;
561 }
562 for witness in MILLER_RABIN_WITNESSES {
563 if n == witness {
564 return true;
565 }
566 if n.is_multiple_of(witness) {
567 return false;
568 }
569 }
570 let (mut odd, mut rounds) = (n - 1, 0);
571 while odd.is_multiple_of(2) {
572 odd /= 2;
573 rounds += 1;
574 }
575 'witnesses: for witness in MILLER_RABIN_WITNESSES {
576 let mut x = power_mod(witness, odd, n);
577 if x == 1 || x == n - 1 {
578 continue;
579 }
580 for _ in 1..rounds {
581 x = ((u128::from(x) * u128::from(x)) % u128::from(n)) as u64;
582 if x == n - 1 {
583 continue 'witnesses;
584 }
585 }
586 return false;
587 }
588 true
589}
590
591pub fn discrete_binary_pad(
599 series: &[IntegerType],
600 fix_range: Option<&[IntegerType]>,
601) -> Result<Vec<u8>> {
602 let bounds = fix_range.unwrap_or(series);
603 let (Some(lowest), Some(highest)) = (bounds.iter().min(), bounds.iter().max()) else {
604 return Err(Error::Sieve(
605 "a binary pad needs a range: give a series or a fixRange".to_string(),
606 ));
607 };
608 Ok((*lowest..=*highest)
609 .map(|value| u8::from(series.contains(&value)))
610 .collect())
611}
612
613pub fn unit_norm_range(series: &[FloatType], fix_range: Option<&[FloatType]>) -> Vec<FloatType> {
621 let bounds = fix_range.unwrap_or(series);
622 let lowest = bounds
623 .iter()
624 .copied()
625 .fold(FloatType::INFINITY, FloatType::min);
626 let highest = bounds
627 .iter()
628 .copied()
629 .fold(FloatType::NEG_INFINITY, FloatType::max);
630 let span = highest - lowest;
631 if series.len() <= 1 {
632 return vec![0.0];
633 }
634 series
635 .iter()
636 .map(|value| {
637 if span == 0.0 {
638 0.0
639 } else {
640 (value - lowest) / span
641 }
642 })
643 .collect()
644}
645
646pub fn unit_norm_equal(parts: usize) -> Vec<FloatType> {
650 match parts {
651 0 | 1 => vec![0.0],
652 2 => vec![0.0, 1.0],
653 _ => {
654 let step = 1.0 / (parts - 1) as FloatType;
655 let mut unit: Vec<FloatType> = (0..parts - 1).map(|y| y as FloatType * step).collect();
656 unit.push(1.0);
657 unit
658 }
659 }
660}
661
662pub fn unit_norm_step(
667 step: FloatType,
668 a: FloatType,
669 b: FloatType,
670 normalized: bool,
671) -> Result<Vec<FloatType>> {
672 if a == b {
673 return Ok(Vec::new());
674 }
675 if step.is_nan() || step <= 0.0 {
676 return Err(Error::Sieve(format!(
677 "a step of {step} never crosses the range"
678 )));
679 }
680 let (lowest, highest) = if a < b { (a, b) } else { (b, a) };
681 let mut values = Vec::new();
682 let mut x = lowest;
683 while x <= highest {
684 values.push(x);
685 x += step;
686 }
687 Ok(if normalized {
688 unit_norm_equal(values.len())
689 } else {
690 values
691 })
692}
693
694#[cfg(test)]
695mod tests {
696 use super::*;
697
698 #[test]
700 fn the_number_helpers_answer_what_music21_answers() {
701 assert_eq!(
702 eratosthenes(2).take(5).collect::<Vec<_>>(),
703 [2, 3, 5, 7, 11]
704 );
705 assert_eq!(eratosthenes(95).take(2).collect::<Vec<_>>(), [97, 101]);
706
707 assert!(!rabin_miller(234));
708 assert!(rabin_miller(5));
709 assert!(!rabin_miller(4));
710 assert!(!rabin_miller(97 * 2));
711 assert!(rabin_miller(6_i64.pow(4) + 1));
712 assert!(!rabin_miller(123_986_234_193));
713 assert!(rabin_miller(-7));
714 assert!(!rabin_miller(1));
715 assert!(rabin_miller(1_000_000_007));
716 assert!(!rabin_miller(1_000_000_007 * 3));
717
718 assert_eq!(
719 discrete_binary_pad(&[3, 10, 12], None).unwrap(),
720 [1, 0, 0, 0, 0, 0, 0, 1, 0, 1]
721 );
722 assert_eq!(discrete_binary_pad(&[3, 4, 5], None).unwrap(), [1, 1, 1]);
723 assert_eq!(
724 discrete_binary_pad(&[4], Some(&[2, 5])).unwrap(),
725 [0, 0, 1, 0]
726 );
727 assert!(discrete_binary_pad(&[], None).is_err());
728
729 assert_eq!(unit_norm_range(&[0.0, 3.0, 4.0], None), [0.0, 0.75, 1.0]);
730 let thirds = unit_norm_range(&[1.0, 3.0, 4.0], None);
731 assert!((thirds[1] - 2.0 / 3.0).abs() < 1e-12);
732 assert_eq!(unit_norm_range(&[5.0], None), [0.0]);
733 assert_eq!(unit_norm_range(&[2.0, 2.0], None), [0.0, 0.0]);
734 assert_eq!(unit_norm_range(&[1.0, 2.0], Some(&[0.0, 4.0])), [0.25, 0.5]);
735
736 assert_eq!(unit_norm_equal(3), [0.0, 0.5, 1.0]);
737 assert_eq!(unit_norm_equal(1), [0.0]);
738 assert_eq!(unit_norm_equal(2), [0.0, 1.0]);
739
740 assert_eq!(
741 unit_norm_step(0.5, 0.0, 1.0, true).unwrap(),
742 [0.0, 0.5, 1.0]
743 );
744 assert_eq!(
745 unit_norm_step(0.5, -1.0, 1.0, true).unwrap(),
746 [0.0, 0.25, 0.5, 0.75, 1.0]
747 );
748 assert_eq!(
749 unit_norm_step(0.5, -1.0, 1.0, false).unwrap(),
750 [-1.0, -0.5, 0.0, 0.5, 1.0]
751 );
752 assert_eq!(unit_norm_step(0.25, 0.0, 20.0, true).unwrap().len(), 81);
753 assert_eq!(unit_norm_step(0.25, 0.0, 20.0, false).unwrap().len(), 81);
754 assert!(unit_norm_step(0.5, 1.0, 1.0, true).unwrap().is_empty());
755 assert!(unit_norm_step(0.0, 0.0, 1.0, true).is_err());
756 }
757
758 #[test]
762 fn a_sieve_is_written_the_way_music21_writes_one() {
763 for (written, expected) in [
764 ("3@11", "3@2"),
765 ("2&4&8|5", "2@0&4@0&8@0|5@0"),
766 ("(5|2)&4&8", "{5@0|2@0}&4@0&8@0"),
767 ("3@2|7@1", "3@2|7@1"),
768 ] {
769 let sieve = Sieve::parse(written).expect("the expression parses");
770 assert_eq!(sieve.to_string(), expected, "writing {written}");
771 }
772 }
773
774 #[test]
776 fn combining_two_sieves_braces_each_side() {
777 let a = Sieve::parse("3@11").expect("a parses");
778 let b = Sieve::parse("2&4&8|5").expect("b parses");
779 assert_eq!(b.intersection(&a).to_string(), "{2@0&4@0&8@0|5@0}&{3@2}");
780 assert_eq!(b.union(&a).to_string(), "{2@0&4@0&8@0|5@0}|{3@2}");
781 assert_eq!(
782 b.symmetric_difference(&a).to_string(),
783 "{2@0&4@0&8@0|5@0}^{3@2}"
784 );
785 }
786
787 #[test]
788 fn collecting_reads_the_sieve_shifted_on_from_a_starting_point() {
789 let sieve = Sieve::parse("3@11").expect("the expression parses");
790 assert_eq!(
791 sieve.collect(10, 100, 10).expect("ten members are found"),
792 [102, 105, 108, 111, 114, 117, 120, 123, 126, 129]
793 );
794 }
795
796 #[test]
797 fn the_segment_formats_answer_what_music21_answers() {
798 let sieve = Sieve::parse("3@2|7@1").expect("the expression parses");
799 assert_eq!(
800 sieve.segment_binary(0, 99)[..12],
801 [0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1]
802 );
803 assert_eq!(
804 sieve.segment_widths(0, 99)[..12],
805 [1, 3, 3, 3, 3, 1, 2, 3, 2, 1, 3, 3]
806 );
807 let unit = sieve.segment_unit(0, 99);
808 assert_eq!(unit.len(), 43);
809 assert_eq!(unit[0], 1.0 / 99.0);
810 assert_eq!(*unit.last().expect("the segment is not empty"), 1.0);
811 }
812
813 fn widths(expression: &str) -> Vec<IntegerType> {
814 Sieve::parse(expression)
815 .expect("sieve parses")
816 .interval_widths()
817 .expect("sieve has intervals")
818 }
819
820 #[test]
821 fn a_single_residual_class_cycles_at_its_modulus() {
822 assert_eq!(widths("3@0"), [3]);
823 assert_eq!(widths("4@0"), [4]);
824 assert_eq!(widths("2@0"), [2]);
825 assert_eq!(widths("12@0"), [12]);
826 assert_eq!(widths("5"), [5]);
828 }
829
830 #[test]
831 fn the_major_scale_is_a_sieve() {
832 assert_eq!(
833 widths("(-3@2 & 4) | (-3@1 & 4@1) | (3@2 & 4@2) | (-3 & 4@3)"),
834 [2, 2, 1, 2, 2, 2, 1]
835 );
836 }
837
838 #[test]
839 fn union_intersection_and_symmetric_difference_match_music21() {
840 assert_eq!(widths("3@0|7@0"), [3, 3, 1, 2, 3, 2, 1, 3, 3]);
841 assert_eq!(widths("{3@0|4@0}"), [3, 1, 2, 2, 1, 3]);
842 assert_eq!(widths("3@0&4@0"), [12]);
843 assert_eq!(widths("3@0^4@0"), [1, 2, 2, 1]);
844 assert_eq!(widths("5@2|7@3"), [1, 4, 3, 2, 5, 5, 2, 3, 4, 1]);
845 }
846
847 #[test]
848 fn negation_applies_to_residuals_and_to_groups() {
849 assert_eq!(widths("-3@0"), [1]);
850 assert_eq!(widths("-5@2"), [1, 2, 1, 1]);
851 assert_eq!(widths("-{3@0|4@0}"), [1, 3, 2, 3, 1]);
852 }
853
854 #[test]
855 fn and_binds_tighter_than_or() {
856 assert_eq!(widths("3@0|4@0&6@0"), widths("3@0|{4@0&6@0}"));
858 assert_eq!(widths("3@0|4@0&6@0"), [3, 3, 3, 3]);
859 assert_ne!(widths("3@0|4@0&6@0"), widths("{3@0|4@0}&6@0"));
860 assert_eq!(widths("{3@0|4@0}&6@0"), [6, 6]);
861 }
862
863 #[test]
864 fn parentheses_and_braces_group_alike() {
865 assert_eq!(widths("(3@0|4@0)"), widths("{3@0|4@0}"));
866 }
867
868 #[test]
869 fn period_is_the_lcm_of_the_moduli() {
870 assert_eq!(Sieve::parse("3@0").unwrap().period(), 3);
871 assert_eq!(Sieve::parse("3@0|7@0").unwrap().period(), 21);
872 assert_eq!(Sieve::parse("5@2|7@3").unwrap().period(), 35);
873 assert_eq!(Sieve::parse("3@0|4@0").unwrap().period(), 12);
874 }
875
876 #[test]
877 fn a_sieve_too_sparse_for_intervals_errors() {
878 assert!(Sieve::parse("3@1").unwrap().interval_widths().is_err());
881 }
882
883 #[test]
884 fn malformed_expressions_error_instead_of_panicking() {
885 for bad in [
886 "", " ", "@", "3@", "|3@0", "3@0|", "(3@0", "3@0)", "0@0", "3@0 & ", "x", "3@@0",
887 ] {
888 let parsed = Sieve::parse(bad);
889 assert!(parsed.is_err(), "{bad:?} should be rejected");
890 }
891 }
892
893 #[test]
894 fn membership_wraps_for_negative_integers() {
895 let sieve = Sieve::parse("3@1").unwrap();
896 assert!(sieve.contains(1));
897 assert!(sieve.contains(4));
898 assert!(sieve.contains(-2));
899 assert!(!sieve.contains(0));
900 }
901}