Repository: tweag/nickel
Pull request #433 (2021-10-27 13:01:02)

Adding strictness markers on top of stack
- Automatically apply strictness rule on evaluation loop if marker popped
- Ignore strictness markers while counting arguments
- Ignore strictness markers while popping things off the stack

Should fix issue #425 in a more systematic and generic way.


Patch:
diff --git a/src/eval.rs b/src/eval.rs
index 795b4e6b..73fea2d9 100644
--- a/src/eval.rs
+++ b/src/eval.rs
@@ -448,6 +448,10 @@ where
         } = clos;
         let term = *boxed_term;
 
+        if let Some(strict) = stack.pop_strictness_marker() {
+            enriched_strict = strict;
+        }
+
         clos = match term {
             Term::Var(x) => {
                 let mut thunk = env
@@ -475,6 +479,10 @@ where
                 thunk.into_closure()
             }
             Term::App(t1, t2) => {
+                if !enriched_strict {
+                    stack.push_strictness(enriched_strict);
+                }
+                enriched_strict = true;
                 stack.push_arg(
                     Closure {
                         body: t2,
diff --git a/src/stack.rs b/src/stack.rs
index 51fa338c..d60b81fe 100644
--- a/src/stack.rs
+++ b/src/stack.rs
@@ -46,6 +46,7 @@ pub enum Marker {
         usize,       /* the indentation level of the chunk currently evaluated */
         Environment, /* the common environment of chunks */
     ),
+    Strictness(bool),
 }
 
 impl Marker {
@@ -72,6 +73,10 @@ impl Marker {
     pub fn is_str_acc(&self) -> bool {
         matches!(*self, Marker::StrAcc(..))
     }
+
+    pub fn is_strictness(&self) -> bool {
+        matches!(*self, Marker::Strictness(..))
+    }
 }
 
 /// The evaluation stack.
@@ -150,6 +155,10 @@ impl Stack {
         self.0.push(Marker::StrAcc(acc, indent, env));
     }
 
+    pub fn push_strictness(&mut self, strict: bool) {
+        self.0.push(Marker::Strictness(strict));
+    }
+
     /// Try to pop an argument from the top of the stack. If `None` is returned, the top element
     /// was not an argument and the stack is left unchanged.
     ///
@@ -247,6 +256,17 @@ impl Stack {
         }
     }
 
+    pub fn pop_strictness_marker(&mut self) -> Option<bool> {
+        if self.0.last().map(Marker::is_strictness).unwrap_or(false) {
+            match self.0.pop() {
+                Some(Marker::Strictness(s)) => Some(s),
+                _ => panic!(),
+            }
+        } else {
+            None
+        }
+    }
+
     /// 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)
@@ -377,4 +397,27 @@ mod tests {
         );
         assert_eq!(1, s.count_conts());
     }
+
+    #[test]
+    fn pushing_and_poping_strictness_markers() {
+        let mut s = Stack::new();
+        assert_eq!(0, s.count_args());
+
+        s.push_strictness(true);
+        assert_eq!(0, s.count_args());
+        s.push_arg(some_closure(), TermPos::None);
+        s.push_arg(some_closure(), TermPos::None);
+        assert_eq!(2, s.count_args());
+        s.push_strictness(false);
+        assert_eq!(0, s.count_args());
+
+        assert_eq!(s.pop_strictness_marker(), Some(false));
+        assert_eq!(2, s.count_args());
+        assert_matches!(s.pop_arg(), Some(..));
+        assert_matches!(s.pop_arg(), Some(..));
+        assert_eq!(s.pop_arg(), None);
+        assert_eq!(0, s.count_args());
+        assert_eq!(s.pop_strictness_marker(), Some(true));
+        assert_eq!(0, s.count_args());
+    }
 }


Test patch:
diff --git a/tests/query.rs b/tests/query.rs
new file mode 100644
index 00000000..370d9e79
--- /dev/null
+++ b/tests/query.rs
@@ -0,0 +1,33 @@
+use nickel::program::Program;
+use nickel::term::Term;
+
+#[test]
+pub fn test_query_metadata_basic() {
+    let mut program =
+        Program::new_from_source("(1+1) | doc \"Test basic\"".as_bytes(), "regr_tests").unwrap();
+    let result = program.query(None).unwrap();
+
+    if let Term::MetaValue(meta) = result {
+        assert_eq!(meta.doc, Some(String::from("Test basic")));
+        assert_eq!(meta.value.unwrap().term, Box::new(Term::Num(2.0)));
+    } else {
+        panic!();
+    }
+}
+
+#[test]
+pub fn test_query_metadata_from_func() {
+    let mut program = Program::new_from_source(
+        "builtins.seq 2 ((3+1) | doc \"Test from func\")".as_bytes(),
+        "regr_tests",
+    )
+    .unwrap();
+    let result = program.query(None).unwrap();
+
+    if let Term::MetaValue(meta) = result {
+        assert_eq!(meta.doc, Some(String::from("Test from func")));
+        assert_eq!(meta.value.unwrap().term, Box::new(Term::Num(4.0)));
+    } else {
+        panic!();
+    }
+}