frame_sequence/
lib.rs

1//! A simple parser for frame sequences strings.
2//!
3//! This will parse a [`str`] describing a sequence of frames into
4//! a [`Vec`]`<`[`isize`]`>` containing individual frame numbers.
5//!
6//! Mainly intended/useful for rendering/animation/VFX applications.
7//!
8//! # Example Frame Sequence Strings
9//!
10//! Individual frames:
11//!
12//! `1,2,3,5,8,13` ⟶ `[1, 2, 3, 5, 8, 13]`
13//!
14//! A sequence:
15//!
16//! `10-15` ⟶ `[10, 11, 12, 13, 14, 15]`
17//!
18//! With step size:
19//!
20//! `10-20@2` ⟶ `[10, 12, 14, 16, 18, 20]`
21//!
22//! Step size must be always positive.
23//!
24//! To get a sequence backwards specify the range in reverse:
25//!
26//! `42-33@3` ⟶ `[42, 39, 36, 33]`
27//!
28//! With binary splitting:
29//!
30//! `10-20@b` ⟶ `[10, 20, 15, 12, 17, 11, 13, 16, 18, 14, 19]`
31//!
32//! The last frame of a sequence will be omitted if
33//! the specified step size does not touch it:
34//!
35//! `80-70@4` ⟶ `[80, 76, 72]`
36//!
37//! # Nuke-Style Step Size Token
38//!
39//! Using `x` instead of `@` as the step size token separator also works.
40use itertools::Itertools;
41use pest::{
42    error::Error,
43    iterators::{Pair, Pairs},
44    Parser,
45};
46use pest_derive::Parser;
47use std::{cmp::Ordering, collections::HashSet};
48
49#[derive(Parser)]
50#[grammar = "frame_format_grammar.pest"]
51struct FrameSequenceParser;
52
53/// Parse a frame sequence string into a [`Vec`]`<`[`isize`]`>` of frames.
54///
55/// See the main page of the documentation for example `input` strings.
56pub fn parse_frame_sequence(input: &str) -> Result<Vec<isize>, Box<Error<Rule>>> {
57    FrameSequenceParser::parse(Rule::FrameSequenceString, input)
58        .map(|token_tree| remove_duplicates(frame_sequence_token_tree_to_frames(token_tree)))
59        .map_err(|e| e.into())
60}
61
62fn chop(seq: &mut Vec<isize>, result: &mut Vec<isize>, elements: usize) {
63    if seq.len() < elements {
64        let mut new_seq = seq
65            .iter()
66            .tuple_windows()
67            .flat_map(|pair: (&isize, &isize)| {
68                let left = *pair.0;
69                let right = (*pair.0 + *pair.1) / 2;
70                if left < right {
71                    result.push(right);
72                    vec![left, right]
73                } else {
74                    vec![left]
75                }
76            })
77            .collect::<Vec<_>>();
78        new_seq.push(*seq.last().unwrap());
79        if new_seq.len() < elements {
80            chop(&mut new_seq, result, elements);
81        }
82        *seq = new_seq;
83    }
84}
85
86pub(crate) fn binary_sequence(range: (isize, isize)) -> Vec<isize> {
87    match range.0.cmp(&range.1) {
88        Ordering::Less => {
89            let mut seq = vec![range.0, range.1];
90            let mut result = seq.clone();
91            chop(&mut seq, &mut result, (range.1 - range.0) as _);
92            result
93        }
94        Ordering::Greater => {
95            let mut seq = vec![range.1, range.0];
96            let mut result = seq.clone();
97            chop(&mut seq, &mut result, (range.0 - range.1) as _);
98            result.reverse();
99            result
100        }
101        Ordering::Equal => vec![range.0],
102    }
103}
104
105fn frame_to_number(frame: Pair<Rule>) -> isize {
106    frame.as_str().parse::<isize>().unwrap()
107}
108
109fn frame_sequence_token_tree_to_frames(pairs: Pairs<Rule>) -> Vec<isize> {
110    pairs
111        .into_iter()
112        .flat_map(|pair| {
113            match pair.as_rule() {
114                Rule::FrameSequenceString | Rule::FrameSequence | Rule::FrameSequencePart => {
115                    frame_sequence_token_tree_to_frames(pair.into_inner())
116                }
117                Rule::FrameRange => {
118                    let mut pairs = pair.into_inner();
119                    let left = frame_to_number(pairs.next().unwrap());
120                    let right = frame_to_number(pairs.next().unwrap());
121
122                    // Do we have an `@`?
123                    if pairs.next().is_some() {
124                        let pair = pairs.next().unwrap();
125                        match pair.as_rule() {
126                            Rule::PositiveNumber => {
127                                let step = frame_to_number(pair);
128
129                                match left.cmp(&right) {
130                                    Ordering::Less => {
131                                        (left..right + 1).step_by(step as _).collect::<Vec<_>>()
132                                    }
133                                    Ordering::Greater => (right..left + 1)
134                                        .rev()
135                                        .step_by(step as _)
136                                        .collect::<Vec<_>>(),
137                                    Ordering::Equal => vec![left],
138                                }
139                            }
140                            Rule::BinarySequenceSymbol => binary_sequence((left, right)),
141                            _ => unreachable!(),
142                        }
143                    } else if left < right {
144                        (left..right + 1).collect::<Vec<_>>()
145                    } else if right < left {
146                        (right..left + 1).rev().collect::<Vec<_>>()
147                    }
148                    // left == right
149                    else {
150                        vec![left]
151                    }
152                }
153                Rule::Frame => vec![frame_to_number(pair)],
154                _ => vec![],
155            }
156        })
157        .collect::<Vec<_>>()
158}
159
160fn remove_duplicates(elements: Vec<isize>) -> Vec<isize> {
161    let mut set = HashSet::<isize>::new();
162    elements
163        .iter()
164        .filter_map(|e| {
165            if set.contains(e) {
166                None
167            } else {
168                set.insert(*e);
169                Some(*e)
170            }
171        })
172        .collect()
173}
174
175#[cfg(test)]
176mod tests {
177    #[test]
178    fn test_individual_frames() {
179        use crate::parse_frame_sequence;
180        let frames = parse_frame_sequence("1,2,3,5,8,13").unwrap();
181        assert_eq!([1, 2, 3, 5, 8, 13], frames.as_slice());
182    }
183
184    #[test]
185    fn test_frame_sequence() {
186        use crate::parse_frame_sequence;
187        let frames = parse_frame_sequence("10-15").unwrap();
188        assert_eq!([10, 11, 12, 13, 14, 15], frames.as_slice());
189    }
190
191    #[test]
192    fn test_fram_sequence_with_step() {
193        use crate::parse_frame_sequence;
194        let frames = parse_frame_sequence("10-20@2").unwrap();
195        assert_eq!([10, 12, 14, 16, 18, 20], frames.as_slice());
196    }
197
198    #[test]
199    fn test_frame_sequence_with_step_reversed() {
200        use crate::parse_frame_sequence;
201        let frames = parse_frame_sequence("42-33@3").unwrap();
202        assert_eq!([42, 39, 36, 33], frames.as_slice());
203    }
204
205    #[test]
206    fn test_binary_frame_sequence() {
207        use crate::parse_frame_sequence;
208        let frames = parse_frame_sequence("10-20@b").unwrap();
209        assert_eq!(
210            [10, 20, 15, 12, 17, 11, 13, 16, 18, 14, 19],
211            frames.as_slice()
212        );
213    }
214
215    #[test]
216    fn test_multi_sequence() {
217        use crate::parse_frame_sequence;
218        let frames = parse_frame_sequence("10-20@2,42-33@3").unwrap();
219        assert_eq!([10, 12, 14, 16, 18, 20, 42, 39, 36, 33], frames.as_slice());
220    }
221
222    #[test]
223    fn test_multi_sequence_x() {
224        use crate::parse_frame_sequence;
225        let frames = parse_frame_sequence("10-20x2,42-33x3").unwrap();
226        assert_eq!([10, 12, 14, 16, 18, 20, 42, 39, 36, 33], frames.as_slice());
227    }
228}