Skip to content

Commit 0281a3c

Browse files
committed
feat(fzn-rs): Implement support for i32 as an integer type
1 parent 07a1ca5 commit 0281a3c

File tree

4 files changed

+150
-13
lines changed

4 files changed

+150
-13
lines changed

fzn-rs/src/ast.rs

Lines changed: 121 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
//! It is a modified version of the `FlatZinc` type from [`flatzinc-serde`](https://docs.rs/flatzinc-serde).
55
use std::collections::BTreeMap;
66
use std::fmt::Display;
7+
use std::iter::FusedIterator;
78
use std::ops::RangeInclusive;
89
use std::rc::Rc;
910

@@ -153,15 +154,26 @@ impl<E: PartialOrd> RangeList<E> {
153154
}
154155
}
155156

156-
impl RangeList<i64> {
157-
/// Obtain an iterator over the values in this set.
158-
///
159-
/// Currently only implemented for `i64` elements, as that is what is used in the AST.
160-
pub fn iter(&self) -> impl Iterator<Item = i64> + '_ {
161-
self.intervals.iter().flat_map(|&(start, end)| start..=end)
162-
}
157+
macro_rules! impl_iter_fn {
158+
($int_type:ty) => {
159+
impl<'a> IntoIterator for &'a RangeList<$int_type> {
160+
type Item = $int_type;
161+
162+
type IntoIter = RangeListIter<'a, $int_type>;
163+
164+
fn into_iter(self) -> Self::IntoIter {
165+
RangeListIter {
166+
current_interval: self.intervals.first().copied().unwrap_or((1, 0)),
167+
tail: &self.intervals[1..],
168+
}
169+
}
170+
}
171+
};
163172
}
164173

174+
impl_iter_fn!(i32);
175+
impl_iter_fn!(i64);
176+
165177
impl<E: Copy + Ord> From<RangeInclusive<E>> for RangeList<E> {
166178
fn from(value: RangeInclusive<E>) -> Self {
167179
RangeList {
@@ -170,15 +182,81 @@ impl<E: Copy + Ord> From<RangeInclusive<E>> for RangeList<E> {
170182
}
171183
}
172184

173-
impl<E: Copy + Ord> FromIterator<E> for RangeList<E> {
174-
fn from_iter<T: IntoIterator<Item = E>>(iter: T) -> Self {
175-
let mut intervals: Vec<_> = iter.into_iter().map(|e| (e, e)).collect();
176-
intervals.sort();
185+
macro_rules! range_list_from_iter {
186+
($int_type:ty) => {
187+
impl FromIterator<$int_type> for RangeList<$int_type> {
188+
fn from_iter<T: IntoIterator<Item = $int_type>>(iter: T) -> Self {
189+
let mut intervals: Vec<_> = iter.into_iter().map(|e| (e, e)).collect();
190+
intervals.sort();
191+
intervals.dedup();
192+
193+
let mut idx = 0;
194+
195+
while idx < intervals.len() - 1 {
196+
let current = intervals[idx];
197+
let next = intervals[idx + 1];
198+
199+
if current.1 >= next.0 - 1 {
200+
intervals[idx] = (current.0, next.1);
201+
let _ = intervals.remove(idx + 1);
202+
} else {
203+
idx += 1;
204+
}
205+
}
206+
207+
RangeList { intervals }
208+
}
209+
}
210+
};
211+
}
212+
213+
range_list_from_iter!(i32);
214+
range_list_from_iter!(i64);
177215

178-
RangeList { intervals }
179-
}
216+
/// An [`Iterator`] over a [`RangeList`].
217+
#[derive(Debug)]
218+
pub struct RangeListIter<'a, E> {
219+
current_interval: (E, E),
220+
tail: &'a [(E, E)],
221+
}
222+
223+
macro_rules! impl_range_list_iter {
224+
($int_type:ty) => {
225+
impl<'a> RangeListIter<'a, $int_type> {
226+
fn new(intervals: &'a [($int_type, $int_type)]) -> Self {
227+
RangeListIter {
228+
current_interval: intervals.first().copied().unwrap_or((1, 0)),
229+
tail: &intervals[1..],
230+
}
231+
}
232+
}
233+
234+
impl Iterator for RangeListIter<'_, $int_type> {
235+
type Item = $int_type;
236+
237+
fn next(&mut self) -> Option<Self::Item> {
238+
let (current_lb, current_ub) = self.current_interval;
239+
240+
if current_lb > current_ub {
241+
let (next_interval, new_tail) = self.tail.split_first()?;
242+
self.current_interval = *next_interval;
243+
self.tail = new_tail;
244+
}
245+
246+
let current_lb = self.current_interval.0;
247+
self.current_interval.0 += 1;
248+
249+
Some(current_lb)
250+
}
251+
}
252+
253+
impl FusedIterator for RangeListIter<'_, $int_type> {}
254+
};
180255
}
181256

257+
impl_range_list_iter!(i32);
258+
impl_range_list_iter!(i64);
259+
182260
/// A literal in the instance.
183261
#[derive(Clone, Debug, PartialEq, Eq)]
184262
pub enum Literal {
@@ -265,3 +343,33 @@ pub enum AnnotationLiteral {
265343
/// `Annotation::Atom(ident)` or an `Literal::Identifier`.
266344
Annotation(AnnotationCall),
267345
}
346+
347+
#[cfg(test)]
348+
mod tests {
349+
use super::*;
350+
351+
#[test]
352+
fn rangelist_from_iter_identifies_continuous_ranges() {
353+
let set = RangeList::from_iter([1, 2, 3, 4]);
354+
355+
assert!(set.is_continuous());
356+
}
357+
358+
#[test]
359+
fn rangelist_from_iter_identifiers_non_continuous_ranges() {
360+
let set = RangeList::from_iter([1, 3, 4, 6]);
361+
362+
assert!(!set.is_continuous());
363+
}
364+
365+
#[test]
366+
fn rangelist_iter_produces_elements_in_set() {
367+
let set: RangeList<i32> = RangeList::from_iter([1, 3, 5]);
368+
369+
let mut iter = set.into_iter();
370+
assert_eq!(Some(1), iter.next());
371+
assert_eq!(Some(3), iter.next());
372+
assert_eq!(Some(5), iter.next());
373+
assert_eq!(None, iter.next());
374+
}
375+
}

fzn-rs/src/error.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ pub enum InstanceError {
2222

2323
#[error("expected {expected} arguments, got {actual}")]
2424
IncorrectNumberOfArguments { expected: usize, actual: usize },
25+
26+
#[error("value {0} does not fit in the required integer type")]
27+
IntegerOverflow(i64),
2528
}
2629

2730
#[derive(Clone, Debug, PartialEq, Eq)]

fzn-rs/src/from_ast.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,17 @@ pub trait FromLiteral: Sized {
6464
fn from_literal(node: &ast::Node<ast::Literal>) -> Result<Self, InstanceError>;
6565
}
6666

67+
impl FromLiteral for i32 {
68+
fn expected() -> Token {
69+
Token::IntLiteral
70+
}
71+
72+
fn from_literal(node: &ast::Node<ast::Literal>) -> Result<Self, InstanceError> {
73+
let integer = <i64 as FromLiteral>::from_literal(node)?;
74+
i32::try_from(integer).map_err(|_| InstanceError::IntegerOverflow(integer))
75+
}
76+
}
77+
6778
impl FromLiteral for i64 {
6879
fn expected() -> Token {
6980
Token::IntLiteral
@@ -138,6 +149,20 @@ impl FromLiteral for bool {
138149
}
139150
}
140151

152+
impl FromLiteral for RangeList<i32> {
153+
fn expected() -> Token {
154+
Token::IntSetLiteral
155+
}
156+
157+
fn from_literal(argument: &ast::Node<ast::Literal>) -> Result<Self, InstanceError> {
158+
let set = <RangeList<i64> as FromLiteral>::from_literal(argument)?;
159+
160+
set.into_iter()
161+
.map(|elem| i32::try_from(elem).map_err(|_| InstanceError::IntegerOverflow(elem)))
162+
.collect::<Result<_, _>>()
163+
}
164+
}
165+
141166
impl FromLiteral for RangeList<i64> {
142167
fn expected() -> Token {
143168
Token::IntSetLiteral

fzn-rs/src/fzn/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,7 @@ fn set_of<'tokens, 'src: 'tokens, I, T: Copy + Ord>(
502502
) -> impl Parser<'tokens, I, ast::RangeList<T>, FznExtra<'tokens, 'src>> + Clone
503503
where
504504
I: ValueInput<'tokens, Span = ast::Span, Token = Token<'src>>,
505+
ast::RangeList<T>: FromIterator<T>,
505506
{
506507
let sparse_set = value_parser
507508
.clone()

0 commit comments

Comments
 (0)