Skip to main content

cl_interpret/
place.rs

1//! Place-expressions in the interpreter
2use crate::{
3    convalue::ConValue,
4    env::Environment,
5    error::{Error, ErrorKind, IResult},
6    interpret::Interpret,
7};
8use cl_ast::{
9    At, Expr, Op,
10    types::{Literal, Symbol},
11};
12use std::{fmt::Display, mem::take};
13
14#[derive(Clone, Debug, Default)]
15pub struct Place {
16    place: usize,
17    projections: Vec<Projection>,
18}
19
20#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
21pub enum Projection {
22    Deref,
23    Index(usize, bool),
24    DotSym(Symbol),
25    DotIdx(usize),
26}
27
28impl Place {
29    /// Creates a new [Place] [Projection] expression from an [Expr].
30    ///
31    /// If a place-projection can't be constructed, returns [Error::NotPlace].
32    pub fn new(value: &Expr, env: &mut Environment) -> IResult<Self> {
33        // base case
34        if let Expr::Id(names) = value
35            && let &[name] = names.parts.as_slice()
36        {
37            return Ok(Self { place: env.id_of(name)?, projections: vec![] });
38        };
39        let Expr::Op(op, exprs) = value else {
40            return Err(Error::NotPlace());
41        };
42
43        match (op, exprs.as_slice()) {
44            (Op::As, exprs) => Err(Error::NotPlace()),
45            (Op::Block | Op::Group | Op::MetaInner | Op::MetaOuter | Op::Pub, [expr]) => {
46                Self::new(expr.value(), env)
47            }
48            (Op::Index, [place, idx]) => {
49                let place = Self::new(place.value(), env)?;
50                // this may be surprising, as it evaluates an arbitrary expression!!!
51                match idx.interpret(env)? {
52                    ConValue::Int(idx @ ..0) => Ok(place.index(-idx as usize, true)),
53                    ConValue::Int(idx @ 0..) => Ok(place.index(idx as usize, false)),
54                    err => Err(Error::TypeError("int", err.typename()))?,
55                }
56            }
57            (Op::Dot, [place, At(Expr::Lit(Literal::Int(idx, _)), _)]) => {
58                Ok(Self::new(place.value(), env)?.dot_idx(*idx as _))
59            }
60            (Op::Dot, [place, At(Expr::Id(path), _)]) if path.parts.len() == 1 => {
61                Ok(Self::new(place.value(), env)?.dot_sym(path.parts[0]))
62            }
63            (Op::Deref, [place]) => Ok(Self::new(place.value(), env)?.deref()),
64            _ => Err(Error::NotPlace()),
65        }
66    }
67
68    /// Creates a new [Place] if this [Expr] is a place expression,
69    /// otherwise allocates a new temporary and returns a Place-reference to it.
70    pub fn new_or_temporary(value: &Expr, env: &mut Environment) -> IResult<Self> {
71        let new = Self::new(value, env);
72        if let Err(Error { kind: ErrorKind::NotPlace, .. }) = new {
73            let value = value.interpret(env)?;
74            return Ok(Self::from_index(env.stack_alloc(value)?));
75        }
76        new
77    }
78
79    pub fn from_index(index: usize) -> Self {
80        Self { place: index, projections: vec![] }
81    }
82
83    pub fn with(mut self, projection: Projection) -> Self {
84        self.projections.push(projection);
85        self
86    }
87    pub fn deref(mut self) -> Self {
88        self.with(Projection::Deref)
89    }
90    pub fn index(mut self, idx: usize, from_end: bool) -> Self {
91        self.with(Projection::Index(idx, from_end))
92    }
93    pub fn dot_idx(mut self, idx: usize) -> Self {
94        self.with(Projection::DotIdx(idx))
95    }
96    pub fn dot_sym(mut self, sym: impl Into<Symbol>) -> Self {
97        self.with(Projection::DotSym(sym.into()))
98    }
99
100    pub fn get_mut<'v, 'e: 'v>(&self, env: &'e mut Environment) -> IResult<&'v mut ConValue> {
101        let Self { place, projections } = self;
102        let env = env as *mut Environment;
103
104        let mut place = unsafe { &mut *env }
105            .get_id_mut(*place)
106            .ok_or(Error::StackOverflow(*place as _))?;
107
108        for projection in projections {
109            place = match (place, projection) {
110                // SAFETY: fuck it, we ball. Polonius would totally have our back here
111                (ConValue::Ref(place), Projection::Deref) => {
112                    place.get_mut(unsafe { &mut *(env) })?
113                }
114                (ConValue::Ref(place), projection) => place
115                    .clone()
116                    .with(*projection)
117                    .get_mut(unsafe { &mut *env })?,
118                (value, Projection::Deref) => value,
119                (ConValue::Array(arr), &Projection::Index(idx, from_end)) => {
120                    let len = arr.len();
121                    let idx = if from_end { len - idx } else { idx };
122                    arr.get_mut(idx).ok_or_else(|| Error::OobIndex(idx, len))?
123                }
124                (ConValue::Slice(place, len), &Projection::Index(idx, from_end)) => {
125                    if idx >= *len {
126                        Err(Error::OobIndex(idx, *len))?
127                    };
128                    // SAFETY: see above
129                    place
130                        .clone()
131                        .index(idx, from_end)
132                        .get_mut(unsafe { &mut *env })?
133                }
134                (place, Projection::Index(_, _)) => Err(Error::NotIndexable())?,
135                (ConValue::Struct(_, values), Projection::DotSym(sym)) => {
136                    values.get_mut(sym).ok_or(Error::NotDefined(*sym))?
137                }
138                (place, Projection::DotSym(name)) => {
139                    Err(Error::TypeError(name.to_ref(), place.typename()))?
140                }
141                (ConValue::Tuple(values), &Projection::DotIdx(idx)) => {
142                    let len = values.len();
143                    values.get_mut(idx).ok_or(Error::OobIndex(idx, len))?
144                }
145                (ConValue::TupleStruct(_, values), &Projection::DotIdx(idx)) => {
146                    let len = values.len();
147                    values.get_mut(idx).ok_or(Error::OobIndex(idx, len))?
148                }
149                (place, Projection::DotIdx(_)) => todo!(),
150                (value, place) => Err(Error::Panic(format!(
151                    "Failed to match {value} against {place:?}"
152                )))?,
153            }
154        }
155
156        Ok(place)
157    }
158
159    pub fn get<'e>(&self, env: &'e Environment) -> IResult<&'e ConValue> {
160        let Self { place, projections } = self;
161
162        let mut place = env
163            .get_id(*place)
164            .ok_or(Error::StackOverflow(*place as _))?;
165
166        for projection in projections {
167            place = match (place, projection) {
168                (ConValue::Ref(place), Projection::Deref) => place.get(env)?,
169                (ConValue::Ref(place), projection) => place.clone().with(*projection).get(env)?,
170                (ConValue::Array(arr), &Projection::Index(idx, from_end)) => {
171                    let idx = if from_end { arr.len() - idx } else { idx };
172                    arr.get(idx).ok_or(Error::OobIndex(idx, arr.len()))?
173                }
174                (place, Projection::Index(_, _)) => todo!("Index {self}"),
175                (ConValue::Struct(_, values), Projection::DotSym(sym)) => {
176                    values.get(sym).ok_or(Error::NotDefined(*sym))?
177                }
178                (place, Projection::DotSym(interned)) => todo!(),
179                (ConValue::Tuple(values), &Projection::DotIdx(idx)) => {
180                    values.get(idx).ok_or(Error::OobIndex(idx, values.len()))?
181                }
182                (ConValue::TupleStruct(_, values), &Projection::DotIdx(idx)) => {
183                    values.get(idx).ok_or(Error::OobIndex(idx, values.len()))?
184                }
185                (place, Projection::DotIdx(_)) => todo!(),
186                _ => Err(Error::MatchNonexhaustive())?,
187            }
188        }
189
190        Ok(place)
191    }
192}
193
194impl Display for Place {
195    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
196        let Self { place, projections } = self;
197        fn format_inner(
198            place: usize,
199            projections: &[Projection],
200            f: &mut std::fmt::Formatter<'_>,
201        ) -> std::fmt::Result {
202            match projections {
203                [] => place.fmt(f),
204                [first @ .., Projection::Deref] => {
205                    "*".fmt(f)?;
206                    format_inner(place, first, f)
207                }
208                [Projection::Index(idx, from_end), rest @ ..] => {
209                    format_inner(place, rest, f)?;
210                    write!(f, "[{}{idx}]", if *from_end { "-" } else { "" })
211                }
212                [Projection::DotIdx(idx), rest @ ..] => {
213                    format_inner(place, rest, f)?;
214                    write!(f, ".{idx}")
215                }
216                [Projection::DotSym(idx), rest @ ..] => {
217                    format_inner(place, rest, f)?;
218                    write!(f, ".{idx}")
219                }
220                _ => unreachable!("{projections:?}"),
221            }
222        }
223        format_inner(*place, projections, f)
224    }
225}