From cabbe3c8eaf0bef73054c904e63a10996ce30a1d Mon Sep 17 00:00:00 2001 From: parazyd Date: Thu, 3 Feb 2022 14:17:15 +0100 Subject: [PATCH] zkas: Unify error functions into an error emitter. --- src/zkas/analyzer.rs | 62 +++++++--------------------- src/zkas/compiler.rs | 45 +++++--------------- src/zkas/error.rs | 41 ++++++++++++++++++ src/zkas/lexer.rs | 54 +++++++++--------------- src/zkas/mod.rs | 2 + src/zkas/parser.rs | 98 ++++++++++++++++++-------------------------- 6 files changed, 127 insertions(+), 175 deletions(-) create mode 100644 src/zkas/error.rs diff --git a/src/zkas/analyzer.rs b/src/zkas/analyzer.rs index 1f823b1d0..0d11971b7 100644 --- a/src/zkas/analyzer.rs +++ b/src/zkas/analyzer.rs @@ -1,27 +1,23 @@ use std::{ - io, io::{stdin, stdout, Read, Write}, - process, str::Chars, }; -use termion::{color, style}; - use super::{ ast::{ Constant, Constants, StatementType, Statements, Var, Variable, Variables, Witness, Witnesses, }, + error::ErrorEmitter, types::Type, }; pub struct Analyzer { - file: String, - lines: Vec, pub constants: Constants, pub witnesses: Witnesses, pub statements: Statements, pub stack: Variables, + error: ErrorEmitter, } impl Analyzer { @@ -34,15 +30,10 @@ impl Analyzer { ) -> Self { // For nice error reporting, we'll load everything into a string // vector so we have references to lines. - let lines = source.as_str().lines().map(|x| x.to_string()).collect(); - Analyzer { - file: filename.to_string(), - lines, - constants, - witnesses, - statements, - stack: vec![], - } + let lines: Vec = source.as_str().lines().map(|x| x.to_string()).collect(); + let error = ErrorEmitter::new("Semantic", filename, lines.clone()); + + Analyzer { constants, witnesses, statements, stack: vec![], error } } pub fn analyze_types(&mut self) { @@ -62,7 +53,7 @@ impl Analyzer { // It's kinda ugly. if arg_types[0] == Type::BaseArray || arg_types[0] == Type::ScalarArray { if statement.args.is_empty() { - self.error( + self.error.emit( format!( "Passed no arguments to `{:?}` call. Expected at least 1.", statement.opcode @@ -81,7 +72,7 @@ impl Analyzer { }; if arg_types[0] == Type::BaseArray && var_type != Type::Base { - self.error( + self.error.emit( format!( "Incorrect argument type. Expected `{:?}`, got `{:?}`", arg_types[0], @@ -93,7 +84,7 @@ impl Analyzer { } if arg_types[0] == Type::ScalarArray && var_type != Type::Scalar { - self.error( + self.error.emit( format!( "Incorrect argument type. Expected `{:?}`, got `{:?}`", arg_types[0], @@ -108,7 +99,7 @@ impl Analyzer { arg.typ = var_type; args.push(arg); } else { - self.error( + self.error.emit( format!("Unknown argument reference `{}`.", i.name), i.line, i.column, @@ -117,7 +108,7 @@ impl Analyzer { } } else { if statement.args.len() != arg_types.len() { - self.error( + self.error.emit( format!( "Incorrent number of args to `{:?}` call. Expected {}, got {}", statement.opcode, @@ -138,7 +129,7 @@ impl Analyzer { }; if var_type != arg_types[idx] { - self.error( + self.error.emit( format!( "Incorrect argument type. Expected `{:?}`, got `{:?}`", arg_types[idx], var_type, @@ -152,7 +143,7 @@ impl Analyzer { arg.typ = var_type; args.push(arg); } else { - self.error( + self.error.emit( format!("Unknown argument reference `{}`.", i.name), i.line, i.column, @@ -213,7 +204,7 @@ impl Analyzer { if let Some(index) = stack.iter().position(|&r| r == &arg.name) { println!("Found at stack index {}", index); } else { - self.error( + self.error.emit( format!("Could not find `{}` on the stack", arg.name), arg.line, arg.column, @@ -283,31 +274,6 @@ impl Analyzer { None } - fn error(&self, msg: String, ln: usize, col: usize) { - let err_msg = format!("{} (line {}, column {})", msg, ln, col); - let dbg_msg = format!("{}:{}:{}: {}", self.file, ln, col, self.lines[ln - 1]); - let pad = dbg_msg.split(": ").next().unwrap().len() + col + 2; - let caret = format!("{:width$}^", "", width = pad); - let msg = format!("{}\n{}\n{}\n", err_msg, dbg_msg, caret); - Analyzer::abort(&msg); - } - - fn abort(msg: &str) { - let stderr = io::stderr(); - let mut handle = stderr.lock(); - write!( - handle, - "{}{}Semantic error:{} {}", - style::Bold, - color::Fg(color::Red), - style::Reset, - msg, - ) - .unwrap(); - handle.flush().unwrap(); - process::exit(1); - } - fn pause() { let msg = b"[Press Enter to continue]\r"; let mut stdout = stdout(); diff --git a/src/zkas/compiler.rs b/src/zkas/compiler.rs index c462ef113..73180bb9c 100644 --- a/src/zkas/compiler.rs +++ b/src/zkas/compiler.rs @@ -1,8 +1,9 @@ -use std::{io, io::Write, process, str::Chars}; +use std::str::Chars; -use termion::{color, style}; - -use super::ast::{Constants, StatementType, Statements, Witnesses}; +use super::{ + ast::{Constants, StatementType, Statements, Witnesses}, + error::ErrorEmitter, +}; use crate::util::serial::{serialize, VarInt}; /// Version of the binary @@ -11,12 +12,11 @@ pub const BINARY_VERSION: u8 = 1; pub const MAGIC_BYTES: [u8; 4] = [0x0b, 0x00, 0xb1, 0x35]; pub struct Compiler { - file: String, - lines: Vec, constants: Constants, witnesses: Witnesses, statements: Statements, debug_info: bool, + error: ErrorEmitter, } impl Compiler { @@ -30,8 +30,10 @@ impl Compiler { ) -> Self { // For nice error reporting, we'll load everything into a string // vector so we have references to lines. - let lines = source.as_str().lines().map(|x| x.to_string()).collect(); - Compiler { file: filename.to_string(), lines, constants, witnesses, statements, debug_info } + let lines: Vec = source.as_str().lines().map(|x| x.to_string()).collect(); + let error = ErrorEmitter::new("Compiler", filename, lines.clone()); + + Compiler { constants, witnesses, statements, debug_info, error } } pub fn compile(&self) -> Vec { @@ -77,7 +79,7 @@ impl Compiler { continue } - self.error( + self.error.emit( format!("Failed finding a stack reference for `{}`", arg.name), arg.line, arg.column, @@ -104,29 +106,4 @@ impl Compiler { None } - - fn error(&self, msg: String, ln: usize, col: usize) { - let err_msg = format!("{} (line {}, column {})", msg, ln, col); - let dbg_msg = format!("{}:{}:{}: {}", self.file, ln, col, self.lines[ln - 1]); - let pad = dbg_msg.split(": ").next().unwrap().len() + col + 2; - let caret = format!("{:width$}^", "", width = pad); - let msg = format!("{}\n{}\n{}\n", err_msg, dbg_msg, caret); - Compiler::abort(&msg); - } - - fn abort(msg: &str) { - let stderr = io::stderr(); - let mut handle = stderr.lock(); - write!( - handle, - "{}{}Compiler error:{} {}", - style::Bold, - color::Fg(color::Red), - style::Reset, - msg, - ) - .unwrap(); - handle.flush().unwrap(); - process::exit(1); - } } diff --git a/src/zkas/error.rs b/src/zkas/error.rs new file mode 100644 index 000000000..eed3f6947 --- /dev/null +++ b/src/zkas/error.rs @@ -0,0 +1,41 @@ +use std::{io, io::Write, process}; + +use termion::{color, style}; + +pub(super) struct ErrorEmitter { + namespace: String, + file: String, + lines: Vec, +} + +impl ErrorEmitter { + pub fn new(namespace: &str, file: &str, lines: Vec) -> Self { + Self { namespace: namespace.to_string(), file: file.to_string(), lines } + } + + pub fn emit(&self, msg: String, ln: usize, col: usize) { + let err_msg = format!("{} (line{}, column {})", msg, ln, col); + let dbg_msg = format!("{}:{}:{}: {}", self.file, ln, col, self.lines[ln - 1]); + let pad = dbg_msg.split(": ").next().unwrap().len() + col + 2; + let caret = format!("{:width$}^", "", width = pad); + let msg = format!("{}\n{}\n{}\n", err_msg, dbg_msg, caret); + self.abort(&msg); + } + + fn abort(&self, msg: &str) { + let stderr = io::stderr(); + let mut handle = stderr.lock(); + write!( + handle, + "{}{}{} error:{} {}", + style::Bold, + color::Fg(color::Red), + self.namespace, + style::Reset, + msg, + ) + .unwrap(); + handle.flush().unwrap(); + process::exit(1); + } +} diff --git a/src/zkas/lexer.rs b/src/zkas/lexer.rs index 50a18bfbd..1106292c8 100644 --- a/src/zkas/lexer.rs +++ b/src/zkas/lexer.rs @@ -1,6 +1,6 @@ -use std::{io, io::Write, process, str::Chars}; +use std::str::Chars; -use termion::{color, style}; +use super::error::ErrorEmitter; #[derive(Hash, Eq, PartialEq, Clone, Debug)] pub enum TokenType { @@ -33,9 +33,8 @@ impl Token { } pub struct Lexer<'a> { - file: String, - lines: Vec, source: Chars<'a>, + error: ErrorEmitter, } impl<'a> Lexer<'a> { @@ -43,7 +42,9 @@ impl<'a> Lexer<'a> { // For nice error reporting, we'll load everything into a string // vector so we have references to lines. let lines: Vec = source.as_str().lines().map(|x| x.to_string()).collect(); - Lexer { file: filename.to_string(), lines, source } + let error = ErrorEmitter::new("Lexer", filename, lines.clone()); + + Self { source, error } } pub fn lex(self) -> Vec { @@ -78,7 +79,11 @@ impl<'a> Lexer<'a> { if in_string { // TODO: Allow newlines in strings? - self.error(format!("Invalid ending in string `{}`", &strbuf), lineno, column); + self.error.emit( + format!("Invalid ending in string `{}`", &strbuf), + lineno, + column, + ); } in_comment = false; @@ -141,7 +146,7 @@ impl<'a> Lexer<'a> { if c == '"' && !in_string { if in_symbol { - self.error(format!("Illegal char `{}` for symbol", c), lineno, column); + self.error.emit(format!("Illegal char `{}` for symbol", c), lineno, column); } in_string = true; continue @@ -149,7 +154,11 @@ impl<'a> Lexer<'a> { if c == '"' && in_string { if strbuf.is_empty() { - self.error(format!("Invalid ending in string `{}`", &strbuf), lineno, column); + self.error.emit( + format!("Invalid ending in string `{}`", &strbuf), + lineno, + column, + ); } in_string = false; @@ -229,41 +238,16 @@ impl<'a> Lexer<'a> { tokens.push(Token::new("=".to_string(), TokenType::Assign, lineno, column)); continue } - _ => self.error(format!("Invalid token `{}`", c), lineno, column - 1), + _ => self.error.emit(format!("Invalid token `{}`", c), lineno, column - 1), } continue } - self.error(format!("Invalid token `{}`", c), lineno, column - 1); + self.error.emit(format!("Invalid token `{}`", c), lineno, column - 1); } tokens } - - fn error(&self, msg: String, ln: usize, col: usize) { - let err_msg = format!("{} (line {}, column {})", msg, ln, col); - let dbg_msg = format!("{}:{}:{}: {}", self.file, ln, col, self.lines[ln - 1]); - let pad = dbg_msg.split(": ").next().unwrap().len() + col + 2; - let caret = format!("{:width$}^", "", width = pad); - let msg = format!("{}\n{}\n{}\n", err_msg, dbg_msg, caret); - Lexer::abort(&msg); - } - - fn abort(msg: &str) { - let stderr = io::stderr(); - let mut handle = stderr.lock(); - write!( - handle, - "{}{}Lexer error:{} {}", - style::Bold, - color::Fg(color::Red), - style::Reset, - msg, - ) - .unwrap(); - handle.flush().unwrap(); - process::exit(1); - } } fn is_letter(ch: char) -> bool { diff --git a/src/zkas/mod.rs b/src/zkas/mod.rs index e86d9a487..c05e4b267 100644 --- a/src/zkas/mod.rs +++ b/src/zkas/mod.rs @@ -6,6 +6,8 @@ pub mod ast; pub mod compiler; /// Binary decoder pub mod decoder; +/// Error emitter +mod error; /// Lexer module pub mod lexer; /// Language opcodes diff --git a/src/zkas/parser.rs b/src/zkas/parser.rs index ff2c13625..3ac3e455c 100644 --- a/src/zkas/parser.rs +++ b/src/zkas/parser.rs @@ -1,31 +1,32 @@ -use std::{io, io::Write, iter::Peekable, process, str::Chars}; +use std::{iter::Peekable, str::Chars}; use indexmap::IndexMap; use itertools::Itertools; -use termion::{color, style}; use super::{ ast::{ Constant, Constants, Statement, StatementType, Statements, UnparsedConstants, UnparsedWitnesses, Variable, Witness, Witnesses, }, + error::ErrorEmitter, lexer::{Token, TokenType}, opcode::Opcode, types::Type, }; pub struct Parser { - file: String, - lines: Vec, tokens: Vec, + error: ErrorEmitter, } impl Parser { pub fn new(filename: &str, source: Chars, tokens: Vec) -> Self { // For nice error reporting, we'll load everything into a string // vector so we have references to lines. - let lines = source.as_str().lines().map(|x| x.to_string()).collect(); - Parser { file: filename.to_string(), lines, tokens } + let lines: Vec = source.as_str().lines().map(|x| x.to_string()).collect(); + let error = ErrorEmitter::new("Parser", filename, lines.clone()); + + Parser { tokens, error } } pub fn parse(self) -> (Constants, Witnesses, Statements) { @@ -93,7 +94,9 @@ impl Parser { } } - x => self.error(format!("Unknown `{}` proof section", x), t.line, t.column), + x => { + self.error.emit(format!("Unknown `{}` proof section", x), t.line, t.column) + } } } @@ -114,7 +117,7 @@ impl Parser { // TODO: Do we need this? if namespace_found && namespace != constant_tokens[0].token { - self.error( + self.error.emit( format!( "Found `{}` namespace. Expected `{}`.", constant_tokens[0].token, namespace @@ -134,7 +137,7 @@ impl Parser { while let Some((typ, name, comma)) = constants_inner.next_tuple() { if comma.token_type != TokenType::Comma { - self.error( + self.error.emit( "Separator is not a comma".to_string(), comma.line, comma.column, @@ -142,7 +145,7 @@ impl Parser { } if constants_map.contains_key(name.token.as_str()) { - self.error( + self.error.emit( format!( "Section `constant` already contains the token `{}`.", &name.token @@ -164,7 +167,7 @@ impl Parser { // TODO: Do we need this? if namespace_found && namespace != contract_tokens[0].token { - self.error( + self.error.emit( format!( "Found `{}` namespace. Expected `{}`.", contract_tokens[0].token, namespace @@ -184,7 +187,7 @@ impl Parser { while let Some((typ, name, comma)) = contract_inner.next_tuple() { if comma.token_type != TokenType::Comma { - self.error( + self.error.emit( "Separator is not a comma".to_string(), comma.line, comma.column, @@ -192,7 +195,7 @@ impl Parser { } if contract_map.contains_key(name.token.as_str()) { - self.error( + self.error.emit( format!( "Section `contract` already contains the token `{}`.", &name.token @@ -213,7 +216,7 @@ impl Parser { self.check_section_structure("circuit", contract_tokens.clone()); if circuit_tokens[circuit_tokens.len() - 2].token_type != TokenType::Semicolon { - self.error( + self.error.emit( "Circuit section does not end with a semicolon. Would never finish parsing.".to_string(), circuit_tokens[circuit_tokens.len()-2].line, circuit_tokens[circuit_tokens.len()-2].column @@ -222,7 +225,7 @@ impl Parser { // TODO: Do we need this? if namespace_found && namespace != circuit_tokens[0].token { - self.error( + self.error.emit( format!( "Found `{}` namespace. Expected `{}`.", circuit_tokens[0].token, namespace @@ -271,7 +274,7 @@ impl Parser { fn check_section_structure(&self, section: &str, tokens: Vec) { if tokens[0].token_type != TokenType::String { - self.error( + self.error.emit( format!("{} section declaration must start with a naming string.", section), tokens[0].line, tokens[0].column, @@ -279,7 +282,7 @@ impl Parser { } if tokens[1].token_type != TokenType::LeftBrace { - self.error( + self.error.emit( format!( "{} section opening is not correct. Must be opened with a left brace `{{`", section @@ -290,7 +293,7 @@ impl Parser { } if tokens[tokens.len() - 1].token_type != TokenType::RightBrace { - self.error( + self.error.emit( format!( "{} section closing is not correct. Must be closed with a right brace `}}`", section @@ -303,7 +306,7 @@ impl Parser { if (section == "constant" || section == "contract") && tokens[2..tokens.len() - 1].len() % 3 != 0 { - self.error( + self.error.emit( format!( "Invalid number of elements in `{}` section. Must be pairs of `type:name` separated with a comma `,`", section @@ -319,7 +322,7 @@ impl Parser { for (k, v) in ast { if &v.0.token != k { - self.error( + self.error.emit( format!("Constant name `{}` doesn't match token `{}`.", v.0.token, k), v.0.line, v.0.column, @@ -327,7 +330,7 @@ impl Parser { } if v.0.token_type != TokenType::Symbol { - self.error( + self.error.emit( format!("Constant name `{}` is not a symbol.", v.0.token), v.0.line, v.0.column, @@ -335,7 +338,7 @@ impl Parser { } if v.1.token_type != TokenType::Symbol { - self.error( + self.error.emit( format!("Constant type `{}` is not a symbol.", v.1.token), v.1.line, v.1.column, @@ -353,7 +356,7 @@ impl Parser { } x => { - self.error( + self.error.emit( format!("`{}` is an illegal constant type", x), v.1.line, v.1.column, @@ -370,7 +373,7 @@ impl Parser { for (k, v) in ast { if &v.0.token != k { - self.error( + self.error.emit( format!("Witness name `{}` doesn't match token `{}`.", v.0.token, k), v.0.line, v.0.column, @@ -378,7 +381,7 @@ impl Parser { } if v.0.token_type != TokenType::Symbol { - self.error( + self.error.emit( format!("Witness name `{}` is not a symbol.", v.0.token), v.0.line, v.0.column, @@ -386,7 +389,7 @@ impl Parser { } if v.1.token_type != TokenType::Symbol { - self.error( + self.error.emit( format!("Witness type `{}` is not a symbol.", v.1.token), v.1.line, v.1.column, @@ -440,7 +443,11 @@ impl Parser { } x => { - self.error(format!("`{}` is an illegal witness type", x), v.1.line, v.1.column); + self.error.emit( + format!("`{}` is an illegal witness type", x), + v.1.line, + v.1.column, + ); } } } @@ -461,7 +468,7 @@ impl Parser { } } if left_paren != right_paren { - self.error( + self.error.emit( "Incorrect number of left and right parenthesis for statement.".to_string(), statement[0].line, statement[0].column, @@ -508,7 +515,7 @@ impl Parser { } if !parsing { - self.error( + self.error.emit( format!("Illegal token `{}`", next_token.token), next_token.line, next_token.column, @@ -614,7 +621,7 @@ impl Parser { } x => { - self.error( + self.error.emit( format!("Unimplemented function call `{}`", x), token.line, token.column, @@ -635,7 +642,7 @@ impl Parser { ) -> Vec { if let Some(next_token) = iter.peek() { if next_token.token_type != TokenType::LeftParen { - self.error( + self.error.emit( "Invalid function call opening. Must start with a `(`".to_string(), next_token.line, next_token.column, @@ -644,7 +651,7 @@ impl Parser { // Skip the opening parenthesis iter.next(); } else { - self.error("Premature ending of statement".to_string(), token.line, token.column); + self.error.emit("Premature ending of statement".to_string(), token.line, token.column); } // Eat up function arguments @@ -663,7 +670,7 @@ impl Parser { } if sep.token_type != TokenType::Comma { - self.error( + self.error.emit( "Argument separator is not a comma (`,`)".to_string(), sep.line, sep.column, @@ -673,29 +680,4 @@ impl Parser { args } - - fn error(&self, msg: String, ln: usize, col: usize) { - let err_msg = format!("{} (line {}, column {})", msg, ln, col); - let dbg_msg = format!("{}:{}:{}: {}", self.file, ln, col, self.lines[ln - 1]); - let pad = dbg_msg.split(": ").next().unwrap().len() + col + 2; - let caret = format!("{:width$}^", "", width = pad); - let msg = format!("{}\n{}\n{}\n", err_msg, dbg_msg, caret); - Parser::abort(&msg); - } - - fn abort(msg: &str) { - let stderr = io::stderr(); - let mut handle = stderr.lock(); - write!( - handle, - "{}{}Parser error:{} {}", - style::Bold, - color::Fg(color::Red), - style::Reset, - msg, - ) - .unwrap(); - handle.flush().unwrap(); - process::exit(1); - } }