/ cab / runtime / value / thunk.rs
thunk.rs
  1  #![allow(dead_code)]
  2  
  3  use std::{
  4     mem,
  5     sync::Arc,
  6  };
  7  
  8  use dup::{
  9     Dupe,
 10     OptionDupedExt as _,
 11  };
 12  use tokio::sync::RwLock;
 13  
 14  use crate::{
 15     Code,
 16     Operation,
 17     Scopes,
 18     State,
 19     Value,
 20     value,
 21  };
 22  
 23  const EXPECT_SCOPE: &str = "must have at least once scope";
 24  
 25  #[derive(Clone, Dupe)]
 26  enum ThunkInner {
 27     SuspendedNative {
 28        location:  value::Location,
 29        code:      Arc<dyn Fn() -> Value + Send + Sync>,
 30        is_lambda: bool,
 31        argument:  Option<Value>,
 32     },
 33  
 34     Suspended {
 35        location:  value::Location,
 36        code:      Arc<Code>,
 37        is_lambda: bool,
 38        argument:  Option<Value>,
 39        scopes:    Scopes,
 40     },
 41  
 42     Evaluated {
 43        scopagate: Option<Scopes>,
 44        value:     Value,
 45     },
 46  }
 47  
 48  impl ThunkInner {
 49     thread_local! {
 50        static NOT_BOOLEAN: value::Error = value::Error::new(value::string::new!("expected boolean, got something else"));
 51  
 52        static NOT_LAMBDA: value::Error = value::Error::new(value::string::new!("expected lambda, got something else"));
 53  
 54        static NOT_ATTRIBUTES: value::Error = value::Error::new(value::string::new!("expected attributes, got something else"));
 55     }
 56  
 57     fn black_hole(location: value::Location) -> Self {
 58        ThunkInner::SuspendedNative {
 59           location,
 60           code: Arc::new(|| {
 61              Value::from(Arc::new(value::Error::new(value::string::new!(
 62                 "infinite recursion encountered"
 63              ))))
 64           }),
 65           is_lambda: false,
 66           argument: None,
 67        }
 68     }
 69  }
 70  
 71  #[derive(Clone, Dupe)]
 72  pub struct Thunk(Arc<RwLock<ThunkInner>>);
 73  
 74  #[bon::bon]
 75  impl Thunk {
 76     #[must_use]
 77     pub fn suspended_native(
 78        code: impl Fn() -> Value + Send + Sync + 'static,
 79        location: value::Location,
 80        is_lambda: bool,
 81     ) -> Self {
 82        Self(Arc::new(RwLock::new(ThunkInner::SuspendedNative {
 83           location,
 84           code: Arc::new(code),
 85           is_lambda,
 86           argument: None,
 87        })))
 88     }
 89  
 90     #[must_use]
 91     #[builder(finish_fn(name = "location"))]
 92     pub fn suspended(
 93        #[builder(start_fn)] code: Arc<Code>,
 94        #[builder(finish_fn)] location: value::Location,
 95        is_lambda: bool,
 96        scopes: Scopes,
 97     ) -> Self {
 98        Self(Arc::new(RwLock::new(ThunkInner::Suspended {
 99           location,
100           code,
101           is_lambda,
102           argument: None,
103           scopes,
104        })))
105     }
106  
107     pub async fn argument(&self, arg: Value) -> Self {
108        let mut inner = self.0.read().await.dupe();
109  
110        match inner {
111           ThunkInner::SuspendedNative {
112              ref mut argument, ..
113           } => *argument = Some(arg),
114           ThunkInner::Suspended {
115              ref mut argument, ..
116           } => *argument = Some(arg),
117           ThunkInner::Evaluated { .. } => panic!("cannot add argument to evaluated thunk"),
118        }
119  
120        Thunk(Arc::new(RwLock::new(inner)))
121     }
122  
123     pub async fn get(&self) -> Option<(Option<Scopes>, Value)> {
124        if let ThunkInner::Evaluated {
125           ref scopagate,
126           ref value,
127        } = *self.0.read().await
128        {
129           Some((scopagate.dupe(), value.dupe()))
130        } else {
131           None
132        }
133     }
134  
135     pub async fn force(&self, state: &State) {
136        let this = mem::replace(&mut *self.0.write().await, ThunkInner::Evaluated {
137           scopagate: None,
138           // FIXME
139           value:     Value::Nil(value::Nil),
140        });
141  
142        let new = match this {
143           evaluated @ ThunkInner::Evaluated { .. } => evaluated.dupe(),
144  
145           ThunkInner::SuspendedNative {
146              location,
147              code,
148              is_lambda: _is_lambda,
149              argument: _argument,
150           } => {
151              *self.0.write().await = ThunkInner::black_hole(location);
152  
153              ThunkInner::Evaluated {
154                 scopagate: None,
155                 value:     code(),
156              }
157           },
158  
159           ThunkInner::Suspended {
160              location,
161              code,
162              is_lambda,
163              argument,
164              mut scopes,
165           } => {
166              if is_lambda && argument.is_none() {
167                 return; // FIXME: Makes forced lambdas be nil. Also god damn, this code sucks ass.
168              }
169  
170              *self.0.write().await = ThunkInner::black_hole(location.dupe());
171  
172              let mut stack = argument.into_iter().collect::<Vec<_>>();
173  
174              let items = &mut code.iter().peekable();
175              while let Some((index, item)) = items.next() {
176                 let operation = *item.as_operation().expect("next item must be an operation");
177  
178                 match operation {
179                    Operation::Push => {
180                       let value_index = items
181                          .next()
182                          .expect("push must not be the last item")
183                          .1
184                          .as_argument()
185                          .expect("push must have an argument")
186                          .as_value_index()
187                          .expect("push argument must be a value index");
188  
189                       stack.push(match &code[value_index] {
190                          &Value::Code {
191                             ref code,
192                             is_lambda,
193                          } => {
194                             Value::from(
195                                Thunk::suspended(code.dupe())
196                                   .scopes(scopes.dupe())
197                                   .is_lambda(is_lambda)
198                                   // FIXME: .location(code.read_operation(index).0),
199                                   .location(location.dupe()),
200                             )
201                          },
202  
203                          other => other.dupe(),
204                       });
205                    },
206                    Operation::Pop => {
207                       stack
208                          .pop()
209                          .expect("pop operation must not be called on empty stack");
210                    },
211                    Operation::Swap => {
212                       let &mut [.., ref mut x, ref mut y] = &mut *stack else {
213                          unreachable!("swap must be called on stack of length 2 or higher");
214                       };
215  
216                       mem::swap(x, y);
217                    },
218                    operation @ (Operation::Jump | Operation::JumpIf | Operation::JumpIfError) => {
219                       let target_index = items
220                          .next()
221                          .expect("jump must not be the last item")
222                          .1
223                          .as_argument()
224                          .expect("jump must have an argument")
225                          .as_byte_index()
226                          .expect("jump argument must be a byte index");
227  
228                       match operation {
229                          Operation::Jump => {},
230                          Operation::JumpIf => {
231                             let value = stack.last_mut().expect(
232                                "jump-if and jump-if-error must be called on stack with at least \
233                                 one item",
234                             );
235  
236                             let &mut Value::Boolean(value) = value else {
237                                *value = Value::from(Arc::from(
238                                   ThunkInner::NOT_BOOLEAN
239                                      .with(Dupe::dupe)
240                                      .append_trace(code.read_operation(index).0),
241                                ));
242                                continue;
243                             };
244  
245                             if !value {
246                                continue;
247                             }
248                          },
249                          Operation::JumpIfError => {
250                             let value = stack.last_mut().expect(
251                                "jump-if and jump-if-error must be called on stack with at least \
252                                 one item",
253                             );
254  
255                             let &mut Value::Error(ref error) = value else {
256                                continue;
257                             };
258  
259                             *value = Value::from(Arc::new(
260                                error.append_trace(code.read_operation(index).0),
261                             ));
262                          },
263                          _ => unreachable!(),
264                       }
265  
266                       while items
267                          .next_if(|&(next_index, _)| next_index != target_index)
268                          .is_some()
269                       {}
270                    },
271                    Operation::Force => {
272                       let mut value = stack
273                          .pop()
274                          .expect("force must not be called on an empty stack");
275  
276                       while let Value::Thunk(thunk) = value {
277                          Box::pin(thunk.force(state)).await;
278  
279                          let (scope_new, value_new) = thunk
280                             .get()
281                             .await
282                             .expect("thunk must contain value after forcing");
283  
284                          value = value_new;
285                          scopes = scope_new.unwrap_or(scopes);
286                       }
287  
288                       stack.push(value);
289                    },
290                    Operation::ScopeStart => {
291                       scopes = scopes.push_front(value::attributes::new! {});
292                    },
293                    Operation::ScopeEnd => {
294                       scopes = scopes
295                          .drop_first()
296                          .expect("scope-end must not be called with no scopes");
297                    },
298                    Operation::ScopePush => {
299                       stack.push(Value::from(scopes.first().expect(EXPECT_SCOPE).dupe()));
300                    },
301                    Operation::ScopeSwap => {
302                       let value = stack
303                          .last_mut()
304                          .expect("scope-swap must not be called on a empty stack");
305  
306                       let &mut Value::Attributes(ref mut value) = value else {
307                          *value = Value::from(Arc::from(
308                             ThunkInner::NOT_ATTRIBUTES
309                                .with(Dupe::dupe)
310                                .append_trace(code.read_operation(index).0),
311                          ));
312                          continue;
313                       };
314  
315                       let mut scope = scopes.first().expect(EXPECT_SCOPE).dupe();
316                       mem::swap(&mut scope, value);
317  
318                       scopes = scopes.drop_first().expect(EXPECT_SCOPE).push_front(scope);
319                    },
320                    Operation::Interpolate => todo!(),
321                    Operation::Resolve => {
322                       let reference = stack
323                          .last_mut()
324                          .expect("resolve must not be called on an empty stack");
325  
326                       let &mut Value::Reference(ref identifier) = reference else {
327                          unreachable!("resolve must be called on an identifier");
328                       };
329  
330                       let value = scopes
331                          .iter()
332                          .find_map(|scope| scope.get(identifier))
333                          .duped()
334                          .unwrap_or_else(|| {
335                             Value::from(Arc::new(
336                                value::Error::new(value::SString::from(&*format!(
337                                   "undefined value: '{identifier}'",
338                                   identifier = &**identifier,
339                                )))
340                                .append_trace(code.read_operation(index).0),
341                             ))
342                          });
343  
344                       *reference = value;
345                    },
346                    Operation::AssertBoolean => {
347                       let value = stack
348                          .last_mut()
349                          .expect("assert-boolean must not be called on an empty stack");
350  
351                       let &mut Value::Boolean(_) = value else {
352                          *value = Value::from(Arc::from(
353                             ThunkInner::NOT_BOOLEAN
354                                .with(Dupe::dupe)
355                                .append_trace(code.read_operation(index).0),
356                          ));
357                          continue;
358                       };
359                    },
360                    Operation::Construct => {
361                       let tail = stack
362                          .pop()
363                          .expect("construct must be called on a stack with 2 items or more");
364                       let head = stack
365                          .pop()
366                          .expect("construct must be called on a stack with 2 items or more");
367  
368                       stack.push(Value::from(Arc::new(value::Cons(head, tail))));
369                    },
370                    Operation::Call => {
371                       let argument = stack.pop().expect("call must not be called on empty stack");
372  
373                       let Value::Thunk(thunk) =
374                          stack.pop().expect("call must not be called on empty stack")
375                       else {
376                          stack.push(Value::from(Arc::from(
377                             ThunkInner::NOT_LAMBDA
378                                .with(Dupe::dupe)
379                                .append_trace(code.read_operation(index).0),
380                          )));
381                          continue;
382                       };
383  
384                       let thunk = thunk.argument(argument).await;
385  
386                       stack.push(Value::from(thunk));
387                    },
388                    Operation::Equal => {
389                       let right = stack
390                          .pop()
391                          .expect("equal must be called on a stack with 2 items or more");
392                       let left = stack
393                          .pop()
394                          .expect("equal must be called on a stack with 2 items or more");
395  
396                       // TODO: Not sure about the design here.
397                       let (equal, binds) = Value::equals(&left, &right);
398  
399                       stack.push(Value::from(equal));
400                       scopes = scopes
401                          .drop_first()
402                          .expect("equal must be called with a scope")
403                          .push_front(
404                             scopes
405                                .first()
406                                .expect("equal must be called with a scope")
407                                .merge(&binds),
408                          );
409                    },
410                    Operation::All => todo!(),
411                    Operation::Any => todo!(),
412                 }
413              }
414  
415              let len = stack.len();
416              let Ok([value]) = <[_; 1]>::try_from(stack) else {
417                 unreachable!("stack must have exactly one item left, has {len}");
418              };
419  
420              ThunkInner::Evaluated {
421                 scopagate: Some(scopes),
422                 value,
423              }
424           },
425        };
426  
427        *self.0.write().await = new;
428     }
429  }