Browse Source

handle errors in the repl too

Getty Ritter 1 year ago
parent
commit
85640df573
1 changed files with 44 additions and 29 deletions
  1. 44 29
      src/interp.rs

+ 44 - 29
src/interp.rs

@@ -8,7 +8,6 @@ use std::cell::RefCell;
 use std::collections::HashMap;
 use std::fmt;
 use std::io;
-use std::io::Write;
 use std::rc::Rc;
 
 /// A `Value` is a representation of the result of evaluation. Note
@@ -271,21 +270,25 @@ impl State {
         self.run_with_writer(src, &mut io::stdout())
     }
 
+    fn print_error(&self, file: FileRef, mtz: MatzoError) -> String {
+        let mut buf = String::new();
+        buf.push_str(&mtz.message);
+        buf.push('\n');
+        buf.push_str(&self.ast.borrow().get_line(file, mtz.span));
+        for ctx in mtz.context {
+            buf.push('\n');
+            buf.push_str(&ctx.message);
+            buf.push_str(&self.ast.borrow().get_line(file, ctx.span));
+        }
+        buf
+    }
+
     /// Evaluate this string as a standalone program, writing the
     /// results to the provided writer.
     pub fn run_with_writer(&self, src: &str, w: &mut impl std::io::Write) -> Result<(), Error> {
         let file = self.ast.borrow_mut().add_file(src.to_string());
         if let Err(mtz) = self.run_file(src, file, w) {
-            let mut buf = String::new();
-            buf.push_str(&mtz.message);
-            buf.push('\n');
-            buf.push_str(&self.ast.borrow().get_line(file, mtz.span));
-            for ctx in mtz.context {
-                buf.push('\n');
-                buf.push_str(&ctx.message);
-                buf.push_str(&self.ast.borrow().get_line(file, ctx.span));
-            }
-            bail!("{}", buf);
+            bail!("{}", self.print_error(file, mtz));
         }
         Ok(())
     }
@@ -305,14 +308,7 @@ impl State {
         Ok(())
     }
 
-    /// Evaluate this string as a fragment in a REPL, writing the
-    /// results to stdout. One way this differs from the standalone
-    /// program is that it actually tries parsing twice: first it
-    /// tries parsing the fragment normally, and then if that doesn't
-    /// work it tries adding a `puts` ahead of it: this is hacky, but
-    /// it allows the REPL to respond by printing values when someone
-    /// simply types an expression.
-    pub fn run_repl(&self, src: &str) -> Result<(), Error> {
+    fn repl_parse(&self, src: &str) -> Result<Vec<Stmt>, MatzoError> {
         let lexed = crate::lexer::tokens(src);
         let file = self.ast.borrow_mut().add_file(src.to_string());
         let stmts = {
@@ -320,26 +316,45 @@ impl State {
             self.parser.parse(&mut ast, file, lexed)
         };
         match stmts {
-            Ok(stmts) => {
-                for stmt in stmts {
-                    self.execute(&stmt, io::stdout())?;
-                }
-            }
+            Ok(stmts) => Ok(stmts),
             Err(err) => {
+                // this might have just been an expression instead, so
+                // try parsing a single expression to see if that
+                // works
                 let lexed = crate::lexer::tokens(src);
                 let expr = {
                     let mut ast = self.ast.borrow_mut();
                     self.expr_parser.parse(&mut ast, file, lexed)
                 };
                 if let Ok(expr) = expr {
-                    let val = self.eval(expr, &None)?;
-                    let val = self.force(val)?;
-                    writeln!(io::stdout(), "{}", val.to_string(&self.ast.borrow()))?;
+                    Ok(vec![Stmt::Puts(expr)])
                 } else {
-                    bail!("{:?}", err);
+                    Err(MatzoError::from_parse_error(err))
                 }
             }
-        };
+        }
+    }
+
+    /// Evaluate this string as a fragment in a REPL, writing the
+    /// results to stdout. One way this differs from the standalone
+    /// program is that it actually tries parsing twice: first it
+    /// tries parsing the fragment normally, and then if that doesn't
+    /// work it tries adding a `puts` ahead of it: this is hacky, but
+    /// it allows the REPL to respond by printing values when someone
+    /// simply types an expression.
+    pub fn run_repl(&self, src: &str) -> Result<(), Error> {
+        let file = self.ast.borrow_mut().add_file(src.to_string());
+        if let Err(mtz) = self.repl_main(src) {
+            bail!("{}", self.print_error(file, mtz));
+        }
+        Ok(())
+    }
+
+    fn repl_main(&self, src: &str) -> Result<(), MatzoError> {
+        let stmts = self.repl_parse(src)?;
+        for stmt in stmts {
+            self.execute(&stmt, io::stdout())?;
+        }
         Ok(())
     }