int_seq/lib.rs
1extern crate proc_macro;
2
3mod affine;
4mod oeis;
5mod parser;
6mod sequence;
7
8use parser::parse_int_seq;
9use proc_macro::TokenStream;
10
11// /// Run doctests from the README.md file
12// #[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
13// #[cfg(doctest)]
14// struct ReadmeDoctests;
15
16/// Given a sequence of integers that includes an range ellipsis, we deduce the
17/// integers that are omitted using various heuristics. This macro will produce
18/// an array of integers at compile time. We do not support lazy iterators yet
19/// since some integer sequences do not have a simple closed form formula.
20///
21/// We currently use two heuristics for inferring integer sequences:
22/// - Affine sequences are of the form `a*i + b`
23/// - OEIS sequences are in the [On-Line Encyclopedia of Integer Sequences (OEIS)](https://oeis.org/) database. We use `reqwest` to perform HTTP requests within a procedural macro to query the database. By doing this at compile time, we can avoid runtime overheads
24///
25/// This is inspired by [Raku lang's sequence operator (`...`)](https://doc.perl6.org/language/operators#infix_...).
26///
27/// Example:
28/// ```rust
29/// use int_seq::int_seq;
30///
31/// // affine sequence
32/// assert_eq!(int_seq!(57, 64, 71, 78, 85..100), [57, 64, 71, 78, 85, 92, 99]);
33/// // inclusive upper bound
34/// assert_eq!(int_seq!(3, 6..=12), [3, 6, 9, 12]);
35/// // basic range
36/// assert_eq!(int_seq!(1..5), [1, 2, 3, 4]);
37/// // powers of 2
38/// assert_eq!(int_seq!(1, 2, 4, 8, 16..=1024), [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]);
39/// // fibonacci sequence
40/// assert_eq!(int_seq!(0, 1, 1, 2, 3, 5, 8, 13..100), [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]);
41/// // McKay-Thompson series of class 32e for the Monster group (OEIS A082303).
42/// assert_eq!(int_seq!(-4, -9, 4, 10..26), [-4, -9, 4, 10, -4, -12, 6, 15, -7, -17, 7, 19, -8, -22, 10])
43/// ```
44#[proc_macro]
45pub fn int_seq(token_stream: TokenStream) -> TokenStream {
46 let (seq, end) = parse_int_seq(&token_stream).unwrap_or_else(|| {
47 panic!(
48 "could not parse token stream. token stream: {:x?}",
49 token_stream
50 )
51 });
52 let inferred_seq = sequence::infer_sequence(&seq).expect("could not infer sequence");
53 let generated_seq = inferred_seq.generate(&seq, end);
54 let comma_seperated_ints = generated_seq
55 .iter()
56 .map(|x| x.to_string())
57 .collect::<Vec<String>>()
58 .join(",");
59 format!("[{}]", comma_seperated_ints).parse().unwrap()
60}