Browse Source

zkas: Unify error functions into an error emitter.

parazyd 4 years ago
parent
commit
cabbe3c8ea
6 changed files with 127 additions and 175 deletions
  1. 14 48
      src/zkas/analyzer.rs
  2. 11 34
      src/zkas/compiler.rs
  3. 41 0
      src/zkas/error.rs
  4. 19 35
      src/zkas/lexer.rs
  5. 2 0
      src/zkas/mod.rs
  6. 40 58
      src/zkas/parser.rs

+ 14 - 48
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<String>,
     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<String> = 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();

+ 11 - 34
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<String>,
     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<String> = 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<u8> {
@@ -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);
-    }
 }

+ 41 - 0
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<String>,
+}
+
+impl ErrorEmitter {
+    pub fn new(namespace: &str, file: &str, lines: Vec<String>) -> 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);
+    }
+}

+ 19 - 35
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<String>,
     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<String> = 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<Token> {
@@ -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 {

+ 2 - 0
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

+ 40 - 58
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<String>,
     tokens: Vec<Token>,
+    error: ErrorEmitter,
 }
 
 impl Parser {
     pub fn new(filename: &str, source: Chars, tokens: Vec<Token>) -> 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<String> = 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<Token>) {
         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<Variable> {
         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);
-    }
 }