mirror of
https://github.com/tweag/nickel.git
synced 2024-11-10 10:46:49 +03:00
commit
a832ffed90
12
src/error.rs
12
src/error.rs
@ -68,6 +68,8 @@ pub enum EvalError {
|
||||
),
|
||||
/// An unbound identifier was referenced.
|
||||
UnboundIdentifier(Ident, Option<RawSpan>),
|
||||
/// A thunk was entered during its own update.
|
||||
InfiniteRecursion(CallStack, Option<RawSpan>),
|
||||
/// An unexpected internal error.
|
||||
InternalError(String, Option<RawSpan>),
|
||||
/// Errors occurring rarely enough to not deserve a dedicated variant.
|
||||
@ -979,6 +981,16 @@ impl ToDiagnostic<FileId> for EvalError {
|
||||
.with_message("Unbound identifier")
|
||||
.with_labels(vec![primary_alt(span_opt, ident.clone(), files)
|
||||
.with_message("this identifier is unbound")])],
|
||||
EvalError::InfiniteRecursion(_call_stack, span_opt) => {
|
||||
let labels = span_opt
|
||||
.as_ref()
|
||||
.map(|span| vec![primary(span).with_message("recursive reference")])
|
||||
.unwrap_or_else(Vec::new);
|
||||
|
||||
vec![Diagnostic::error()
|
||||
.with_message("infinite recursion")
|
||||
.with_labels(labels)]
|
||||
}
|
||||
EvalError::Other(msg, span_opt) => {
|
||||
let labels = span_opt
|
||||
.as_ref()
|
||||
|
258
src/eval.rs
258
src/eval.rs
@ -95,12 +95,167 @@ use crate::operation::{continuate_operation, OperationCont};
|
||||
use crate::position::RawSpan;
|
||||
use crate::stack::Stack;
|
||||
use crate::term::{make as mk_term, MetaValue, RichTerm, StrChunk, Term, UnaryOp};
|
||||
use std::cell::RefCell;
|
||||
use std::cell::{Ref, RefCell, RefMut};
|
||||
use std::collections::HashMap;
|
||||
use std::rc::{Rc, Weak};
|
||||
|
||||
/// The state of a thunk.
|
||||
///
|
||||
/// When created, a thunk is flagged as suspended. When accessed for the first time, a corresponding
|
||||
/// [`ThunkUpdateFrame`](./struct.ThunkUpdateFrame.html) is pushed on the stack and the thunk is
|
||||
/// flagged as black-hole. This prevents direct infinite recursions, since if a thunk is
|
||||
/// re-accessed while still in a black-hole state, we are sure that the evaluation will loop, and
|
||||
/// we can thus error out before overflowing the stack or looping forever. Finally, once the
|
||||
/// content of a thunk has been evaluated, the thunk is updated with the new value and flagged as
|
||||
/// evaluated, so that future accesses won't even push an update frame on the stack.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
pub enum ThunkState {
|
||||
Blackholed,
|
||||
Suspended,
|
||||
Evaluated,
|
||||
}
|
||||
|
||||
/// The mutable data stored inside a thunk.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct ThunkData {
|
||||
closure: Closure,
|
||||
state: ThunkState,
|
||||
}
|
||||
|
||||
impl ThunkData {
|
||||
pub fn new(closure: Closure) -> Self {
|
||||
ThunkData {
|
||||
closure,
|
||||
state: ThunkState::Suspended,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A thunk.
|
||||
///
|
||||
/// A thunk is a shared suspended computation. It is the primary device for the implementation of
|
||||
/// lazy evaluation.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Thunk {
|
||||
data: Rc<RefCell<ThunkData>>,
|
||||
ident_kind: IdentKind,
|
||||
}
|
||||
|
||||
/// A black-holed thunk was accessed, which would lead to infinite recursion.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct BlackholedError;
|
||||
|
||||
impl Thunk {
|
||||
pub fn new(closure: Closure, ident_kind: IdentKind) -> Self {
|
||||
Thunk {
|
||||
data: Rc::new(RefCell::new(ThunkData::new(closure))),
|
||||
ident_kind,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn state(&self) -> ThunkState {
|
||||
self.data.borrow().state
|
||||
}
|
||||
|
||||
/// Set the state to evaluated.
|
||||
pub fn set_evaluated(&mut self) {
|
||||
self.data.borrow_mut().state = ThunkState::Evaluated;
|
||||
}
|
||||
|
||||
/// Generate an update frame from this thunk and set the state to `Blackholed`. Return an
|
||||
/// error if the thunk was already black-holed.
|
||||
pub fn to_update_frame(&mut self) -> Result<ThunkUpdateFrame, BlackholedError> {
|
||||
if self.data.borrow().state == ThunkState::Blackholed {
|
||||
return Err(BlackholedError);
|
||||
}
|
||||
|
||||
self.data.borrow_mut().state = ThunkState::Blackholed;
|
||||
|
||||
Ok(ThunkUpdateFrame {
|
||||
data: Rc::downgrade(&self.data),
|
||||
ident_kind: self.ident_kind,
|
||||
})
|
||||
}
|
||||
|
||||
/// Immutably borrow the inner closure. Panic if there is another active mutable borrow.
|
||||
pub fn borrow(&self) -> Ref<'_, Closure> {
|
||||
let (closure, _) = Ref::map_split(self.data.borrow(), |data| {
|
||||
let ThunkData {
|
||||
ref closure,
|
||||
ref state,
|
||||
} = data;
|
||||
(closure, state)
|
||||
});
|
||||
|
||||
closure
|
||||
}
|
||||
|
||||
/// Mutably borrow the inner closure. Panic if there is any other active borrow.
|
||||
pub fn borrow_mut<'a>(&'a mut self) -> RefMut<'_, Closure> {
|
||||
let (closure, _) = RefMut::map_split(self.data.borrow_mut(), |data| {
|
||||
let ThunkData {
|
||||
ref mut closure,
|
||||
ref mut state,
|
||||
} = data;
|
||||
(closure, state)
|
||||
});
|
||||
|
||||
closure
|
||||
}
|
||||
|
||||
/// Get an owned clone of the inner closure.
|
||||
pub fn get_owned(&self) -> Closure {
|
||||
self.data.borrow().closure.clone()
|
||||
}
|
||||
|
||||
pub fn ident_kind(&self) -> IdentKind {
|
||||
self.ident_kind
|
||||
}
|
||||
|
||||
/// Consume the thunk and return an owned closure. Avoid cloning if this thunk is the only
|
||||
/// reference to the inner closure.
|
||||
pub fn into_closure(self) -> Closure {
|
||||
match Rc::try_unwrap(self.data) {
|
||||
Ok(inner) => inner.into_inner().closure,
|
||||
Err(rc) => rc.borrow().clone().closure,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A thunk update frame.
|
||||
///
|
||||
/// A thunk update frame is put on the stack whenever a variable is entered, such that once this
|
||||
/// variable is evaluated, the corresponding thunk can be updated. It is similar to a thunk but it
|
||||
/// holds a weak reference to the inner closure, to avoid unnecessarily keeping the underlying
|
||||
/// closure alive.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ThunkUpdateFrame {
|
||||
data: Weak<RefCell<ThunkData>>,
|
||||
ident_kind: IdentKind,
|
||||
}
|
||||
|
||||
impl ThunkUpdateFrame {
|
||||
/// Update the corresponding thunk with a closure. Set the state to `Evaluated`
|
||||
///
|
||||
/// # Return
|
||||
///
|
||||
/// - `true` if the thunk was successfully updated
|
||||
/// - `false` if the corresponding closure has been dropped since
|
||||
pub fn update(self, closure: Closure) -> bool {
|
||||
if let Some(data) = Weak::upgrade(&self.data) {
|
||||
*data.borrow_mut() = ThunkData {
|
||||
closure,
|
||||
state: ThunkState::Evaluated,
|
||||
};
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An environment, which is a mapping from identifiers to closures.
|
||||
pub type Environment = HashMap<Ident, (Rc<RefCell<Closure>>, IdentKind)>;
|
||||
pub type Environment = HashMap<Ident, Thunk>;
|
||||
|
||||
/// A call stack, saving the history of function calls.
|
||||
///
|
||||
@ -118,7 +273,7 @@ pub enum StackElem {
|
||||
}
|
||||
|
||||
/// Kind of an identifier.
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
pub enum IdentKind {
|
||||
Let(),
|
||||
Lam(),
|
||||
@ -154,11 +309,10 @@ pub fn env_add_term(env: &mut Environment, rt: RichTerm) -> Result<(), EnvBuildE
|
||||
match *term {
|
||||
Term::Record(bindings) | Term::RecRecord(bindings) => {
|
||||
let ext = bindings.into_iter().map(|(id, t)| {
|
||||
let closure = Closure {
|
||||
body: t,
|
||||
env: HashMap::new(),
|
||||
};
|
||||
(id, (Rc::new(RefCell::new(closure)), IdentKind::Record()))
|
||||
(
|
||||
id,
|
||||
Thunk::new(Closure::atomic_closure(t), IdentKind::Record()),
|
||||
)
|
||||
});
|
||||
|
||||
env.extend(ext);
|
||||
@ -174,7 +328,7 @@ pub fn env_add(env: &mut Environment, id: Ident, rt: RichTerm, local_env: Enviro
|
||||
body: rt,
|
||||
env: local_env,
|
||||
};
|
||||
env.insert(id, (Rc::new(RefCell::new(closure)), IdentKind::Let()));
|
||||
env.insert(id, Thunk::new(closure, IdentKind::Let()));
|
||||
}
|
||||
|
||||
/// Determine if a thunk is worth being put on the stack for future update.
|
||||
@ -238,13 +392,12 @@ where
|
||||
let thunk = env
|
||||
.get(&id)
|
||||
.or_else(|| global_env.get(&id))
|
||||
.map(|(rc, _)| rc.clone())
|
||||
.expect("eval::eval_meta(): unexpected unbound identifier");
|
||||
|
||||
let Closure {
|
||||
body,
|
||||
env: local_env,
|
||||
} = thunk.borrow().clone();
|
||||
} = thunk.get_owned();
|
||||
|
||||
t = body;
|
||||
env = local_env;
|
||||
@ -304,29 +457,29 @@ where
|
||||
let term = *boxed_term;
|
||||
clos = match term {
|
||||
Term::Var(x) => {
|
||||
let (thunk, id_kind) = env
|
||||
let mut thunk = env
|
||||
.remove(&x)
|
||||
.or_else(|| {
|
||||
global_env
|
||||
.get(&x)
|
||||
.map(|(rc, id_kind)| (rc.clone(), id_kind.clone()))
|
||||
})
|
||||
.or_else(|| global_env.get(&x).map(Thunk::clone))
|
||||
.ok_or(EvalError::UnboundIdentifier(x.clone(), pos.clone()))?;
|
||||
std::mem::drop(env); // thunk may be a 1RC pointer
|
||||
if should_update(&thunk.borrow().body.term) {
|
||||
stack.push_thunk(Rc::downgrade(&thunk));
|
||||
}
|
||||
call_stack.push(StackElem::Var(id_kind, x, pos));
|
||||
match Rc::try_unwrap(thunk) {
|
||||
Ok(c) => {
|
||||
// thunk was the only strong ref to the closure
|
||||
c.into_inner()
|
||||
|
||||
if thunk.state() != ThunkState::Evaluated {
|
||||
if should_update(&thunk.borrow().body.term) {
|
||||
match thunk.to_update_frame() {
|
||||
Ok(thunk_upd) => stack.push_thunk(thunk_upd),
|
||||
Err(BlackholedError) => {
|
||||
return Err(EvalError::InfiniteRecursion(call_stack, pos))
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(rc) => {
|
||||
// We need to clone it, there are other strong refs
|
||||
rc.borrow().clone()
|
||||
// If the thunk isn't to be updated, directly set the evaluated flag.
|
||||
else {
|
||||
thunk.set_evaluated();
|
||||
}
|
||||
}
|
||||
|
||||
call_stack.push(StackElem::Var(thunk.ident_kind(), x, pos));
|
||||
thunk.into_closure()
|
||||
}
|
||||
Term::App(t1, t2) => {
|
||||
stack.push_arg(
|
||||
@ -339,11 +492,11 @@ where
|
||||
Closure { body: t1, env }
|
||||
}
|
||||
Term::Let(x, s, t) => {
|
||||
let thunk = Rc::new(RefCell::new(Closure {
|
||||
let closure = Closure {
|
||||
body: s,
|
||||
env: env.clone(),
|
||||
}));
|
||||
env.insert(x, (Rc::clone(&thunk), IdentKind::Let()));
|
||||
};
|
||||
env.insert(x, Thunk::new(closure, IdentKind::Let()));
|
||||
Closure { body: t, env }
|
||||
}
|
||||
Term::Switch(exp, cases, default) => {
|
||||
@ -441,24 +594,22 @@ where
|
||||
ts.iter()
|
||||
.try_fold(HashMap::new(), |mut rec_env, (id, rt)| match rt.as_ref() {
|
||||
&Term::Var(ref var_id) => {
|
||||
let (thunk, id_kind) = env.get(var_id).ok_or(
|
||||
EvalError::UnboundIdentifier(var_id.clone(), rt.pos.clone()),
|
||||
)?;
|
||||
rec_env.insert(id.clone(), (thunk.clone(), id_kind.clone()));
|
||||
let thunk = env.get(var_id).ok_or(EvalError::UnboundIdentifier(
|
||||
var_id.clone(),
|
||||
rt.pos.clone(),
|
||||
))?;
|
||||
rec_env.insert(id.clone(), thunk.clone());
|
||||
Ok(rec_env)
|
||||
}
|
||||
_ => {
|
||||
// If we are in this branch, the term must be a constant after the
|
||||
// share normal form transformation, hence it should not need an
|
||||
// environment, which is it is dropped.
|
||||
// environment, which is why it is dropped.
|
||||
let closure = Closure {
|
||||
body: rt.clone(),
|
||||
env: HashMap::new(),
|
||||
};
|
||||
rec_env.insert(
|
||||
id.clone(),
|
||||
(Rc::new(RefCell::new(closure)), IdentKind::Let()),
|
||||
);
|
||||
rec_env.insert(id.clone(), Thunk::new(closure, IdentKind::Let()));
|
||||
Ok(rec_env)
|
||||
}
|
||||
})?;
|
||||
@ -469,7 +620,7 @@ where
|
||||
Term::Var(var_id) => {
|
||||
// We already checked for unbound identifier in the previous fold, so this
|
||||
// get should always succeed.
|
||||
let (thunk, _) = env.get(&var_id).unwrap();
|
||||
let thunk = env.get_mut(&var_id).unwrap();
|
||||
thunk.borrow_mut().env.extend(rec_env.clone());
|
||||
(
|
||||
id,
|
||||
@ -577,8 +728,7 @@ where
|
||||
if 0 < stack.count_args() {
|
||||
let (arg, pos_app) = stack.pop_arg().expect("Condition already checked.");
|
||||
call_stack.push(StackElem::App(pos_app));
|
||||
let thunk = Rc::new(RefCell::new(arg));
|
||||
env.insert(x, (thunk, IdentKind::Lam()));
|
||||
env.insert(x, Thunk::new(arg, IdentKind::Lam()));
|
||||
Closure { body: t, env }
|
||||
} else {
|
||||
return Ok((Term::Fun(x, t), env));
|
||||
@ -607,9 +757,7 @@ where
|
||||
/// Pop and update all the thunks on the top of the stack with the given closure.
|
||||
fn update_thunks(stack: &mut Stack, closure: &Closure) {
|
||||
while let Some(thunk) = stack.pop_thunk() {
|
||||
if let Some(safe_thunk) = Weak::upgrade(&thunk) {
|
||||
*safe_thunk.borrow_mut() = closure.clone();
|
||||
}
|
||||
thunk.update(closure.clone());
|
||||
}
|
||||
}
|
||||
|
||||
@ -633,8 +781,8 @@ pub fn subst(rt: RichTerm, global_env: &Environment, env: &Environment) -> RichT
|
||||
Term::Var(id) if !bound.as_ref().contains(&id) => env
|
||||
.get(&id)
|
||||
.or_else(|| global_env.get(&id))
|
||||
.map(|(rc, _)| {
|
||||
let closure = rc.borrow().clone();
|
||||
.map(|thunk| {
|
||||
let closure = thunk.get_owned();
|
||||
subst_(closure.body, global_env, &closure.env, bound)
|
||||
})
|
||||
.unwrap_or_else(|| RichTerm::new(Term::Var(id), pos)),
|
||||
@ -1109,8 +1257,13 @@ mod tests {
|
||||
fn global_env() {
|
||||
let mut global_env = HashMap::new();
|
||||
let mut resolver = DummyResolver {};
|
||||
let thunk = Rc::new(RefCell::new(Closure::atomic_closure(Term::Num(1.0).into())));
|
||||
global_env.insert(Ident::from("g"), (Rc::clone(&thunk), IdentKind::Let()));
|
||||
global_env.insert(
|
||||
Ident::from("g"),
|
||||
Thunk::new(
|
||||
Closure::atomic_closure(Term::Num(1.0).into()),
|
||||
IdentKind::Let(),
|
||||
),
|
||||
);
|
||||
|
||||
let t = mk_term::let_in("x", Term::Num(2.0), mk_term::var("x"));
|
||||
assert_eq!(eval(t, &global_env, &mut resolver), Ok(Term::Num(2.0)));
|
||||
@ -1129,10 +1282,7 @@ mod tests {
|
||||
.map(|(id, t)| {
|
||||
(
|
||||
id.into(),
|
||||
(
|
||||
Rc::new(RefCell::new(Closure::atomic_closure(t))),
|
||||
IdentKind::Let(),
|
||||
),
|
||||
Thunk::new(Closure::atomic_closure(t), IdentKind::Let()),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
|
@ -149,9 +149,6 @@ pub fn query(
|
||||
global_env: &eval::Environment,
|
||||
path: Option<String>,
|
||||
) -> Result<Term, Error> {
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
cache.prepare(file_id, global_env)?;
|
||||
|
||||
let t = if let Some(p) = path {
|
||||
@ -166,15 +163,12 @@ pub fn query(
|
||||
|
||||
// Substituting `y` for `t`
|
||||
let mut env = eval::Environment::new();
|
||||
let closure = eval::Closure {
|
||||
body: cache.get_owned(file_id).unwrap(),
|
||||
env: eval::Environment::new(),
|
||||
};
|
||||
env.insert(
|
||||
eval::env_add(
|
||||
&mut env,
|
||||
Ident::from("y"),
|
||||
(Rc::new(RefCell::new(closure)), eval::IdentKind::Let()),
|
||||
cache.get_owned(file_id).unwrap(),
|
||||
eval::Environment::new(),
|
||||
);
|
||||
|
||||
//TODO: why passing an empty global environment?
|
||||
eval::subst(new_term, &eval::Environment::new(), &env)
|
||||
} else {
|
||||
@ -1517,4 +1511,24 @@ too
|
||||
// that this test fails.
|
||||
eval_string_full("{y = fun x => x; x = fun y => y}").unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn infinite_loops() {
|
||||
assert_matches!(
|
||||
eval_string("{x = x}.x"),
|
||||
Err(Error::EvalError(EvalError::InfiniteRecursion(..)))
|
||||
);
|
||||
assert_matches!(
|
||||
eval_string("{x = y; y = z; z = x }.x"),
|
||||
Err(Error::EvalError(EvalError::InfiniteRecursion(..)))
|
||||
);
|
||||
assert_matches!(
|
||||
eval_string("{x = y + z; y = z + x; z = 1}.x"),
|
||||
Err(Error::EvalError(EvalError::InfiniteRecursion(..)))
|
||||
);
|
||||
assert_matches!(
|
||||
eval_string("{x = (fun a => a + y) 0; y = (fun a => a + x) 0}.x"),
|
||||
Err(Error::EvalError(EvalError::InfiniteRecursion(..)))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
36
src/stack.rs
36
src/stack.rs
@ -1,12 +1,10 @@
|
||||
//! Define the main evaluation stack of the Nickel abstract machine and related operations.
|
||||
//!
|
||||
//! See [eval](../eval/index.html).
|
||||
use crate::eval::{Closure, Environment};
|
||||
use crate::eval::{Closure, Environment, ThunkUpdateFrame};
|
||||
use crate::operation::OperationCont;
|
||||
use crate::position::RawSpan;
|
||||
use crate::term::{RichTerm, StrChunk};
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Weak;
|
||||
|
||||
/// An element of the stack.
|
||||
#[derive(Debug)]
|
||||
@ -22,7 +20,7 @@ pub enum Marker {
|
||||
/// An argument of an application.
|
||||
Arg(Closure, Option<RawSpan>),
|
||||
/// A thunk, which is pointer to a mutable memory cell to be updated.
|
||||
Thunk(Weak<RefCell<Closure>>),
|
||||
Thunk(ThunkUpdateFrame),
|
||||
/// The continuation of a primitive operation.
|
||||
Cont(
|
||||
OperationCont,
|
||||
@ -132,7 +130,7 @@ impl Stack {
|
||||
self.0.push(Marker::Arg(arg, pos))
|
||||
}
|
||||
|
||||
pub fn push_thunk(&mut self, thunk: Weak<RefCell<Closure>>) {
|
||||
pub fn push_thunk(&mut self, thunk: ThunkUpdateFrame) {
|
||||
self.0.push(Marker::Thunk(thunk))
|
||||
}
|
||||
|
||||
@ -176,7 +174,7 @@ impl Stack {
|
||||
|
||||
/// Try to pop a thunk from the top of the stack. If `None` is returned, the top element was
|
||||
/// not a thunk and the stack is left unchanged.
|
||||
pub fn pop_thunk(&mut self) -> Option<Weak<RefCell<Closure>>> {
|
||||
pub fn pop_thunk(&mut self) -> Option<ThunkUpdateFrame> {
|
||||
match self.0.pop() {
|
||||
Some(Marker::Thunk(thunk)) => Some(thunk),
|
||||
Some(m) => {
|
||||
@ -239,7 +237,7 @@ impl Stack {
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the top element is an argument.
|
||||
/// Check if the top element is a thunk.
|
||||
pub fn is_top_thunk(&self) -> bool {
|
||||
self.0.last().map(Marker::is_thunk).unwrap_or(false)
|
||||
}
|
||||
@ -259,8 +257,9 @@ impl Stack {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::eval::{IdentKind, Thunk};
|
||||
use crate::term::{Term, UnaryOp};
|
||||
use std::rc::Rc;
|
||||
use assert_matches::assert_matches;
|
||||
|
||||
impl Stack {
|
||||
/// Count the number of thunks at the top of the stack.
|
||||
@ -287,8 +286,8 @@ mod tests {
|
||||
}
|
||||
|
||||
fn some_thunk_marker() -> Marker {
|
||||
let rc = Rc::new(RefCell::new(some_closure()));
|
||||
Marker::Thunk(Rc::downgrade(&rc))
|
||||
let mut thunk = Thunk::new(some_closure(), IdentKind::Let());
|
||||
Marker::Thunk(thunk.to_update_frame().unwrap())
|
||||
}
|
||||
|
||||
fn some_cont_marker() -> Marker {
|
||||
@ -319,13 +318,26 @@ mod tests {
|
||||
let mut s = Stack::new();
|
||||
assert_eq!(0, s.count_thunks());
|
||||
|
||||
s.push_thunk(Rc::downgrade(&Rc::new(RefCell::new(some_closure()))));
|
||||
s.push_thunk(Rc::downgrade(&Rc::new(RefCell::new(some_closure()))));
|
||||
let mut thunk = Thunk::new(some_closure(), IdentKind::Let());
|
||||
s.push_thunk(thunk.to_update_frame().unwrap());
|
||||
thunk = Thunk::new(some_closure(), IdentKind::Let());
|
||||
s.push_thunk(thunk.to_update_frame().unwrap());
|
||||
|
||||
assert_eq!(2, s.count_thunks());
|
||||
s.pop_thunk().expect("Already checked");
|
||||
assert_eq!(1, s.count_thunks());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn thunk_blackhole() {
|
||||
let mut thunk = Thunk::new(some_closure(), IdentKind::Let());
|
||||
let thunk_upd = thunk.to_update_frame();
|
||||
assert_matches!(thunk_upd, Ok(..));
|
||||
assert_matches!(thunk.to_update_frame(), Err(..));
|
||||
thunk_upd.unwrap().update(some_closure());
|
||||
assert_matches!(thunk.to_update_frame(), Ok(..));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pushing_and_poping_conts() {
|
||||
let mut s = Stack::new();
|
||||
|
@ -2,15 +2,13 @@
|
||||
|
||||
use crate::cache::ImportResolver;
|
||||
use crate::error::ImportError;
|
||||
use crate::eval::{Closure, Environment, IdentKind};
|
||||
use crate::eval::{Closure, Environment, IdentKind, Thunk};
|
||||
use crate::identifier::Ident;
|
||||
use crate::term::{RichTerm, Term};
|
||||
use crate::types::{AbsType, Types};
|
||||
use codespan::FileId;
|
||||
use simple_counter::*;
|
||||
use std::cell::RefCell;
|
||||
use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
|
||||
generate_counter!(FreshVarCounter, usize);
|
||||
|
||||
@ -321,12 +319,11 @@ impl Closurizable for RichTerm {
|
||||
/// and return this variable as a fresh term.
|
||||
fn closurize(self, env: &mut Environment, with_env: Environment) -> RichTerm {
|
||||
let var = fresh_var();
|
||||
let c = Closure {
|
||||
let closure = Closure {
|
||||
body: self,
|
||||
env: with_env,
|
||||
};
|
||||
|
||||
env.insert(var.clone(), (Rc::new(RefCell::new(c)), IdentKind::Record()));
|
||||
env.insert(var.clone(), Thunk::new(closure, IdentKind::Record()));
|
||||
|
||||
Term::Var(var).into()
|
||||
}
|
||||
|
@ -359,10 +359,10 @@ impl<'a> Envs<'a> {
|
||||
pub fn mk_global(eval_env: &eval::Environment) -> Environment {
|
||||
eval_env
|
||||
.iter()
|
||||
.map(|(id, (rc, _))| {
|
||||
.map(|(id, thunk)| {
|
||||
(
|
||||
id.clone(),
|
||||
to_typewrapper(apparent_type(rc.borrow().body.as_ref(), None).into()),
|
||||
to_typewrapper(apparent_type(thunk.borrow().body.as_ref(), None).into()),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
|
Loading…
Reference in New Issue
Block a user