Skip to main content

nifty_interp/machine/
walk.rs

1//! Stepping through what a `for` loops over, shared by loop statements and `for` regions.
2
3use std::rc::Rc;
4
5use nifty_check::hir::{Iterable, Loop};
6use nifty_syntax::Span;
7
8use super::{Frame, Location, Machine, Result, Slot};
9use crate::value::Value;
10
11/// A loop's iterable, worked out once before the first time round.
12pub(crate) enum Walk {
13    /// The length is read each time round, so a loop body may change the list.
14    Place(Location, Span),
15    Items(Rc<Vec<Value>>),
16    Range {
17        start: i64,
18        end: i64,
19    },
20}
21
22impl Machine {
23    pub(crate) fn walk(&self, iterable: &Iterable, frame: &mut Frame) -> Result<Walk> {
24        Ok(match iterable {
25            Iterable::Place(place) => Walk::Place(self.locate(place, frame)?, place.span),
26            Iterable::Value(list) => match self.eval(list, frame)? {
27                Value::List(items) => Walk::Items(items),
28                other => panic!("the checker only allows looping over lists, not {other:?}"),
29            },
30            Iterable::Range { start, end } => Walk::Range {
31                start: self.eval(start, frame)?.as_int(),
32                end: self.eval(end, frame)?.as_int(),
33            },
34        })
35    }
36
37    /// Puts the loop's variables for the round at `position` in `frame`, or returns
38    /// `false` once the walk is over.
39    pub(crate) fn enter_round(
40        &self,
41        head: &Loop,
42        walk: &Walk,
43        (position, index): (usize, i64),
44        frame: &mut Frame,
45    ) -> Result<bool> {
46        let item = match walk {
47            Walk::Place(location, span) => {
48                if position >= list_len(&self.read(location, frame, *span)?) {
49                    return Ok(false);
50                }
51                Slot::Alias(location.item(position))
52            }
53            Walk::Items(items) => match items.get(position) {
54                Some(item) => Slot::Value(item.clone()),
55                None => return Ok(false),
56            },
57            Walk::Range { start, end } => match start.checked_add(index) {
58                Some(count) if count < *end => Slot::Value(Value::Int(count)),
59                _ => return Ok(false),
60            },
61        };
62        frame[head.item.0] = item;
63        if let Some(local) = head.index {
64            frame[local.0] = Slot::Value(Value::Int(index));
65        }
66        Ok(true)
67    }
68}
69
70/// Each round's list position, and the same position as an `int` for the loop's index.
71pub(crate) fn rounds() -> impl Iterator<Item = (usize, i64)> {
72    (0..).zip(0..)
73}
74
75fn list_len(list: &Value) -> usize {
76    match list {
77        Value::List(items) => items.len(),
78        other => panic!("the checker only allows looping over lists, not {other:?}"),
79    }
80}