Skip to main content

nifty_interp/machine/
place.rs

1//! Reading and changing places: state, locals, and the fields and list items inside them.
2
3use std::rc::Rc;
4
5use nifty_check::hir::{LocalId, Place, PlaceRoot, PlaceStep, StateId};
6use nifty_download::{StatePlace, StateReader};
7use nifty_runtime::Step;
8use nifty_syntax::Span;
9
10use super::{Frame, Machine, Result, Slot, item, out_of_range, position};
11use crate::value::Value;
12
13/// Where a value lives, with list indexes already evaluated.
14#[derive(Clone, Debug, PartialEq)]
15pub(crate) struct Location {
16    root: Root,
17    path: Vec<Step>,
18}
19
20#[derive(Clone, Copy, Debug, PartialEq)]
21enum Root {
22    State(StateId),
23    Local(LocalId),
24}
25
26impl Location {
27    /// Whether this is part of the app's state, which outlives any one frame.
28    pub(crate) fn is_in_state(&self) -> bool {
29        matches!(self.root, Root::State(_))
30    }
31
32    /// The location of the list item at `index` inside this location.
33    pub(crate) fn item(&self, index: usize) -> Self {
34        let mut path = self.path.clone();
35        path.push(Step::Index(index));
36        Self {
37            root: self.root,
38            path,
39        }
40    }
41}
42
43impl StatePlace for Location {
44    fn steps(&self) -> &[Step] {
45        &self.path
46    }
47
48    fn with_steps(&self, steps: Vec<Step>) -> Self {
49        Self {
50            root: self.root,
51            path: steps,
52        }
53    }
54
55    fn same_root(&self, other: &Self) -> bool {
56        self.root == other.root
57    }
58}
59
60/// Reads state without a frame, for finding where a download's id went.
61impl StateReader for Machine {
62    type Place = Location;
63
64    fn list_len(&self, place: &Location) -> Option<usize> {
65        match self.read_in_state(place)? {
66            Value::List(items) => Some(items.len()),
67            _ => None,
68        }
69    }
70
71    fn holds_text(&self, place: &Location, text: &str) -> bool {
72        matches!(self.read_in_state(place), Some(Value::String(held)) if *held == *text)
73    }
74}
75
76impl Machine {
77    /// The value at `place` in state; `None` when it is a variable's, or no longer exists.
78    fn read_in_state(&self, place: &Location) -> Option<Value> {
79        let Root::State(state) = place.root else {
80            return None;
81        };
82        walk(&self.states.borrow()[state.0], &place.path, Span::default()).ok()
83    }
84
85    /// Resolves `place` against `frame`, following `for` variables to the item they alias.
86    pub(crate) fn locate(&self, place: &Place, frame: &mut Frame) -> Result<Location> {
87        let mut location = match place.root {
88            PlaceRoot::State(state) => Location {
89                root: Root::State(state),
90                path: Vec::new(),
91            },
92            PlaceRoot::Local(local) => match &frame[local.0] {
93                Slot::Alias(location) => location.clone(),
94                Slot::Value(_) => Location {
95                    root: Root::Local(local),
96                    path: Vec::new(),
97                },
98                Slot::Empty => panic!("the checker only allows using locals after they are set"),
99            },
100        };
101        for step in &place.path {
102            location.path.push(match step {
103                PlaceStep::Field(index) => Step::Field(*index),
104                PlaceStep::Index(index) => {
105                    Step::Index(position(self.eval(index, frame)?.as_int(), place.span)?)
106                }
107            });
108        }
109        Ok(location)
110    }
111
112    pub(crate) fn read(&self, location: &Location, frame: &Frame, span: Span) -> Result<Value> {
113        match location.root {
114            Root::State(state) => walk(&self.states.borrow()[state.0], &location.path, span),
115            Root::Local(local) => match &frame[local.0] {
116                Slot::Value(value) => walk(value, &location.path, span),
117                _ => panic!("a local location always roots at a stored value"),
118            },
119        }
120    }
121
122    pub(crate) fn write(
123        &self,
124        location: &Location,
125        frame: &mut Frame,
126        value: Value,
127        span: Span,
128    ) -> Result<()> {
129        self.modify(location, frame, span, |target| {
130            *target = value;
131            Ok(())
132        })
133    }
134
135    /// Writes to a location in state, which needs no frame.
136    ///
137    /// # Panics
138    /// When the location is a variable's; check [`Location::is_in_state`] first.
139    pub(crate) fn write_state(&self, location: &Location, value: Value, span: Span) -> Result<()> {
140        let Root::State(state) = location.root else {
141            panic!("only a location in state can be written without its frame");
142        };
143        self.modify_state(state, &location.path, span, |target| {
144            *target = value;
145            Ok(())
146        })
147    }
148
149    /// Changes the value at `location` in place, marking its state changed.
150    pub(crate) fn modify<R>(
151        &self,
152        location: &Location,
153        frame: &mut Frame,
154        span: Span,
155        change: impl FnOnce(&mut Value) -> Result<R>,
156    ) -> Result<R> {
157        match location.root {
158            Root::State(state) => self.modify_state(state, &location.path, span, change),
159            Root::Local(local) => match &mut frame[local.0] {
160                Slot::Value(value) => change(walk_mut(value, &location.path, span)?),
161                _ => panic!("a local location always roots at a stored value"),
162            },
163        }
164    }
165
166    fn modify_state<R>(
167        &self,
168        state: StateId,
169        path: &[Step],
170        span: Span,
171        change: impl FnOnce(&mut Value) -> Result<R>,
172    ) -> Result<R> {
173        let result = {
174            let mut states = self.states.borrow_mut();
175            change(walk_mut(&mut states[state.0], path, span)?)?
176        };
177        self.changed.borrow_mut().insert(state);
178        Ok(result)
179    }
180}
181
182fn walk(value: &Value, path: &[Step], span: Span) -> Result<Value> {
183    let Some((step, rest)) = path.split_first() else {
184        return Ok(value.clone());
185    };
186    match (step, value) {
187        (Step::Field(index), Value::Struct(fields)) => walk(&fields[*index], rest, span),
188        (Step::Index(position), list) => walk(item(list, *position, span)?, rest, span),
189        (Step::Field(_), other) => panic!("checked code read a field of {other:?}"),
190    }
191}
192
193fn walk_mut<'v>(value: &'v mut Value, path: &[Step], span: Span) -> Result<&'v mut Value> {
194    let Some((step, rest)) = path.split_first() else {
195        return Ok(value);
196    };
197    match (step, value) {
198        (Step::Field(index), Value::Struct(fields)) => {
199            walk_mut(&mut Rc::make_mut(fields)[*index], rest, span)
200        }
201        (Step::Index(position), Value::List(items)) => {
202            let len = items.len();
203            let item = Rc::make_mut(items)
204                .get_mut(*position)
205                .ok_or_else(|| out_of_range(*position, len, span))?;
206            walk_mut(item, rest, span)
207        }
208        (_, other) => panic!("checked code changed part of {other:?}"),
209    }
210}