about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--tvix/eval/src/compiler.rs199
-rw-r--r--tvix/eval/src/errors.rs6
-rw-r--r--tvix/eval/src/eval.rs8
3 files changed, 111 insertions, 102 deletions
diff --git a/tvix/eval/src/compiler.rs b/tvix/eval/src/compiler.rs
index 92f04c84dc..577578a936 100644
--- a/tvix/eval/src/compiler.rs
+++ b/tvix/eval/src/compiler.rs
@@ -19,7 +19,7 @@ use rowan::ast::AstNode;
 use std::path::{Path, PathBuf};
 
 use crate::chunk::Chunk;
-use crate::errors::{ErrorKind, EvalResult};
+use crate::errors::{Error, ErrorKind, EvalResult};
 use crate::opcode::{CodeIdx, OpCode};
 use crate::value::Value;
 use crate::warnings::{EvalWarning, WarningKind};
@@ -30,6 +30,7 @@ use crate::warnings::{EvalWarning, WarningKind};
 pub struct CompilationResult {
     pub chunk: Chunk,
     pub warnings: Vec<EvalWarning>,
+    pub errors: Vec<Error>,
 }
 
 // Represents a single local already known to the compiler.
@@ -87,11 +88,12 @@ struct Compiler {
     scope: Scope,
 
     warnings: Vec<EvalWarning>,
+    errors: Vec<Error>,
     root_dir: PathBuf,
 }
 
 impl Compiler {
-    fn compile(&mut self, expr: ast::Expr) -> EvalResult<()> {
+    fn compile(&mut self, expr: ast::Expr) {
         match expr {
             ast::Expr::Literal(literal) => self.compile_literal(literal),
             ast::Expr::Path(path) => self.compile_path(path),
@@ -121,18 +123,16 @@ impl Compiler {
         }
     }
 
-    fn compile_literal(&mut self, node: ast::Literal) -> EvalResult<()> {
+    fn compile_literal(&mut self, node: ast::Literal) {
         match node.kind() {
             ast::LiteralKind::Float(f) => {
                 let idx = self.chunk.push_constant(Value::Float(f.value().unwrap()));
                 self.chunk.push_op(OpCode::OpConstant(idx));
-                Ok(())
             }
 
             ast::LiteralKind::Integer(i) => {
                 let idx = self.chunk.push_constant(Value::Integer(i.value().unwrap()));
                 self.chunk.push_op(OpCode::OpConstant(idx));
-                Ok(())
             }
             ast::LiteralKind::Uri(u) => {
                 self.emit_warning(node.syntax().clone(), WarningKind::DeprecatedLiteralURL);
@@ -141,12 +141,11 @@ impl Compiler {
                     .chunk
                     .push_constant(Value::String(u.syntax().text().into()));
                 self.chunk.push_op(OpCode::OpConstant(idx));
-                Ok(())
             }
         }
     }
 
-    fn compile_path(&mut self, node: ast::Path) -> EvalResult<()> {
+    fn compile_path(&mut self, node: ast::Path) {
         // TODO(tazjin): placeholder implementation while waiting for
         // https://github.com/nix-community/rnix-parser/pull/96
 
@@ -154,9 +153,16 @@ impl Compiler {
         let path = if raw_path.starts_with('/') {
             Path::new(&raw_path).to_owned()
         } else if raw_path.starts_with('~') {
-            let mut buf = dirs::home_dir().ok_or_else(|| {
-                ErrorKind::PathResolution("failed to determine home directory".into())
-            })?;
+            let mut buf = match dirs::home_dir() {
+                Some(buf) => buf,
+                None => {
+                    self.emit_error(
+                        node.syntax().clone(),
+                        ErrorKind::PathResolution("failed to determine home directory".into()),
+                    );
+                    return;
+                }
+            };
 
             buf.push(&raw_path);
             buf
@@ -174,11 +180,9 @@ impl Compiler {
         let value = Value::Path(path.clean());
         let idx = self.chunk.push_constant(value);
         self.chunk.push_op(OpCode::OpConstant(idx));
-
-        Ok(())
     }
 
-    fn compile_str(&mut self, node: ast::Str) -> EvalResult<()> {
+    fn compile_str(&mut self, node: ast::Str) {
         let mut count = 0;
 
         // The string parts are produced in literal order, however
@@ -192,7 +196,7 @@ impl Compiler {
                 // Interpolated expressions are compiled as normal and
                 // dealt with by the VM before being assembled into
                 // the final string.
-                ast::InterpolPart::Interpolation(node) => self.compile(node.expr().unwrap())?,
+                ast::InterpolPart::Interpolation(node) => self.compile(node.expr().unwrap()),
 
                 ast::InterpolPart::Literal(lit) => {
                     let idx = self.chunk.push_constant(Value::String(lit.into()));
@@ -204,12 +208,10 @@ impl Compiler {
         if count != 1 {
             self.chunk.push_op(OpCode::OpInterpolate(count));
         }
-
-        Ok(())
     }
 
-    fn compile_unary_op(&mut self, op: ast::UnaryOp) -> EvalResult<()> {
-        self.compile(op.expr().unwrap())?;
+    fn compile_unary_op(&mut self, op: ast::UnaryOp) {
+        self.compile(op.expr().unwrap());
 
         let opcode = match op.operator().unwrap() {
             ast::UnaryOpKind::Invert => OpCode::OpInvert,
@@ -217,10 +219,9 @@ impl Compiler {
         };
 
         self.chunk.push_op(opcode);
-        Ok(())
     }
 
-    fn compile_binop(&mut self, op: ast::BinOp) -> EvalResult<()> {
+    fn compile_binop(&mut self, op: ast::BinOp) {
         use ast::BinOpKind;
 
         // Short-circuiting and other strange operators, which are
@@ -238,8 +239,8 @@ impl Compiler {
         // For all other operators, the two values need to be left on
         // the stack in the correct order before pushing the
         // instruction for the operation itself.
-        self.compile(op.lhs().unwrap())?;
-        self.compile(op.rhs().unwrap())?;
+        self.compile(op.lhs().unwrap());
+        self.compile(op.rhs().unwrap());
 
         match op.operator().unwrap() {
             BinOpKind::Add => self.chunk.push_op(OpCode::OpAdd),
@@ -264,11 +265,9 @@ impl Compiler {
                 unreachable!()
             }
         };
-
-        Ok(())
     }
 
-    fn compile_and(&mut self, node: ast::BinOp) -> EvalResult<()> {
+    fn compile_and(&mut self, node: ast::BinOp) {
         debug_assert!(
             matches!(node.operator(), Some(ast::BinOpKind::And)),
             "compile_and called with wrong operator kind: {:?}",
@@ -276,7 +275,7 @@ impl Compiler {
         );
 
         // Leave left-hand side value on the stack.
-        self.compile(node.lhs().unwrap())?;
+        self.compile(node.lhs().unwrap());
 
         // If this value is false, jump over the right-hand side - the
         // whole expression is false.
@@ -286,15 +285,13 @@ impl Compiler {
         // right-hand side on the stack. Its result is now the value
         // of the whole expression.
         self.chunk.push_op(OpCode::OpPop);
-        self.compile(node.rhs().unwrap())?;
+        self.compile(node.rhs().unwrap());
 
         self.patch_jump(end_idx);
         self.chunk.push_op(OpCode::OpAssertBool);
-
-        Ok(())
     }
 
-    fn compile_or(&mut self, node: ast::BinOp) -> EvalResult<()> {
+    fn compile_or(&mut self, node: ast::BinOp) {
         debug_assert!(
             matches!(node.operator(), Some(ast::BinOpKind::Or)),
             "compile_or called with wrong operator kind: {:?}",
@@ -302,20 +299,18 @@ impl Compiler {
         );
 
         // Leave left-hand side value on the stack
-        self.compile(node.lhs().unwrap())?;
+        self.compile(node.lhs().unwrap());
 
         // Opposite of above: If this value is **true**, we can
         // short-circuit the right-hand side.
         let end_idx = self.chunk.push_op(OpCode::OpJumpIfTrue(0));
         self.chunk.push_op(OpCode::OpPop);
-        self.compile(node.rhs().unwrap())?;
+        self.compile(node.rhs().unwrap());
         self.patch_jump(end_idx);
         self.chunk.push_op(OpCode::OpAssertBool);
-
-        Ok(())
     }
 
-    fn compile_implication(&mut self, node: ast::BinOp) -> EvalResult<()> {
+    fn compile_implication(&mut self, node: ast::BinOp) {
         debug_assert!(
             matches!(node.operator(), Some(ast::BinOpKind::Implication)),
             "compile_implication called with wrong operator kind: {:?}",
@@ -323,22 +318,20 @@ impl Compiler {
         );
 
         // Leave left-hand side value on the stack and invert it.
-        self.compile(node.lhs().unwrap())?;
+        self.compile(node.lhs().unwrap());
         self.chunk.push_op(OpCode::OpInvert);
 
         // Exactly as `||` (because `a -> b` = `!a || b`).
         let end_idx = self.chunk.push_op(OpCode::OpJumpIfTrue(0));
         self.chunk.push_op(OpCode::OpPop);
-        self.compile(node.rhs().unwrap())?;
+        self.compile(node.rhs().unwrap());
         self.patch_jump(end_idx);
         self.chunk.push_op(OpCode::OpAssertBool);
-
-        Ok(())
     }
 
-    fn compile_has_attr(&mut self, node: ast::HasAttr) -> EvalResult<()> {
+    fn compile_has_attr(&mut self, node: ast::HasAttr) {
         // Put the attribute set on the stack.
-        self.compile(node.expr().unwrap())?;
+        self.compile(node.expr().unwrap());
         let mut count = 0;
 
         // Push all path fragments with an operation for fetching the
@@ -348,24 +341,19 @@ impl Compiler {
                 self.chunk.push_op(OpCode::OpAttrOrNotFound);
             }
             count += 1;
-            self.compile_attr(fragment)?;
+            self.compile_attr(fragment);
         }
 
         // After the last fragment, emit the actual instruction that
         // leaves a boolean on the stack.
         self.chunk.push_op(OpCode::OpAttrsIsSet);
-
-        Ok(())
     }
 
-    fn compile_attr(&mut self, node: ast::Attr) -> EvalResult<()> {
+    fn compile_attr(&mut self, node: ast::Attr) {
         match node {
             ast::Attr::Dynamic(dynamic) => self.compile(dynamic.expr().unwrap()),
             ast::Attr::Str(s) => self.compile_str(s),
-            ast::Attr::Ident(ident) => {
-                self.emit_literal_ident(&ident);
-                Ok(())
-            }
+            ast::Attr::Ident(ident) => self.emit_literal_ident(&ident),
         }
     }
 
@@ -375,16 +363,15 @@ impl Compiler {
     //
     // The VM, after evaluating the code for each element, simply
     // constructs the list from the given number of elements.
-    fn compile_list(&mut self, node: ast::List) -> EvalResult<()> {
+    fn compile_list(&mut self, node: ast::List) {
         let mut count = 0;
 
         for item in node.items() {
             count += 1;
-            self.compile(item)?;
+            self.compile(item);
         }
 
         self.chunk.push_op(OpCode::OpList(count));
-        Ok(())
     }
 
     // Compile attribute set literals into equivalent bytecode.
@@ -395,7 +382,7 @@ impl Compiler {
     // 1. Keys can be dynamically constructed through interpolation.
     // 2. Keys can refer to nested attribute sets.
     // 3. Attribute sets can (optionally) be recursive.
-    fn compile_attr_set(&mut self, node: ast::AttrSet) -> EvalResult<()> {
+    fn compile_attr_set(&mut self, node: ast::AttrSet) {
         if node.rec_token().is_some() {
             todo!("recursive attribute sets are not yet implemented")
         }
@@ -422,7 +409,7 @@ impl Compiler {
                         // instruction followed by a merge, rather
                         // than pushing/popping the same attrs
                         // potentially a lot of times.
-                        self.compile(from.expr().unwrap())?;
+                        self.compile(from.expr().unwrap());
                         self.emit_literal_ident(&ident);
                         self.chunk.push_op(OpCode::OpAttrsSelect);
                     }
@@ -435,7 +422,13 @@ impl Compiler {
 
                         match self.resolve_local(ident.ident_token().unwrap().text()) {
                             Some(idx) => self.chunk.push_op(OpCode::OpGetLocal(idx)),
-                            None => return Err(ErrorKind::UnknownStaticVariable(ident).into()),
+                            None => {
+                                self.emit_error(
+                                    ident.syntax().clone(),
+                                    ErrorKind::UnknownStaticVariable,
+                                );
+                                continue;
+                            }
                         };
                     }
                 }
@@ -453,7 +446,7 @@ impl Compiler {
             let mut key_count = 0;
             for fragment in kv.attrpath().unwrap().attrs() {
                 key_count += 1;
-                self.compile_attr(fragment)?;
+                self.compile_attr(fragment);
             }
 
             // We're done with the key if there was only one fragment,
@@ -466,34 +459,32 @@ impl Compiler {
             // The value is just compiled as normal so that its
             // resulting value is on the stack when the attribute set
             // is constructed at runtime.
-            self.compile(kv.value().unwrap())?;
+            self.compile(kv.value().unwrap());
         }
 
         self.chunk.push_op(OpCode::OpAttrs(count));
-        Ok(())
     }
 
-    fn compile_select(&mut self, node: ast::Select) -> EvalResult<()> {
+    fn compile_select(&mut self, node: ast::Select) {
         let set = node.expr().unwrap();
         let path = node.attrpath().unwrap();
 
         if node.or_token().is_some() {
-            return self.compile_select_or(set, path, node.default_expr().unwrap());
+            self.compile_select_or(set, path, node.default_expr().unwrap());
+            return;
         }
 
         // Push the set onto the stack
-        self.compile(set)?;
+        self.compile(set);
 
         // Compile each key fragment and emit access instructions.
         //
         // TODO: multi-select instruction to avoid re-pushing attrs on
         // nested selects.
         for fragment in path.attrs() {
-            self.compile_attr(fragment)?;
+            self.compile_attr(fragment);
             self.chunk.push_op(OpCode::OpAttrsSelect);
         }
-
-        Ok(())
     }
 
     /// Compile an `or` expression into a chunk of conditional jumps.
@@ -525,17 +516,12 @@ impl Compiler {
     ///  │ 14 ...                     │   │ ..   ....               │
     ///  └────────────────────────────┘   └─────────────────────────┘
     /// ```
-    fn compile_select_or(
-        &mut self,
-        set: ast::Expr,
-        path: ast::Attrpath,
-        default: ast::Expr,
-    ) -> EvalResult<()> {
-        self.compile(set)?;
+    fn compile_select_or(&mut self, set: ast::Expr, path: ast::Attrpath, default: ast::Expr) {
+        self.compile(set);
         let mut jumps = vec![];
 
         for fragment in path.attrs() {
-            self.compile_attr(fragment)?;
+            self.compile_attr(fragment);
             self.chunk.push_op(OpCode::OpAttrOrNotFound);
             jumps.push(self.chunk.push_op(OpCode::OpJumpIfNotFound(0)));
         }
@@ -548,21 +534,19 @@ impl Compiler {
 
         // Compile the default value expression and patch the final
         // jump to point *beyond* it.
-        self.compile(default)?;
+        self.compile(default);
         self.patch_jump(final_jump);
-
-        Ok(())
     }
 
-    fn compile_assert(&mut self, node: ast::Assert) -> EvalResult<()> {
+    fn compile_assert(&mut self, node: ast::Assert) {
         // Compile the assertion condition to leave its value on the stack.
-        self.compile(node.condition().unwrap())?;
+        self.compile(node.condition().unwrap());
         self.chunk.push_op(OpCode::OpAssert);
 
         // The runtime will abort evaluation at this point if the
         // assertion failed, if not the body simply continues on like
         // normal.
-        self.compile(node.body().unwrap())
+        self.compile(node.body().unwrap());
     }
 
     // Compile conditional expressions using jumping instructions in the VM.
@@ -575,23 +559,21 @@ impl Compiler {
     //  Jump over else body  ││ 4  [  else body  ]←┼─┘
     //  if condition is true.└┼─5─→     ...        │
     //                        └────────────────────┘
-    fn compile_if_else(&mut self, node: ast::IfElse) -> EvalResult<()> {
-        self.compile(node.condition().unwrap())?;
+    fn compile_if_else(&mut self, node: ast::IfElse) {
+        self.compile(node.condition().unwrap());
 
         let then_idx = self.chunk.push_op(OpCode::OpJumpIfFalse(0));
 
         self.chunk.push_op(OpCode::OpPop); // discard condition value
-        self.compile(node.body().unwrap())?;
+        self.compile(node.body().unwrap());
 
         let else_idx = self.chunk.push_op(OpCode::OpJump(0));
 
         self.patch_jump(then_idx); // patch jump *to* else_body
         self.chunk.push_op(OpCode::OpPop); // discard condition value
-        self.compile(node.else_body().unwrap())?;
+        self.compile(node.else_body().unwrap());
 
         self.patch_jump(else_idx); // patch jump *over* else body
-
-        Ok(())
     }
 
     // Compile a standard `let ...; in ...` statement.
@@ -599,7 +581,7 @@ impl Compiler {
     // Unless in a non-standard scope, the encountered values are
     // simply pushed on the stack and their indices noted in the
     // entries vector.
-    fn compile_let_in(&mut self, node: ast::LetIn) -> EvalResult<()> {
+    fn compile_let_in(&mut self, node: ast::LetIn) {
         self.begin_scope();
 
         for inherit in node.inherits() {
@@ -614,7 +596,7 @@ impl Compiler {
 
                 Some(from) => {
                     for ident in inherit.idents() {
-                        self.compile(from.expr().unwrap())?;
+                        self.compile(from.expr().unwrap());
                         self.emit_literal_ident(&ident);
                         self.chunk.push_op(OpCode::OpAttrsSelect);
                         self.declare_local(ident.ident_token().unwrap().text());
@@ -624,23 +606,28 @@ impl Compiler {
         }
 
         for entry in node.attrpath_values() {
-            let mut path = normalise_ident_path(entry.attrpath().unwrap().attrs())?;
+            let mut path = match normalise_ident_path(entry.attrpath().unwrap().attrs()) {
+                Ok(p) => p,
+                Err(err) => {
+                    self.errors.push(err);
+                    continue;
+                }
+            };
 
             if path.len() != 1 {
                 todo!("nested bindings in let expressions :(")
             }
 
-            self.compile(entry.value().unwrap())?;
+            self.compile(entry.value().unwrap());
             self.declare_local(path.pop().unwrap());
         }
 
         // Deal with the body, then clean up the locals afterwards.
-        self.compile(node.body().unwrap())?;
+        self.compile(node.body().unwrap());
         self.end_scope();
-        Ok(())
     }
 
-    fn compile_ident(&mut self, node: ast::Ident) -> EvalResult<()> {
+    fn compile_ident(&mut self, node: ast::Ident) {
         match node.ident_token().unwrap().text() {
             // TODO(tazjin): Nix technically allows code like
             //
@@ -663,7 +650,11 @@ impl Compiler {
                     Some(idx) => self.chunk.push_op(OpCode::OpGetLocal(idx)),
                     None => {
                         if self.scope.with_stack.is_empty() {
-                            return Err(ErrorKind::UnknownStaticVariable(node).into());
+                            self.emit_error(
+                                node.syntax().clone(),
+                                ErrorKind::UnknownStaticVariable,
+                            );
+                            return;
                         }
 
                         // Variable needs to be dynamically resolved
@@ -675,18 +666,16 @@ impl Compiler {
                 }
             }
         };
-
-        Ok(())
     }
 
     // Compile `with` expressions by emitting instructions that
     // pop/remove the indices of attribute sets that are implicitly in
     // scope through `with` on the "with-stack".
-    fn compile_with(&mut self, node: ast::With) -> EvalResult<()> {
+    fn compile_with(&mut self, node: ast::With) {
         // TODO: Detect if the namespace is just an identifier, and
         // resolve that directly (thus avoiding duplication on the
         // stack).
-        self.compile(node.namespace().unwrap())?;
+        self.compile(node.namespace().unwrap());
 
         self.declare_phantom();
         self.scope.with_stack.push(With {
@@ -696,7 +685,7 @@ impl Compiler {
         self.chunk
             .push_op(OpCode::OpPushWith(self.scope.locals.len() - 1));
 
-        self.compile(node.body().unwrap())
+        self.compile(node.body().unwrap());
     }
 
     /// Emit the literal string value of an identifier. Required for
@@ -825,6 +814,13 @@ impl Compiler {
     fn emit_warning(&mut self, node: rnix::SyntaxNode, kind: WarningKind) {
         self.warnings.push(EvalWarning { node, kind })
     }
+
+    fn emit_error(&mut self, node: rnix::SyntaxNode, kind: ErrorKind) {
+        self.errors.push(Error {
+            node: Some(node),
+            kind,
+        })
+    }
 }
 
 /// Convert a non-dynamic string expression to a string if possible,
@@ -836,7 +832,10 @@ fn expr_str_to_string(expr: ast::Str) -> EvalResult<String> {
         }
     }
 
-    return Err(ErrorKind::DynamicKeyInLet(expr.syntax().clone()).into());
+    return Err(Error {
+        node: Some(expr.syntax().clone()),
+        kind: ErrorKind::DynamicKeyInLet(expr.syntax().clone()),
+    });
 }
 
 /// Convert a single identifier path fragment to a string if possible,
@@ -883,13 +882,15 @@ pub fn compile(expr: ast::Expr, location: Option<PathBuf>) -> EvalResult<Compila
         root_dir,
         chunk: Chunk::default(),
         warnings: vec![],
+        errors: vec![],
         scope: Default::default(),
     };
 
-    c.compile(expr)?;
+    c.compile(expr);
 
     Ok(CompilationResult {
         chunk: c.chunk,
         warnings: c.warnings,
+        errors: c.errors,
     })
 }
diff --git a/tvix/eval/src/errors.rs b/tvix/eval/src/errors.rs
index 01a5e81449..fd3fddc326 100644
--- a/tvix/eval/src/errors.rs
+++ b/tvix/eval/src/errors.rs
@@ -1,6 +1,6 @@
 use std::fmt::Display;
 
-#[derive(Debug)]
+#[derive(Clone, Debug)]
 pub enum ErrorKind {
     DuplicateAttrsKey {
         key: String,
@@ -27,7 +27,7 @@ pub enum ErrorKind {
     DynamicKeyInLet(rnix::SyntaxNode),
 
     // Unknown variable in statically known scope.
-    UnknownStaticVariable(rnix::ast::Ident),
+    UnknownStaticVariable,
 
     // Unknown variable in dynamic scope (with, rec, ...).
     UnknownDynamicVariable(String),
@@ -37,7 +37,7 @@ pub enum ErrorKind {
     AssertionFailed,
 }
 
-#[derive(Debug)]
+#[derive(Clone, Debug)]
 pub struct Error {
     pub node: Option<rnix::SyntaxNode>,
     pub kind: ErrorKind,
diff --git a/tvix/eval/src/eval.rs b/tvix/eval/src/eval.rs
index 8f2946e6cc..10d36904e4 100644
--- a/tvix/eval/src/eval.rs
+++ b/tvix/eval/src/eval.rs
@@ -41,5 +41,13 @@ pub fn interpret(code: &str, location: Option<PathBuf>) -> EvalResult<Value> {
         )
     }
 
+    for error in &result.errors {
+        eprintln!("compiler error: {:?} at {:?}", error.kind, error.node,);
+    }
+
+    if let Some(err) = result.errors.last() {
+        return Err(err.clone());
+    }
+
     crate::vm::run_chunk(result.chunk)
 }