Browse Source

use span information in errors

Getty Ritter 2 years ago
parent
commit
c2d6b994a2
3 changed files with 64 additions and 14 deletions
  1. 32 1
      src/ast.rs
  2. 2 0
      src/grammar.lalrpop
  3. 30 13
      src/interp.rs

+ 32 - 1
src/ast.rs

@@ -1,5 +1,5 @@
 use std::fmt;
-pub use crate::lexer::{FileRef, Located};
+pub use crate::lexer::{FileRef, Located, Span};
 
 pub type StrRef = string_interner::DefaultSymbol;
 pub type Name = Located<StrRef>;
@@ -77,6 +77,37 @@ impl ASTArena {
         FileRef { idx }
     }
 
+    pub fn get_line(&self, file: FileRef, span: Span) -> String {
+        let mut line_number = 0;
+        let mut start_of_line = 0;
+        let mut end_of_line = None;
+        let src = &self.files[file.idx];
+
+        for (i, ch) in src.char_indices() {
+            if ch == '\n' {
+                line_number += 1;
+                if i < span.start as usize {
+                    start_of_line = i;
+                }
+                if i >= span.end as usize && end_of_line.is_none() {
+                    end_of_line = Some(i);
+                }
+            }
+        }
+        let end_of_line = end_of_line.unwrap_or_else(|| src.len());
+
+        let mut result = format!("{:3} |", line_number);
+        result.push_str(&src[start_of_line .. end_of_line]);
+        result.push_str("\n     ");
+        for _ in start_of_line..(span.start as usize) {
+            result.push(' ');
+        }
+        for _ in span.start..span.end {
+            result.push('^');
+        }
+        result
+    }
+
     fn indent(&self, f: &mut fmt::Formatter, depth: usize) -> fmt::Result {
         for _ in 0..depth {
             write!(f, " ")?;

+ 2 - 0
src/grammar.lalrpop

@@ -68,6 +68,8 @@ pub Name: Name = {
         Located::new(ast.add_string(str), file, Span { start: start as u32, end: end as u32 }),
 };
 
+pub ExprRef = Located<Expr>;
+
 pub Expr: ExprId = {
     <mut ts:(<Choice> "|")*> <t:Choice> => {
         if ts.len() == 0 {

+ 30 - 13
src/interp.rs

@@ -7,6 +7,7 @@ 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
@@ -125,10 +126,14 @@ const BUILTINS: &[BuiltinFunc] = &[
                 let ast = state.ast.borrow();
                 let args = match &ast[expr] {
                     Expr::Tup(tup) => tup,
-                    _ => bail!("`rep`: expected tuple"),
+                    _ => {
+                        let span = state.ast.borrow().get_line(expr.file, expr.span);
+                        bail!("`rep`: expected tuple\n{}", span)
+                    }
                 };
                 if args.len() != 2 {
-                    bail!("`rep`: expected two arguments, got {}", args.len())
+                    let span = state.ast.borrow().get_line(expr.file, expr.span);
+                    bail!("`rep`: expected two arguments, got {}\n{}", args.len(), span)
                 }
                 (args[0], args[1])
             };
@@ -263,6 +268,8 @@ pub struct State {
     rand: RefCell<Box<dyn MatzoRand>>,
     /// The instantiated parser used to parse Matzo programs
     parser: crate::grammar::StmtsParser,
+    /// The instantiated parser used to parse Matzo programs
+    expr_parser: crate::grammar::ExprRefParser,
 }
 
 impl Default for State {
@@ -279,6 +286,7 @@ impl State {
             root_scope: RefCell::new(HashMap::new()),
             rand: RefCell::new(Box::new(DefaultRNG::new())),
             parser: crate::grammar::StmtsParser::new(),
+            expr_parser: crate::grammar::ExprRefParser::new(),
             ast: RefCell::new(ASTArena::new()),
         };
         for builtin in BUILTINS {
@@ -297,6 +305,7 @@ impl State {
             root_scope: RefCell::new(HashMap::new()),
             rand: RefCell::new(Box::new(SeededRNG::from_seed(seed))),
             parser: crate::grammar::StmtsParser::new(),
+            expr_parser: crate::grammar::ExprRefParser::new(),
             ast: RefCell::new(ASTArena::new()),
         };
         for builtin in BUILTINS {
@@ -327,7 +336,10 @@ impl State {
             }
         } else {
             match self.root_scope.borrow().get(&name.item) {
-                None => bail!("no such thing: {}", &self.ast.borrow()[name.item]),
+                None => {
+                    let span = self.ast.borrow().get_line(name.file, name.span);
+                    bail!("no such thing: {}\n{}", &self.ast.borrow()[name.item], span)
+                }
                 Some(ne) => Ok(ne.clone()),
             }
         }
@@ -378,22 +390,27 @@ impl State {
             let mut ast = self.ast.borrow_mut();
             self.parser.parse(&mut ast, file, lexed)
         };
-        let stmts = match stmts {
-            Ok(stmts) => stmts,
+        match stmts {
+            Ok(stmts) => {
+                for stmt in stmts {
+                    self.execute(&stmt, io::stdout())?;
+                }
+            },
             Err(err) => {
-                let with_puts = format!("puts {}", src);
-                let lexed = crate::lexer::tokens(&with_puts);
-                let file = self.ast.borrow_mut().add_file(src.to_string());
-                if let Ok(stmts) = self.parser.parse(&mut self.ast.borrow_mut(), file, lexed) {
-                    stmts
+                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())?;
                 } else {
                     bail!("{:?}", err);
                 }
             }
         };
-        for stmt in stmts {
-            self.execute(&stmt, io::stdout())?;
-        }
         Ok(())
     }