commit 955bac5df7d4815b1161916d097df7fe7c96c297
parent 5d67441adc65a9e2c9302b53b1cf5aab23ea29ae
Author: Demonstrandum <moi@knutsen.co>
Date: Fri, 19 Jul 2019 01:02:20 +0100
Proper error handling.
Diffstat:
5 files changed, 104 insertions(+), 14 deletions(-)
diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs
@@ -1,4 +1,3 @@
-use std::convert::TryFrom;
use std::fmt;
/// Identifiers, node representing a name that
diff --git a/src/syntax/err.rs b/src/syntax/err.rs
@@ -0,0 +1,53 @@
+use super::token;
+
+use std::fs;
+use std::fmt;
+use std::io::{BufRead, BufReader};
+
+pub enum Types {
+ LexError,
+ ParseError,
+ TypeError,
+}
+
+impl fmt::Display for Types {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let printable = match *self {
+ Types::LexError => "Lexicographical Error",
+ Types::ParseError => "Grammar Error",
+ Types::TypeError => "Typing Error"
+ };
+ write!(f, "{}", printable)
+ }
+}
+
+pub fn issue(class : Types, filename : &str, token : &token::Token, message : &str) {
+ let file = fs::File::open(filename).expect("Invalid filename for error message.");
+ let line = BufReader::new(file).lines().nth((token.location.line - 1) as usize).unwrap().unwrap();
+
+ eprintln!("{}", message);
+ eprintln!(" ==> {class} in (`{file}`:{line}:{col}):\n{space}|\n{line_str}| {stuff}",
+ class=class, file=filename, line=token.location.line,
+ col=token.location.col, space=" ".repeat(5),
+ line_str=format!("{: >4} ", token.location.line), stuff=line);
+ eprintln!("{space}|{: >offset$}",
+ "^".repeat(token.location.span as usize), space=" ".repeat(5),
+ offset=((token.location.col + token.location.span) as usize));
+}
+
+#[macro_export]
+macro_rules! issue {
+ ($type:path, $file:expr, $token:expr, $message:expr) => {
+ {
+ super::err::issue($type, $file, $token, $message);
+ std::process::exit(1)
+ }
+ };
+ ($type:path, $file:expr, $token:expr, $message:expr, $($form:expr),*) => {
+ {
+ super::err::issue($type, $file, $token, &format!($message, $($form),*));
+ std::process::exit(1)
+ }
+ };
+}
+
diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs
@@ -1,3 +1,5 @@
+//! Syntax, parsing and analysis.
+
/// location manages line and column location of
/// lexical tokens as well as their span.
mod location;
@@ -11,6 +13,10 @@ mod ast;
/// Dealing with associativity and precedence.
mod operators;
+/// Error messages.
+#[macro_use]
+mod err;
+
/// Lexer splits code up into a token-stream
/// of relevant lexical tokens, making the
/// parsing step a lot easier.
@@ -24,14 +30,14 @@ use token::ShowStream;
/// Parses a given file, calling various methods from
/// the `syntax` sub-module.
-pub fn parse_file(filename : &str) {
+pub fn parse_file(filename : &'static str) {
let code = fs::read_to_string(filename)
.expect("Could not open file for reading.");
println!("Code:\n{}\n", code);
- let mut stream = lexer::lex(&code);
+ let stream = lexer::lex(&code);
println!("Stream:\n{}\n", stream.to_string());
- let tree = parser::parse(stream);
+ let tree = parser::parse(stream, filename);
println!("AST:\n{}\n", tree);
}
\ No newline at end of file
diff --git a/src/syntax/operators.rs b/src/syntax/operators.rs
@@ -100,6 +100,8 @@ impl PrecedenceTable {
op("unless", 20, Side::Neither, 2),
op( ",", 10, Side::Right, 2),
op( "=>", 1, Side::Neither, 2),
+ op( "(", 0, Side::Neither, 1),
+ op( ")", 0, Side::Neither, 1),
]};
table
diff --git a/src/syntax/parser.rs b/src/syntax/parser.rs
@@ -1,12 +1,13 @@
use super::token;
use super::ast;
use super::operators;
+use super::err;
use token::{Token, TokenType};
-use ast::Nodes;
+use ast::{Nodes, Numerics};
-pub fn parse(stream : Vec<Token>) -> ast::Root {
- let mut environment = ParseEnvironment::new(stream);
+pub fn parse(stream : Vec<Token>, file : &'static str) -> ast::Root {
+ let mut environment = ParseEnvironment::new(stream, file);
environment.optable.new_fun("max", 4);
environment.start();
@@ -17,15 +18,17 @@ pub fn parse(stream : Vec<Token>) -> ast::Root {
struct ParseEnvironment {
pub root : ast::Root,
pub stream : Vec<Token>,
- pub optable : operators::PrecedenceTable
+ pub optable : operators::PrecedenceTable,
+ pub file : &'static str
}
impl ParseEnvironment {
- pub fn new(stream : Vec<Token>) -> Self {
+ pub fn new(stream : Vec<Token>, file : &'static str) -> Self {
ParseEnvironment {
root: ast::Root::new(),
stream: stream,
- optable: operators::PrecedenceTable::new()
+ optable: operators::PrecedenceTable::new(),
+ file
}
}
@@ -47,15 +50,23 @@ impl ParseEnvironment {
match token.class {
TokenType::Ident => ast::IdentNode::new(&token.string),
TokenType::Op => { // Prefix Op.
- let op = self.optable.lookup(&token.string, 1);
- if op.is_some() {
+ let is_op = self.optable.exists(&token.string);
+ if is_op {
return ast::CallNode::new(ast::IdentNode::new(&token.string), vec![self.expr(300)]);
}
- return panic!("`{}` is not a prefix operator.", token.string);
+ issue!(err::Types::ParseError, self.file, token,
+ "`{}` is not an operator.", token.string);
},
TokenType::Num => ast::NumNode::new(&*token.string),
TokenType::Str => ast::StrNode::new(&token.string),
- _ => panic!("Passed non-atomic token to `atom` parser.")
+ TokenType::LParen => {
+ let expr = self.expr(0);
+ self.expect(TokenType::RParen, self.stream.get(0));
+ self.stream.remove(0);
+ expr
+ }
+ _ => issue!(err::Types::ParseError, self.file, token,
+ "`{}` has no null-denotation.", token.class)
}
}
@@ -63,6 +74,13 @@ impl ParseEnvironment {
let popped = &self.stream.remove(0);
let mut left = self.null_den(popped);
+ if self.stream.is_empty() { return left; }
+
+ if self.optable.exists(&self.stream[0].string) {
+ return issue!(err::Types::ParseError, self.file, &self.stream[0],
+ "`{}` is not a binary operator.", &self.stream[0].string);
+ }
+
while self.optable.precedence(&self.stream[0].string).unwrap_or(0) > right_prec {
if self.stream[0].class == TokenType::EOF { break; }
let op = self.optable.lookup(&self.stream.remove(0).string, 2).unwrap();
@@ -75,6 +93,18 @@ impl ParseEnvironment {
let right = self.expr(op.precedence - (if op.is_right() { 1 } else { 0 }));
ast::CallNode::new(ast::IdentNode::new(op.name), vec![left, right])
}
+
+ fn expect(&self, tt : TokenType, maybe_t : Option<&Token>) {
+ if maybe_t.is_none() {
+ issue!(err::Types::ParseError, self.file, self.stream.last().unwrap(),
+ "Unexpected end of stream.");
+ }
+ let t = maybe_t.unwrap();
+ if t.class != tt {
+ issue!(err::Types::ParseError, self.file, t,
+ "Unexpected token type: `{}`, expected: `{}`.", t.class, tt);
+ }
+ }
}
#[cfg(test)]