valhallac

Compiler for set-theoretic programming language.
git clone git://git.knutsen.co/valhallac
Log | Files | Refs | README | LICENSE

commit 955bac5df7d4815b1161916d097df7fe7c96c297
parent 5d67441adc65a9e2c9302b53b1cf5aab23ea29ae
Author: Demonstrandum <moi@knutsen.co>
Date:   Fri, 19 Jul 2019 01:02:20 +0100

Proper error handling.

Diffstat:
Msrc/syntax/ast.rs | 1-
Asrc/syntax/err.rs | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/syntax/mod.rs | 12+++++++++---
Msrc/syntax/operators.rs | 2++
Msrc/syntax/parser.rs | 50++++++++++++++++++++++++++++++++++++++++----------
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)]