Sfoglia il codice sorgente

zkas: Rework error subsystem and add warnings.

Luther Blissett 4 anni fa
parent
commit
b0d8217965
5 ha cambiato i file con 151 aggiunte e 110 eliminazioni
  1. 16 16
      src/zkas/analyzer.rs
  2. 2 2
      src/zkas/compiler.rs
  3. 50 18
      src/zkas/error.rs
  4. 7 7
      src/zkas/lexer.rs
  5. 76 67
      src/zkas/parser.rs

+ 16 - 16
src/zkas/analyzer.rs

@@ -53,8 +53,8 @@ impl Analyzer {
             // It's kinda ugly.
             if arg_types[0] == Type::BaseArray || arg_types[0] == Type::ScalarArray {
                 if statement.args.is_empty() {
-                    self.error.emit(
-                        format!(
+                    self.error.abort(
+                        &format!(
                             "Passed no arguments to `{:?}` call. Expected at least 1.",
                             statement.opcode
                         ),
@@ -72,8 +72,8 @@ impl Analyzer {
                         };
 
                         if arg_types[0] == Type::BaseArray && var_type != Type::Base {
-                            self.error.emit(
-                                format!(
+                            self.error.abort(
+                                &format!(
                                     "Incorrect argument type. Expected `{:?}`, got `{:?}`",
                                     arg_types[0],
                                     Type::Base,
@@ -84,8 +84,8 @@ impl Analyzer {
                         }
 
                         if arg_types[0] == Type::ScalarArray && var_type != Type::Scalar {
-                            self.error.emit(
-                                format!(
+                            self.error.abort(
+                                &format!(
                                     "Incorrect argument type. Expected `{:?}`, got `{:?}`",
                                     arg_types[0],
                                     Type::Scalar,
@@ -99,8 +99,8 @@ impl Analyzer {
                         arg.typ = var_type;
                         args.push(arg);
                     } else {
-                        self.error.emit(
-                            format!("Unknown argument reference `{}`.", i.name),
+                        self.error.abort(
+                            &format!("Unknown argument reference `{}`.", i.name),
                             i.line,
                             i.column,
                         );
@@ -108,8 +108,8 @@ impl Analyzer {
                 }
             } else {
                 if statement.args.len() != arg_types.len() {
-                    self.error.emit(
-                        format!(
+                    self.error.abort(
+                        &format!(
                             "Incorrent number of args to `{:?}` call. Expected {}, got {}",
                             statement.opcode,
                             arg_types.len(),
@@ -129,8 +129,8 @@ impl Analyzer {
                         };
 
                         if var_type != arg_types[idx] {
-                            self.error.emit(
-                                format!(
+                            self.error.abort(
+                                &format!(
                                     "Incorrect argument type. Expected `{:?}`, got `{:?}`",
                                     arg_types[idx], var_type,
                                 ),
@@ -143,8 +143,8 @@ impl Analyzer {
                         arg.typ = var_type;
                         args.push(arg);
                     } else {
-                        self.error.emit(
-                            format!("Unknown argument reference `{}`.", i.name),
+                        self.error.abort(
+                            &format!("Unknown argument reference `{}`.", i.name),
                             i.line,
                             i.column,
                         );
@@ -204,8 +204,8 @@ impl Analyzer {
                 if let Some(index) = stack.iter().position(|&r| r == &arg.name) {
                     println!("Found at stack index {}", index);
                 } else {
-                    self.error.emit(
-                        format!("Could not find `{}` on the stack", arg.name),
+                    self.error.abort(
+                        &format!("Could not find `{}` on the stack", arg.name),
                         arg.line,
                         arg.column,
                     );

+ 2 - 2
src/zkas/compiler.rs

@@ -79,8 +79,8 @@ impl Compiler {
                     continue
                 }
 
-                self.error.emit(
-                    format!("Failed finding a stack reference for `{}`", arg.name),
+                self.error.abort(
+                    &format!("Failed finding a stack reference for `{}`", arg.name),
                     arg.line,
                     arg.column,
                 );

+ 50 - 18
src/zkas/error.rs

@@ -13,29 +13,61 @@ impl ErrorEmitter {
         Self { namespace: namespace.to_string(), file: file.to_string(), lines }
     }
 
-    pub fn emit(&self, msg: String, ln: usize, col: usize) {
+    fn fmt(&self, msg: String, ln: usize, col: usize) -> String {
         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);
+        let (dbg_msg, caret) = match ln {
+            0 => ("".to_string(), "".to_string()),
+            _ => {
+                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);
+                (dbg_msg, caret)
+            }
+        };
+        format!("{}\n{}\n{}\n", err_msg, dbg_msg, caret)
     }
 
-    fn abort(&self, msg: &str) {
+    pub fn abort(&self, msg: &str, ln: usize, col: usize) {
+        let m = self.fmt(msg.to_string(), ln, col);
+        self.emit("error", &m);
+        process::exit(1);
+    }
+
+    pub fn warn(&self, msg: &str, ln: usize, col: usize) {
+        let m = self.fmt(msg.to_string(), ln, col);
+        self.emit("warning", &m);
+    }
+
+    pub fn emit(&self, typ: &str, 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();
+
+        match typ {
+            "error" => write!(
+                handle,
+                "{}{}{} error:{} {}",
+                style::Bold,
+                color::Fg(color::Red),
+                self.namespace,
+                style::Reset,
+                msg
+            )
+            .unwrap(),
+
+            "warning" => write!(
+                handle,
+                "{}{}{} warning:{} {}",
+                style::Bold,
+                color::Fg(color::Yellow),
+                self.namespace,
+                style::Reset,
+                msg
+            )
+            .unwrap(),
+
+            _ => unreachable!(),
+        };
+
         handle.flush().unwrap();
-        process::exit(1);
     }
 }

+ 7 - 7
src/zkas/lexer.rs

@@ -79,8 +79,8 @@ impl<'a> Lexer<'a> {
 
                 if in_string {
                     // TODO: Allow newlines in strings?
-                    self.error.emit(
-                        format!("Invalid ending in string `{}`", &strbuf),
+                    self.error.abort(
+                        &format!("Invalid ending in string `{}`", &strbuf),
                         lineno,
                         column,
                     );
@@ -146,7 +146,7 @@ impl<'a> Lexer<'a> {
 
             if c == '"' && !in_string {
                 if in_symbol {
-                    self.error.emit(format!("Illegal char `{}` for symbol", c), lineno, column);
+                    self.error.abort(&format!("Illegal char `{}` for symbol", c), lineno, column);
                 }
                 in_string = true;
                 continue
@@ -154,8 +154,8 @@ impl<'a> Lexer<'a> {
 
             if c == '"' && in_string {
                 if strbuf.is_empty() {
-                    self.error.emit(
-                        format!("Invalid ending in string `{}`", &strbuf),
+                    self.error.abort(
+                        &format!("Invalid ending in string `{}`", &strbuf),
                         lineno,
                         column,
                     );
@@ -238,12 +238,12 @@ impl<'a> Lexer<'a> {
                         tokens.push(Token::new("=".to_string(), TokenType::Assign, lineno, column));
                         continue
                     }
-                    _ => self.error.emit(format!("Invalid token `{}`", c), lineno, column - 1),
+                    _ => self.error.abort(&format!("Invalid token `{}`", c), lineno, column - 1),
                 }
                 continue
             }
 
-            self.error.emit(format!("Invalid token `{}`", c), lineno, column - 1);
+            self.error.abort(&format!("Invalid token `{}`", c), lineno, column - 1);
         }
 
         tokens

+ 76 - 67
src/zkas/parser.rs

@@ -95,9 +95,11 @@ impl Parser {
                         }
                     }
 
-                    x => {
-                        self.error.emit(format!("Unknown `{}` proof section", x), t.line, t.column)
-                    }
+                    x => self.error.abort(
+                        &format!("Unknown `{}` proof section", x),
+                        t.line,
+                        t.column,
+                    ),
                 }
             }
 
@@ -118,8 +120,8 @@ impl Parser {
 
                 // TODO: Do we need this?
                 if namespace_found && namespace != constant_tokens[0].token {
-                    self.error.emit(
-                        format!(
+                    self.error.abort(
+                        &format!(
                             "Found `{}` namespace. Expected `{}`.",
                             constant_tokens[0].token, namespace
                         ),
@@ -138,16 +140,12 @@ impl Parser {
 
                 while let Some((typ, name, comma)) = constants_inner.next_tuple() {
                     if comma.token_type != TokenType::Comma {
-                        self.error.emit(
-                            "Separator is not a comma".to_string(),
-                            comma.line,
-                            comma.column,
-                        );
+                        self.error.abort("Separator is not a comma", comma.line, comma.column);
                     }
 
                     if constants_map.contains_key(name.token.as_str()) {
-                        self.error.emit(
-                            format!(
+                        self.error.abort(
+                            &format!(
                                 "Section `constant` already contains the token `{}`.",
                                 &name.token
                             ),
@@ -168,8 +166,8 @@ impl Parser {
 
                 // TODO: Do we need this?
                 if namespace_found && namespace != contract_tokens[0].token {
-                    self.error.emit(
-                        format!(
+                    self.error.abort(
+                        &format!(
                             "Found `{}` namespace. Expected `{}`.",
                             contract_tokens[0].token, namespace
                         ),
@@ -188,16 +186,12 @@ impl Parser {
 
                 while let Some((typ, name, comma)) = contract_inner.next_tuple() {
                     if comma.token_type != TokenType::Comma {
-                        self.error.emit(
-                            "Separator is not a comma".to_string(),
-                            comma.line,
-                            comma.column,
-                        );
+                        self.error.abort("Separator is not a comma", comma.line, comma.column);
                     }
 
                     if contract_map.contains_key(name.token.as_str()) {
-                        self.error.emit(
-                            format!(
+                        self.error.abort(
+                            &format!(
                                 "Section `contract` already contains the token `{}`.",
                                 &name.token
                             ),
@@ -217,8 +211,8 @@ impl Parser {
                 self.check_section_structure("circuit", contract_tokens.clone());
 
                 if circuit_tokens[circuit_tokens.len() - 2].token_type != TokenType::Semicolon {
-                    self.error.emit(
-                        "Circuit section does not end with a semicolon. Would never finish parsing.".to_string(),
+                    self.error.abort(
+                        "Circuit section does not end with a semicolon. Would never finish parsing.",
                         circuit_tokens[circuit_tokens.len()-2].line,
                         circuit_tokens[circuit_tokens.len()-2].column
                     );
@@ -226,8 +220,8 @@ impl Parser {
 
                 // TODO: Do we need this?
                 if namespace_found && namespace != circuit_tokens[0].token {
-                    self.error.emit(
-                        format!(
+                    self.error.abort(
+                        &format!(
                             "Found `{}` namespace. Expected `{}`.",
                             circuit_tokens[0].token, namespace
                         ),
@@ -254,37 +248,56 @@ impl Parser {
         }
 
         ast.insert(namespace.clone(), ast_inner);
-        // TODO: Verify there are both constant/contract sections
-        // TODO: Verify there is a circuit section
         // TODO: Check that there are no duplicate names in constants, contract
         //       and circuit assignments
 
         // Clean up the `constant` section
-        let c = ast.get(&namespace).unwrap().get("constant").unwrap();
+        let c = match ast.get(&namespace).unwrap().get("constant") {
+            Some(c) => c,
+            None => {
+                self.error.abort("Missing `constant` section in .zk source", 0, 0);
+                unreachable!()
+            }
+        };
         let constants = self.parse_ast_constants(c);
+        if constants.is_empty() {
+            self.error.warn("Constant section is empty", 0, 0);
+        }
 
         // Clean up the `contract` section
-        let c = ast.get(&namespace).unwrap().get("contract").unwrap();
+        let c = match ast.get(&namespace).unwrap().get("contract") {
+            Some(c) => c,
+            None => {
+                self.error.abort("Missing `contract` section in .zk source", 0, 0);
+                unreachable!()
+            }
+        };
         let witnesses = self.parse_ast_contract(c);
+        if witnesses.is_empty() {
+            self.error.abort("Contract section is empty", 0, 0);
+        }
 
         // Clean up the `circuit` section
         let stmt = self.parse_ast_circuit(circuit_statements);
+        if stmt.is_empty() {
+            self.error.abort("Circuit section is empty", 0, 0);
+        }
 
         (constants, witnesses, stmt)
     }
 
     fn check_section_structure(&self, section: &str, tokens: Vec<Token>) {
         if tokens[0].token_type != TokenType::String {
-            self.error.emit(
-                format!("{} section declaration must start with a naming string.", section),
+            self.error.abort(
+                &format!("{} section declaration must start with a naming string.", section),
                 tokens[0].line,
                 tokens[0].column,
             );
         }
 
         if tokens[1].token_type != TokenType::LeftBrace {
-            self.error.emit(
-                format!(
+            self.error.abort(
+                &format!(
                     "{} section opening is not correct. Must be opened with a left brace `{{`",
                     section
                 ),
@@ -294,8 +307,8 @@ impl Parser {
         }
 
         if tokens[tokens.len() - 1].token_type != TokenType::RightBrace {
-            self.error.emit(
-                format!(
+            self.error.abort(
+                &format!(
                     "{} section closing is not correct. Must be closed with a right brace `}}`",
                     section
                 ),
@@ -307,8 +320,8 @@ impl Parser {
         if (section == "constant" || section == "contract") &&
             tokens[2..tokens.len() - 1].len() % 3 != 0
         {
-            self.error.emit(
-                format!(
+            self.error.abort(
+                &format!(
                     "Invalid number of elements in `{}` section. Must be pairs of `type:name` separated with a comma `,`",
                     section
                 ),
@@ -323,24 +336,24 @@ impl Parser {
 
         for (k, v) in ast {
             if &v.0.token != k {
-                self.error.emit(
-                    format!("Constant name `{}` doesn't match token `{}`.", v.0.token, k),
+                self.error.abort(
+                    &format!("Constant name `{}` doesn't match token `{}`.", v.0.token, k),
                     v.0.line,
                     v.0.column,
                 );
             }
 
             if v.0.token_type != TokenType::Symbol {
-                self.error.emit(
-                    format!("Constant name `{}` is not a symbol.", v.0.token),
+                self.error.abort(
+                    &format!("Constant name `{}` is not a symbol.", v.0.token),
                     v.0.line,
                     v.0.column,
                 );
             }
 
             if v.1.token_type != TokenType::Symbol {
-                self.error.emit(
-                    format!("Constant type `{}` is not a symbol.", v.1.token),
+                self.error.abort(
+                    &format!("Constant type `{}` is not a symbol.", v.1.token),
                     v.1.line,
                     v.1.column,
                 );
@@ -375,8 +388,8 @@ impl Parser {
                 }
 
                 x => {
-                    self.error.emit(
-                        format!("`{}` is an illegal constant type", x),
+                    self.error.abort(
+                        &format!("`{}` is an illegal constant type", x),
                         v.1.line,
                         v.1.column,
                     );
@@ -392,24 +405,24 @@ impl Parser {
 
         for (k, v) in ast {
             if &v.0.token != k {
-                self.error.emit(
-                    format!("Witness name `{}` doesn't match token `{}`.", v.0.token, k),
+                self.error.abort(
+                    &format!("Witness name `{}` doesn't match token `{}`.", v.0.token, k),
                     v.0.line,
                     v.0.column,
                 );
             }
 
             if v.0.token_type != TokenType::Symbol {
-                self.error.emit(
-                    format!("Witness name `{}` is not a symbol.", v.0.token),
+                self.error.abort(
+                    &format!("Witness name `{}` is not a symbol.", v.0.token),
                     v.0.line,
                     v.0.column,
                 );
             }
 
             if v.1.token_type != TokenType::Symbol {
-                self.error.emit(
-                    format!("Witness type `{}` is not a symbol.", v.1.token),
+                self.error.abort(
+                    &format!("Witness type `{}` is not a symbol.", v.1.token),
                     v.1.line,
                     v.1.column,
                 );
@@ -462,8 +475,8 @@ impl Parser {
                 }
 
                 x => {
-                    self.error.emit(
-                        format!("`{}` is an illegal witness type", x),
+                    self.error.abort(
+                        &format!("`{}` is an illegal witness type", x),
                         v.1.line,
                         v.1.column,
                     );
@@ -487,8 +500,8 @@ impl Parser {
                 }
             }
             if left_paren != right_paren {
-                self.error.emit(
-                    "Incorrect number of left and right parenthesis for statement.".to_string(),
+                self.error.abort(
+                    "Incorrect number of left and right parenthesis for statement.",
                     statement[0].line,
                     statement[0].column,
                 );
@@ -534,8 +547,8 @@ impl Parser {
                         }
 
                         if !parsing {
-                            self.error.emit(
-                                format!("Illegal token `{}`", next_token.token),
+                            self.error.abort(
+                                &format!("Illegal token `{}`", next_token.token),
                                 next_token.line,
                                 next_token.column,
                             );
@@ -616,8 +629,8 @@ impl Parser {
                     }
 
                     x => {
-                        self.error.emit(
-                            format!("Unimplemented function call `{}`", x),
+                        self.error.abort(
+                            &format!("Unimplemented function call `{}`", x),
                             token.line,
                             token.column,
                         );
@@ -637,8 +650,8 @@ impl Parser {
     ) -> Vec<Variable> {
         if let Some(next_token) = iter.peek() {
             if next_token.token_type != TokenType::LeftParen {
-                self.error.emit(
-                    "Invalid function call opening. Must start with a `(`".to_string(),
+                self.error.abort(
+                    "Invalid function call opening. Must start with a `(`",
                     next_token.line,
                     next_token.column,
                 );
@@ -646,7 +659,7 @@ impl Parser {
             // Skip the opening parenthesis
             iter.next();
         } else {
-            self.error.emit("Premature ending of statement".to_string(), token.line, token.column);
+            self.error.abort("Premature ending of statement", token.line, token.column);
         }
 
         // Eat up function arguments
@@ -665,11 +678,7 @@ impl Parser {
             }
 
             if sep.token_type != TokenType::Comma {
-                self.error.emit(
-                    "Argument separator is not a comma (`,`)".to_string(),
-                    sep.line,
-                    sep.column,
-                );
+                self.error.abort("Argument separator is not a comma (`,`)", sep.line, sep.column);
             }
         }