Browse Source

change the file table repr for better errors

Getty Ritter 1 year ago
parent
commit
5cc4a63088
8 changed files with 199 additions and 139 deletions
  1. 1 1
      build.rs
  2. 0 47
      src/ast.rs
  3. 31 28
      src/builtins.rs
  4. 89 0
      src/core.rs
  5. 12 19
      src/errors.rs
  6. 60 37
      src/interp.rs
  7. 4 6
      src/lexer.rs
  8. 2 1
      tools/regenerate.rs

+ 1 - 1
build.rs

@@ -43,7 +43,7 @@ fn test_%PREFIX%() {
   let state = crate::interp::State::new();
   let source = include_str!(\"%ROOT%/tests/%PREFIX%.matzo\");
   let lexer = lexer::tokens(source);
-  let file = state.get_ast().borrow_mut().add_file(source.to_string());
+  let file = state.file_table.borrow_mut().add_file(\"input\".to_string(), source.to_string());
   let stmts = grammar::StmtsParser::new().parse(&mut state.get_ast().borrow_mut(), file, lexer);
   assert!(stmts.is_ok());
   let stmts = stmts.unwrap();

+ 0 - 47
src/ast.rs

@@ -5,7 +5,6 @@ pub type StrRef = string_interner::DefaultSymbol;
 pub type Name = Located<StrRef>;
 
 pub struct ASTArena {
-    files: Vec<String>,
     strings: string_interner::StringInterner,
     exprs: Vec<Expr>,
 }
@@ -19,7 +18,6 @@ impl Default for ASTArena {
 impl ASTArena {
     pub fn new() -> ASTArena {
         ASTArena {
-            files: Vec::new(),
             strings: string_interner::StringInterner::new(),
             exprs: vec![Expr::Nil],
         }
@@ -71,51 +69,6 @@ impl ASTArena {
         }
     }
 
-    pub fn add_file(&mut self, file: String) -> FileRef {
-        let idx = self.files.len();
-        self.files.push(file);
-        FileRef { idx }
-    }
-
-    pub fn get_file(&self, file: FileRef) -> &str {
-        &self.files[file.idx]
-    }
-
-    pub fn get_line(&self, file: FileRef, span: Span) -> String {
-        if !span.exists() {
-            return String::new();
-        }
-
-        let mut line_number = 1;
-        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' {
-                if i < span.start as usize {
-                    line_number += 1;
-                    start_of_line = i + 1;
-                }
-                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(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, " ")?;

+ 31 - 28
src/builtins.rs

@@ -1,4 +1,5 @@
 use crate::ast::*;
+use crate::core::Loc;
 use crate::errors::MatzoError;
 use crate::interp::*;
 
@@ -11,13 +12,15 @@ fn arity_error(func: &str, expected: usize, actual: &[ExprRef]) -> Result<Value,
         actual.len()
     );
     if actual.is_empty() {
-        Err(MatzoError::new(Span::empty(), msg))
+        panic!("should not be possible to express")
+        // Err(MatzoError::new(Span::empty(), msg))
     } else {
         let span = Span {
-            start: actual[0].span.start,
-            end: actual[actual.len() - 1].span.end,
+            start: actual[0].loc.span.start,
+            end: actual[actual.len() - 1].loc.span.end,
         };
-        Err(MatzoError::new(span, msg))
+        let file = actual[0].loc.file;
+        Err(MatzoError::new(Loc { span, file }, msg))
     }
 }
 
@@ -32,12 +35,12 @@ pub fn builtins() -> Vec<BuiltinFunc> {
                         let mut buf = String::new();
                         let num = state
                             .eval(*rep, env)?
-                            .as_num(&state.ast.borrow(), rep.span)?;
+                            .as_num(&state.ast.borrow(), rep.loc)?;
                         for _ in 0..num {
                             buf.push_str(
                                 state
                                     .eval(*expr, env)?
-                                    .as_str(&state.ast.borrow(), expr.span)?,
+                                    .as_str(&state.ast.borrow(), expr.loc)?,
                             );
                         }
                         Ok(Value::Lit(Literal::Str(buf)))
@@ -54,7 +57,7 @@ pub fn builtins() -> Vec<BuiltinFunc> {
                     if let [expr] = exprs {
                         let s = state.eval(*expr, env)?;
                         Ok(Value::Lit(Literal::Str(
-                            s.as_str(&state.ast.borrow(), expr.span)?.to_uppercase(),
+                            s.as_str(&state.ast.borrow(), expr.loc)?.to_uppercase(),
                         )))
                     } else {
                         arity_error("str/upper", 1, exprs)
@@ -69,7 +72,7 @@ pub fn builtins() -> Vec<BuiltinFunc> {
                     if let [expr] = exprs {
                         let s = state.eval(*expr, env)?;
                         Ok(Value::Lit(Literal::Str(titlecase::titlecase(
-                            s.as_str(&state.ast.borrow(), expr.span)?,
+                            s.as_str(&state.ast.borrow(), expr.loc)?,
                         ))))
                     } else {
                         arity_error("str/capitalize", 1, exprs)
@@ -84,7 +87,7 @@ pub fn builtins() -> Vec<BuiltinFunc> {
                     if let [expr] = exprs {
                         let s = state.eval(*expr, env)?;
                         Ok(Value::Lit(Literal::Str(
-                            s.as_str(&state.ast.borrow(), expr.span)?.to_lowercase(),
+                            s.as_str(&state.ast.borrow(), expr.loc)?.to_lowercase(),
                         )))
                     } else {
                         arity_error("str/lower", 1, exprs)
@@ -99,7 +102,7 @@ pub fn builtins() -> Vec<BuiltinFunc> {
                     let mut buf = String::new();
                     for expr in exprs {
                         let s = state.eval(*expr, env)?;
-                        buf.push_str(s.as_str(&state.ast.borrow(), expr.span)?);
+                        buf.push_str(s.as_str(&state.ast.borrow(), expr.loc)?);
                     }
                     Ok(Value::Lit(Literal::Str(buf)))
                 },
@@ -114,7 +117,7 @@ pub fn builtins() -> Vec<BuiltinFunc> {
                     let mut last_char = '\0';
                     for expr in exprs.iter() {
                         let s = state.eval(*expr, env)?;
-                        let s = s.as_str(&state.ast.borrow(), expr.span)?;
+                        let s = s.as_str(&state.ast.borrow(), expr.loc)?;
                         if !capitalized && !s.trim().is_empty() {
                             capitalized = true;
                             let mut chars = s.chars();
@@ -145,8 +148,8 @@ pub fn builtins() -> Vec<BuiltinFunc> {
             callback: Box::new(
                 |state: &State, exprs: &[ExprRef], env: &Env| -> Result<Value, MatzoError> {
                     if let [x, y] = exprs {
-                        let x = state.eval(*x, env)?.as_num(&state.ast.borrow(), x.span)?;
-                        let y = state.eval(*y, env)?.as_num(&state.ast.borrow(), y.span)?;
+                        let x = state.eval(*x, env)?.as_num(&state.ast.borrow(), x.loc)?;
+                        let y = state.eval(*y, env)?.as_num(&state.ast.borrow(), y.loc)?;
                         Ok(Value::Lit(Literal::Num(x + y)))
                     } else {
                         arity_error("add", 2, exprs)
@@ -159,8 +162,8 @@ pub fn builtins() -> Vec<BuiltinFunc> {
             callback: Box::new(
                 |state: &State, exprs: &[ExprRef], env: &Env| -> Result<Value, MatzoError> {
                     if let [x, y] = exprs {
-                        let x = state.eval(*x, env)?.as_num(&state.ast.borrow(), x.span)?;
-                        let y = state.eval(*y, env)?.as_num(&state.ast.borrow(), y.span)?;
+                        let x = state.eval(*x, env)?.as_num(&state.ast.borrow(), x.loc)?;
+                        let y = state.eval(*y, env)?.as_num(&state.ast.borrow(), y.loc)?;
                         Ok(Value::Lit(Literal::Num(x - y)))
                     } else {
                         arity_error("sub", 2, exprs)
@@ -173,8 +176,8 @@ pub fn builtins() -> Vec<BuiltinFunc> {
             callback: Box::new(
                 |state: &State, exprs: &[ExprRef], env: &Env| -> Result<Value, MatzoError> {
                     if let [x, y] = exprs {
-                        let x = state.eval(*x, env)?.as_num(&state.ast.borrow(), x.span)?;
-                        let y = state.eval(*y, env)?.as_num(&state.ast.borrow(), y.span)?;
+                        let x = state.eval(*x, env)?.as_num(&state.ast.borrow(), x.loc)?;
+                        let y = state.eval(*y, env)?.as_num(&state.ast.borrow(), y.loc)?;
                         Ok(Value::Lit(Literal::Num(x * y)))
                     } else {
                         arity_error("mul", 2, exprs)
@@ -189,7 +192,7 @@ pub fn builtins() -> Vec<BuiltinFunc> {
                     if let [expr] = exprs {
                         let tup = state.eval(*expr, env)?;
                         Ok(Value::Lit(Literal::Num(
-                            tup.as_tup(&state.ast.borrow(), expr.span)?.len() as i64,
+                            tup.as_tup(&state.ast.borrow(), expr.loc)?.len() as i64,
                         )))
                     } else {
                         arity_error("tuple/len", 1, exprs)
@@ -203,12 +206,12 @@ pub fn builtins() -> Vec<BuiltinFunc> {
                 |state: &State, exprs: &[ExprRef], env: &Env| -> Result<Value, MatzoError> {
                     if let [expr] = exprs {
                         let val = state.eval(*expr, env)?;
-                        let tup = val.as_tup(&state.ast.borrow(), expr.span)?;
+                        let tup = val.as_tup(&state.ast.borrow(), expr.loc)?;
                         let mut contents = Vec::new();
                         for elem in tup {
                             for th in state
                                 .hnf(elem)?
-                                .as_tup(&state.ast.borrow(), Span::empty())?
+                                .as_tup(&state.ast.borrow(), expr.loc)?
                             {
                                 contents.push(th.clone());
                             }
@@ -228,8 +231,8 @@ pub fn builtins() -> Vec<BuiltinFunc> {
                         let tup = state.eval(*tup_e, env)?;
                         let idx = state.eval(*idx_e, env)?;
                         state.hnf(
-                            &tup.as_tup(&state.ast.borrow(), tup_e.span)?
-                                [idx.as_num(&state.ast.borrow(), idx_e.span)? as usize],
+                            &tup.as_tup(&state.ast.borrow(), tup_e.loc)?
+                                [idx.as_num(&state.ast.borrow(), idx_e.loc)? as usize],
                         )
                     } else {
                         arity_error("tuple/index", 1, exprs)
@@ -243,10 +246,10 @@ pub fn builtins() -> Vec<BuiltinFunc> {
                 |state: &State, exprs: &[ExprRef], env: &Env| -> Result<Value, MatzoError> {
                     if let [tup, idx, new] = exprs {
                         let tup_val = state.eval(*tup, env)?;
-                        let tup = tup_val.as_tup(&state.ast.borrow(), tup.span)?;
+                        let tup = tup_val.as_tup(&state.ast.borrow(), tup.loc)?;
                         let idx = state
                             .eval(*idx, env)?
-                            .as_num(&state.ast.borrow(), idx.span)?;
+                            .as_num(&state.ast.borrow(), idx.loc)?;
 
                         let mut modified = Vec::with_capacity(tup.len());
                         for i in 0..idx {
@@ -272,9 +275,9 @@ pub fn builtins() -> Vec<BuiltinFunc> {
                         let tup = state.eval(*tup_e, env)?;
 
                         let mut result = Thunk::Expr(*init, env.clone());
-                        for t in tup.as_tup(&state.ast.borrow(), tup_e.span)? {
+                        for t in tup.as_tup(&state.ast.borrow(), tup_e.loc)? {
                             result = Thunk::Value(state.eval_closure(
-                                func.as_closure(&state.ast.borrow(), func_e.span)?,
+                                func.as_closure(&state.ast.borrow(), func_e.loc)?,
                                 vec![result, t.clone()],
                             )?);
                         }
@@ -295,8 +298,8 @@ pub fn builtins() -> Vec<BuiltinFunc> {
                         let tup = state.eval(*tup_e, env)?;
 
                         let mut new_tup = Vec::new();
-                        let closure = func.as_closure(&state.ast.borrow(), func_e.span)?;
-                        for t in tup.as_tup(&state.ast.borrow(), tup_e.span)? {
+                        let closure = func.as_closure(&state.ast.borrow(), func_e.loc)?;
+                        for t in tup.as_tup(&state.ast.borrow(), tup_e.loc)? {
                             new_tup
                                 .push(Thunk::Value(state.eval_closure(closure, vec![t.clone()])?));
                         }

+ 89 - 0
src/core.rs

@@ -10,6 +10,95 @@ pub struct Span {
     pub end: u32,
 }
 
+#[derive(Debug, Clone, Copy)]
+pub struct Loc {
+    pub span: Span,
+    pub file: FileRef,
+}
+
+#[derive(Debug)]
+pub enum FileSource {
+    File(String),
+    Repl(u32),
+}
+
+pub struct File {
+    pub source: FileSource,
+    pub content: String,
+}
+
+pub struct FileTable {
+    files: Vec<File>,
+    last_repl_line: u32,
+}
+
+impl FileTable {
+    pub fn new() -> FileTable {
+        FileTable {
+            files: Vec::new(),
+            last_repl_line: 0,
+        }
+    }
+
+    pub fn add_file(&mut self, path: String, content: String) -> FileRef {
+        self.add_file_from_source(FileSource::File(path), content)
+    }
+
+    pub fn add_repl_line(&mut self, content: String) -> FileRef {
+        let source = FileSource::Repl(self.last_repl_line);
+        self.last_repl_line += 1;
+        self.add_file_from_source(source, content)
+    }
+
+    fn add_file_from_source(&mut self, source: FileSource, content: String) -> FileRef {
+        let idx = self.files.len();
+        self.files.push(File { source, content });
+        FileRef { idx }
+    }
+
+    pub fn get_line(&self, loc: Loc) -> String {
+        if !loc.span.exists() {
+            return String::new();
+        }
+
+        let mut line_number = 1;
+        let mut start_of_line = 0;
+        let mut end_of_line = None;
+        let file = &self.files[loc.file.idx];
+        let span = loc.span;
+        let src = &file.content;
+
+        for (i, ch) in src.char_indices() {
+            if ch == '\n' {
+                if i < span.start as usize {
+                    line_number += 1;
+                    start_of_line = i + 1;
+                }
+                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(src.len());
+
+        if let FileSource::Repl(offset) = file.source {
+            line_number += offset;
+        }
+
+        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
+    }
+
+}
+
 impl Span {
     pub fn empty() -> Span {
         Span {

+ 12 - 19
src/errors.rs

@@ -1,38 +1,30 @@
-use crate::core::Span;
+use crate::core::{FileRef, Loc, Span};
 use crate::lexer;
 
 #[derive(Debug)]
 pub struct MatzoError {
     pub message: String,
-    pub span: Span,
+    pub loc: Loc,
     pub context: Vec<ContextLine>,
 }
 
 #[derive(Debug)]
 pub struct ContextLine {
     pub message: String,
-    pub span: Span,
+    pub loc: Loc,
 }
 
 impl MatzoError {
-    pub fn new(span: Span, message: String) -> MatzoError {
+    pub fn new(loc: Loc, message: String) -> MatzoError {
         MatzoError {
             message,
-            span,
+            loc,
             context: Vec::new(),
         }
     }
 
-    pub fn no_loc(message: String) -> MatzoError {
-        MatzoError {
-            message,
-            span: Span::empty(),
-            context: Vec::new(),
-        }
-    }
-
-    pub fn reposition(mut self, span: Span) -> MatzoError {
-        self.span = span;
+    pub fn reposition(mut self, loc: Loc) -> MatzoError {
+        self.loc = loc;
         self
     }
 
@@ -56,11 +48,12 @@ impl MatzoError {
     }
 
     pub fn from_parse_error(
+        file: FileRef,
         err: lalrpop_util::ParseError<usize, lexer::Token, lexer::LexerError>,
     ) -> Self {
         match err {
             lalrpop_util::ParseError::User { error } => {
-                MatzoError::new(error.range, "Unrecognized token".to_string())
+                MatzoError::new(Loc { span: error.range, file }, "Unrecognized token".to_string())
             }
             lalrpop_util::ParseError::UnrecognizedToken {
                 token: (start, tok, end),
@@ -73,7 +66,7 @@ impl MatzoError {
 
                 let expected = MatzoError::format_expected_list(expected);
                 MatzoError::new(
-                    span,
+                    Loc { span, file },
                     format!("Unexpected {}. Expected {}", tok.token_name(), expected),
                 )
             }
@@ -84,7 +77,7 @@ impl MatzoError {
                 };
                 let expected = MatzoError::format_expected_list(expected);
                 MatzoError::new(
-                    span,
+                    Loc { span, file },
                     format!("Unexpected end-of-file; expected {}", expected),
                 )
             }
@@ -98,7 +91,7 @@ impl MatzoError {
                     start: start as u32,
                     end: end as u32,
                 };
-                MatzoError::new(span, format!("Extra token {}", tok.token_name()))
+                MatzoError::new(Loc { span, file }, format!("Extra token {}", tok.token_name()))
             }
         }
     }

+ 60 - 37
src/interp.rs

@@ -1,6 +1,6 @@
 use crate::ast::*;
+use crate::core::{FileTable, Loc};
 use crate::errors::MatzoError;
-use crate::lexer::Span;
 use crate::rand::*;
 
 use anyhow::{bail, Error};
@@ -38,42 +38,42 @@ impl Value {
 
 impl Value {
     /// Convert this value to a Rust integer, failing otherwise
-    pub fn as_num(&self, ast: &ASTArena, span: Span) -> Result<i64, MatzoError> {
+    pub fn as_num(&self, ast: &ASTArena, loc: Loc) -> Result<i64, MatzoError> {
         match self {
             Value::Lit(Literal::Num(n)) => Ok(*n),
             _ => self.with_str(ast, |s| {
-                return Err(MatzoError::new(span, format!("Expected number, got {}", s)));
+                return Err(MatzoError::new(loc, format!("Expected number, got {}", s)));
             }),
         }
     }
 
     /// Convert this value to a Rust string, failing otherwise
-    pub fn as_str(&self, ast: &ASTArena, span: Span) -> Result<&str, MatzoError> {
+    pub fn as_str(&self, ast: &ASTArena, loc: Loc) -> Result<&str, MatzoError> {
         match self {
             Value::Lit(Literal::Str(s)) => Ok(s),
             _ => self.with_str(ast, |s| {
-                return Err(MatzoError::new(span, format!("Expected string, got {}", s)));
+                return Err(MatzoError::new(loc, format!("Expected string, got {}", s)));
             }),
         }
     }
 
     /// Convert this value to a Rust slice, failing otherwise
-    pub fn as_tup(&self, ast: &ASTArena, span: Span) -> Result<&[Thunk], MatzoError> {
+    pub fn as_tup(&self, ast: &ASTArena, loc: Loc) -> Result<&[Thunk], MatzoError> {
         match self {
             Value::Tup(vals) => Ok(vals),
             _ => self.with_str(ast, |s| {
-                return Err(MatzoError::new(span, format!("Expected tuple, got {}", s)));
+                return Err(MatzoError::new(loc, format!("Expected tuple, got {}", s)));
             }),
         }
     }
 
     /// Convert this value to a closure, failing otherwise
-    pub fn as_closure(&self, ast: &ASTArena, span: Span) -> Result<&Closure, MatzoError> {
+    pub fn as_closure(&self, ast: &ASTArena, loc: Loc) -> Result<&Closure, MatzoError> {
         match self {
             Value::Closure(closure) => Ok(closure),
             _ => self.with_str(ast, |s| {
                 return Err(MatzoError::new(
-                    span,
+                    loc,
                     format!("Expected closure, got {}", s),
                 ));
             }),
@@ -84,7 +84,7 @@ impl Value {
     /// this value. Note that this _will not force the value_ if it's
     /// not completely forced already: indeed, this can't, since it
     /// doesn't have access to the `State`. Unevaluated fragments of
-    /// the value will be printed as `#<unevaluated>`.
+    /// the value will be printed as `...`.
     pub fn with_str<U>(&self, ast: &ASTArena, f: impl FnOnce(&str) -> U) -> U {
         match self {
             Value::Nil => f(""),
@@ -100,7 +100,7 @@ impl Value {
                     }
                     match val {
                         Thunk::Value(v) => buf.push_str(&v.to_string(ast)),
-                        Thunk::Expr(..) => buf.push_str("#<unevaluated>"),
+                        Thunk::Expr(..) => buf.push_str("..."),
                         Thunk::Builtin(func) => buf.push_str(&format!("#<builtin {}>", func.idx)),
                     }
                 }
@@ -147,6 +147,16 @@ pub enum Thunk {
     Builtin(BuiltinRef),
 }
 
+impl Thunk {
+    pub fn with_str<U>(&self, ast: &ASTArena, f: impl FnOnce(&str) -> U) -> U {
+        match self {
+            Thunk::Expr(_, _) => f("..."),
+            Thunk::Value(v) => v.with_str(ast, f),
+            Thunk::Builtin(b) => f(&format!("#<builtin {}", b.name))
+        }
+    }
+}
+
 /// An environment is either `None` (i.e. in the root scope) or `Some`
 /// of some reference-counted scope (since those scopes might be
 /// shared in several places, e.g. as pointers in thunks or closures).
@@ -184,6 +194,8 @@ pub struct State {
     /// The root scope of the program, which contains all the
     /// top-level definitions and builtins.
     root_scope: RefCell<HashMap<StrRef, Thunk>>,
+    /// The file table which contains the raw sources
+    pub file_table: RefCell<FileTable>,
     /// The set of builtin (i.e. implemented-in-Rust) functions
     builtins: Vec<BuiltinFunc>,
     /// The thread-local RNG.
@@ -206,6 +218,7 @@ impl State {
     fn new_with_rand(rand: Box<dyn MatzoRand>) -> State {
         let mut s = State {
             root_scope: RefCell::new(HashMap::new()),
+            file_table: RefCell::new(FileTable::new()),
             rand: RefCell::new(rand),
             parser: crate::grammar::StmtsParser::new(),
             expr_parser: crate::grammar::ExprRefParser::new(),
@@ -259,7 +272,7 @@ impl State {
         } else {
             match self.root_scope.borrow().get(&name.item) {
                 None => Err(MatzoError::new(
-                    name.span,
+                    name.loc,
                     format!("Undefined name {}", &self.ast.borrow()[name.item]),
                 )),
                 Some(ne) => Ok(ne.clone()),
@@ -273,15 +286,15 @@ impl State {
         self.run_with_writer(src, &mut io::stdout())
     }
 
-    fn print_error(&self, file: FileRef, mtz: MatzoError) -> String {
+    fn print_error(&self, 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));
+        buf.push_str(&self.file_table.borrow().get_line(mtz.loc));
         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.push_str(&self.file_table.borrow().get_line(ctx.loc));
         }
         buf
     }
@@ -289,9 +302,9 @@ impl State {
     /// 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());
+        let file = self.file_table.borrow_mut().add_file("???".to_owned(), src.to_string());
         if let Err(mtz) = self.run_file(src, file, w) {
-            bail!("{}", self.print_error(file, mtz));
+            bail!("{}", self.print_error(mtz));
         }
         Ok(())
     }
@@ -304,7 +317,7 @@ impl State {
     ) -> Result<(), MatzoError> {
         let lexed = crate::lexer::tokens(src);
         let stmts = self.parser.parse(&mut self.ast.borrow_mut(), file, lexed);
-        let stmts = stmts.map_err(MatzoError::from_parse_error)?;
+        let stmts = stmts.map_err(|e| MatzoError::from_parse_error(file, e))?;
         for stmt in stmts {
             self.execute(&stmt, &mut w)?;
         }
@@ -313,7 +326,7 @@ impl State {
 
     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 file = self.file_table.borrow_mut().add_repl_line(src.to_string());
         let stmts = {
             let mut ast = self.ast.borrow_mut();
             self.parser.parse(&mut ast, file, lexed)
@@ -332,7 +345,7 @@ impl State {
                 if let Ok(expr) = expr {
                     Ok(vec![Stmt::Puts(expr)])
                 } else {
-                    Err(MatzoError::from_parse_error(err))
+                    Err(MatzoError::from_parse_error(file, err))
                 }
             }
         }
@@ -342,13 +355,11 @@ impl State {
     /// 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.
+    /// work it tries parsing it as an expression instead of a
+    /// statement and then printing the result.
     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));
+            bail!("{}", self.print_error(mtz));
         }
         Ok(())
     }
@@ -444,19 +455,14 @@ impl State {
                         Choice {
                             weight: None,
                             value: Located {
-                                file: s.file,
-                                span: s.span,
+                                loc: s.loc,
                                 item: self.ast.borrow_mut().add_expr(Expr::Lit(Literal::Str(str))),
                             },
                         }
                     })
                     .collect();
                 let choices = Located {
-                    file: choices.first().unwrap().value.file,
-                    span: Span {
-                        start: choices.first().unwrap().value.span.start,
-                        end: choices.last().unwrap().value.span.end,
-                    },
+                    loc: choices.first().unwrap().value.loc,
                     item: self.ast.borrow_mut().add_expr(Expr::Chc(choices)),
                 };
                 self.root_scope
@@ -555,8 +561,8 @@ impl State {
             Expr::Range(from, to) => {
                 let from = self
                     .eval(*from, env)?
-                    .as_num(&self.ast.borrow(), from.span)?;
-                let to = self.eval(*to, env)?.as_num(&self.ast.borrow(), to.span)?;
+                    .as_num(&self.ast.borrow(), from.loc)?;
+                let to = self.eval(*to, env)?.as_num(&self.ast.borrow(), to.loc)?;
                 Ok(Value::Lit(Literal::Num(
                     self.rand.borrow_mut().gen_range_i64(from, to + 1),
                 )))
@@ -583,7 +589,7 @@ impl State {
                     (builtin.callback)(self, vals, env)
                 }
                 _ => Err(MatzoError::new(
-                    expr_ref.span,
+                    expr_ref.loc,
                     "Trying to call a non-function".to_string(),
                 )),
             },
@@ -705,9 +711,26 @@ impl State {
         }
 
         // we couldn't find a matching pattern, so throw an error
+        let mut buf = String::new();
+        if scruts.len() != 1 {
+            let arena = self.ast.borrow();
+            buf.push('[');
+            for (i, scrut) in scruts.iter().enumerate() {
+                if i != 0 {
+                    buf.push_str(", ");
+                }
+                scrut.with_str(&arena, |s| buf.push_str(s));
+            }
+            buf.push(']');
+        } else {
+            scruts[0].with_str(
+                &self.ast.borrow(),
+                |s| buf.push_str(s),
+            );
+        }
         Err(MatzoError::new(
-            Span::empty(),
-            format!("No pattern matched {:?}", scruts),
+            closure.func.loc,
+            format!("No pattern matched {}", buf),
         ))
     }
 

+ 4 - 6
src/lexer.rs

@@ -1,16 +1,15 @@
-pub use crate::core::{FileRef, Span};
+pub use crate::core::{FileRef, Loc, Span};
 use logos::{Lexer, Logos};
 
 #[derive(Debug, Clone, Copy)]
 pub struct Located<T> {
     pub item: T,
-    pub span: Span,
-    pub file: FileRef,
+    pub loc: Loc,
 }
 
 impl<T> Located<T> {
     pub fn new(item: T, file: FileRef, span: Span) -> Located<T> {
-        Located { span, file, item }
+        Located { loc: Loc { file, span} , item }
     }
 }
 
@@ -18,8 +17,7 @@ impl<T: Clone> Located<T> {
     pub fn map<R>(&self, func: impl FnOnce(T) -> R) -> Located<R> {
         Located {
             item: func(self.item.clone()),
-            span: self.span,
-            file: self.file,
+            loc: self.loc,
         }
     }
 }

+ 2 - 1
tools/regenerate.rs

@@ -39,10 +39,11 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
                 f
             };
 
+            let mut files = matzo::core::FileTable::new();
             let mut ast = matzo::ast::ASTArena::new();
             let src = std::fs::read_to_string(&exp)?;
             let tokens = lexer::tokens(&src);
-            let file = ast.add_file(src.to_string());
+            let file = files.add_file(fname.to_string(), src.to_string());
             if let Ok(stmts) = grammar::StmtsParser::new().parse(&mut ast, file, tokens) {
                 let mut f = std::fs::File::create(exp_filename("parsed"))?;
                 for stmt in stmts {