Parcourir la source

zkas: Rework lexer and parser into classes and simplify errors.

parazyd il y a 4 ans
Parent
commit
b483c8caf0
6 fichiers modifiés avec 586 ajouts et 385 suppressions
  1. 11 4
      zkas/src/bin/zkas.rs
  2. 0 95
      zkas/src/error.rs
  3. 196 132
      zkas/src/lexer.rs
  4. 4 1
      zkas/src/lib.rs
  5. 358 153
      zkas/src/parser.rs
  6. 17 0
      zkas/src/types.rs

+ 11 - 4
zkas/src/bin/zkas.rs

@@ -2,7 +2,7 @@ use anyhow::Result;
 use clap::clap_app;
 use clap::clap_app;
 use std::fs::read_to_string;
 use std::fs::read_to_string;
 
 
-use zkas::{lexer::lex, parser::parse};
+use zkas::{lexer::Lexer, parser::Parser};
 
 
 fn main() -> Result<()> {
 fn main() -> Result<()> {
     let args = clap_app!(zkas =>
     let args = clap_app!(zkas =>
@@ -12,11 +12,18 @@ fn main() -> Result<()> {
 
 
     let filename = args.value_of("INPUT").unwrap();
     let filename = args.value_of("INPUT").unwrap();
     let source = read_to_string(filename)?;
     let source = read_to_string(filename)?;
-    let tokens = lex(filename, source.chars());
 
 
-    println!("{:#?}", tokens);
+    let lexer = Lexer::new(filename, source.chars());
+    let tokens = lexer.lex();
 
 
-    let ast = parse(filename, source.chars(), tokens);
+    // println!("{:#?}", tokens);
+
+    let parser = Parser::new(filename, source.chars(), tokens);
+    let (constants, witnesses, circuit) = parser.parse();
+
+    println!("{:#?}", constants);
+    println!("{:#?}", witnesses);
+    println!("{:#?}", circuit);
 
 
     Ok(())
     Ok(())
 }
 }

+ 0 - 95
zkas/src/error.rs

@@ -1,95 +0,0 @@
-use colour::{e_prnt_ln, e_red};
-
-pub struct LexerError {
-    file: String,
-    lines: Vec<String>,
-}
-
-impl LexerError {
-    pub fn new(file: &str, lines: Vec<String>) -> Self {
-        LexerError { file: file.to_string(), lines }
-    }
-
-    pub fn invalid_token(&self, t: char, ln: usize, col: usize) {
-        let err_msg = format!("Invalid token `{}` on line {} (column {})\n", t, 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{}", err_msg, dbg_msg, caret);
-        LexerError::lexer_error(&msg);
-    }
-
-    pub fn invalid_string(&self, s: &str, ln: usize, col: usize) {
-        let err_msg = format!("Invalid ending in string `{}` on line {} (column {})", s, 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{}", err_msg, dbg_msg, caret);
-        LexerError::lexer_error(&msg);
-    }
-
-    pub fn invalid_symbol(&self, s: &str, ln: usize, col: usize) {
-        let err_msg = format!("Illegal char `{}` for symbol on line {} (column {})", s, 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{}", err_msg, dbg_msg, caret);
-        LexerError::lexer_error(&msg);
-    }
-
-    fn lexer_error(msg: &str) {
-        e_red!("Lexer error: ");
-        e_prnt_ln!("{}", msg);
-        std::process::exit(1);
-    }
-}
-
-pub struct ParserError {
-    file: String,
-    lines: Vec<String>,
-}
-
-impl ParserError {
-    pub fn new(file: &str, lines: Vec<String>) -> Self {
-        ParserError { file: file.to_string(), lines }
-    }
-
-    pub fn invalid_section_declaration(&self, s: &str, m: &str, ln: usize, col: usize) {
-        let err_msg =
-            format!("Invalid `{}` section declaration on line {} (column {})", s, ln, col);
-        let err_msg = format!("{}\n{}", err_msg, m);
-        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{}", err_msg, dbg_msg, caret);
-        ParserError::parser_error(&msg);
-    }
-
-    pub fn declaration_already_contains_token(&self, s: &str, t: &str, ln: usize, col: usize) {
-        let err_msg = format!(
-            "`{}` section declaration already contains token `{}` on line {} (column {})",
-            s, t, 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{}", err_msg, dbg_msg, caret);
-        ParserError::parser_error(&msg);
-    }
-
-    pub fn separator_not_a_comma(&self, ln: usize, col: usize) {
-        let err_msg = format!("Invalid separator on line {} (column {})", ln, col);
-        let err_msg = format!("{}\nShould be a comma `,`", err_msg);
-        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{}", err_msg, dbg_msg, caret);
-        ParserError::parser_error(&msg);
-    }
-
-    fn parser_error(msg: &str) {
-        e_red!("Parser error: ");
-        e_prnt_ln!("{}", msg);
-        std::process::exit(1);
-    }
-}

+ 196 - 132
zkas/src/lexer.rs

@@ -1,6 +1,6 @@
-use std::str::Chars;
+use std::{io, io::Write, process, str::Chars};
 
 
-use crate::error::LexerError;
+use termion::{color, style};
 
 
 #[derive(Hash, Eq, PartialEq, Clone, Debug)]
 #[derive(Hash, Eq, PartialEq, Clone, Debug)]
 pub enum TokenType {
 pub enum TokenType {
@@ -32,169 +32,233 @@ impl Token {
     }
     }
 }
 }
 
 
-pub fn lex(filename: &str, source: Chars) -> Vec<Token> {
-    // 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();
-    let lexer_error = LexerError::new(filename, lines);
+pub struct Lexer<'a> {
+    file: String,
+    lines: Vec<String>,
+    source: Chars<'a>,
+}
 
 
-    let mut tokens = vec![];
-    let mut lineno = 1;
-    let mut column = 0;
+impl<'a> Lexer<'a> {
+    pub fn new(filename: &str, source: Chars<'a>) -> Self {
+        // 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 }
+    }
 
 
-    // We use these as a buffer to keep strings/symbols.
-    let mut strbuf = String::new();
-    let mut symbuf = String::new();
+    pub fn lex(self) -> Vec<Token> {
+        let mut tokens = vec![];
+        let mut lineno = 1;
+        let mut column = 0;
 
 
-    // We use these to keep state when iterating
-    let mut in_comment = false;
-    let mut in_string = false;
-    let mut in_symbol = false;
+        // We use these as a buffer to keep strings and symbols
+        let mut strbuf = String::new();
+        let mut symbuf = String::new();
 
 
-    #[allow(clippy::explicit_counter_loop)]
-    for c in source {
-        column += 1;
+        // We use these to keep state when iterating
+        let mut in_comment = false;
+        let mut in_string = false;
+        let mut in_symbol = false;
 
 
-        if c == '\n' {
-            if in_symbol {
-                in_symbol = false;
-                tokens.push(Token::new(
-                    symbuf.clone(),
-                    TokenType::Symbol,
-                    lineno,
-                    column - symbuf.len(),
-                ));
-                symbuf = String::new();
-            }
+        #[allow(clippy::explicit_counter_loop)]
+        for c in self.source.clone() {
+            column += 1;
 
 
-            if in_string {
-                // TODO: Allow newlines in strings?
-                lexer_error.invalid_string(&strbuf, lineno, column);
-            }
+            if c == '\n' {
+                if in_symbol {
+                    in_symbol = false;
+                    tokens.push(Token::new(
+                        symbuf.clone(),
+                        TokenType::Symbol,
+                        lineno,
+                        column - symbuf.len(),
+                    ));
+                    symbuf = String::new();
+                }
 
 
-            in_comment = false;
-            lineno += 1;
-            column = 0;
-            continue
-        }
+                if in_string {
+                    // TODO: Allow newlines in strings?
+                    self.error(format!("Invalid ending in string `{}`", &strbuf), lineno, column);
+                }
 
 
-        if c == '#' || in_comment {
-            if in_symbol {
-                in_symbol = false;
-                tokens.push(Token::new(
-                    symbuf.clone(),
-                    TokenType::Symbol,
-                    lineno,
-                    column - symbuf.len(),
-                ));
-                symbuf = String::new();
+                in_comment = false;
+                lineno += 1;
+                column = 0;
+                continue
             }
             }
 
 
-            if in_string {
-                strbuf.push(c);
+            if c == '#' || in_comment {
+                if in_symbol {
+                    in_symbol = false;
+                    tokens.push(Token::new(
+                        symbuf.clone(),
+                        TokenType::Symbol,
+                        lineno,
+                        column - symbuf.len(),
+                    ));
+                    symbuf = String::new();
+                }
+
+                if in_string {
+                    strbuf.push(c);
+                    continue
+                }
+
+                in_comment = true;
                 continue
                 continue
             }
             }
 
 
-            in_comment = true;
-            continue
-        }
+            if c.is_whitespace() {
+                if in_symbol {
+                    in_symbol = false;
+                    tokens.push(Token::new(
+                        symbuf.clone(),
+                        TokenType::Symbol,
+                        lineno,
+                        column - symbuf.len(),
+                    ));
+                    symbuf = String::new();
+                }
 
 
-        if c.is_whitespace() {
-            if in_symbol {
-                in_symbol = false;
-                tokens.push(Token::new(
-                    symbuf.clone(),
-                    TokenType::Symbol,
-                    lineno,
-                    column - symbuf.len(),
-                ));
-                symbuf = String::new();
+                continue
             }
             }
 
 
-            continue
-        }
-
-        if !in_string && is_letter(c) {
-            in_symbol = true;
-            symbuf.push(c);
-            continue
-        }
+            if !in_string && is_letter(c) {
+                in_symbol = true;
+                symbuf.push(c);
+                continue
+            }
 
 
-        if in_string && is_letter(c) {
-            strbuf.push(c);
-            continue
-        }
+            if in_string && is_letter(c) {
+                strbuf.push(c);
+                continue
+            }
 
 
-        if c == '"' && !in_string {
-            if in_symbol {
-                lexer_error.invalid_symbol(&symbuf, lineno, column);
+            if c == '"' && !in_string {
+                if in_symbol {
+                    self.error(format!("Illegal char `{}` for symbol", c), lineno, column);
+                }
+                in_string = true;
+                continue
             }
             }
-            in_string = true;
-            continue
-        }
 
 
-        if c == '"' && in_string {
-            in_string = false;
-            tokens.push(Token::new(
-                strbuf.clone(),
-                TokenType::String,
-                lineno,
-                column - strbuf.len(),
-            ));
-            strbuf = String::new();
-            continue
-        }
+            if c == '"' && in_string {
+                if strbuf.is_empty() {
+                    self.error(format!("Invalid ending in string `{}`", &strbuf), lineno, column);
+                }
 
 
-        if SPECIAL_CHARS.contains(&c) {
-            if in_symbol {
-                in_symbol = false;
+                in_string = false;
                 tokens.push(Token::new(
                 tokens.push(Token::new(
-                    symbuf.clone(),
-                    TokenType::Symbol,
+                    strbuf.clone(),
+                    TokenType::String,
                     lineno,
                     lineno,
-                    column - symbuf.len(),
+                    column - strbuf.len(),
                 ));
                 ));
-                symbuf = String::new();
+                strbuf = String::new();
+                continue
             }
             }
 
 
-            match c {
-                '{' => {
-                    tokens.push(Token::new("{".to_string(), TokenType::LeftBrace, lineno, column));
-                    continue
+            if SPECIAL_CHARS.contains(&c) {
+                if in_symbol {
+                    in_symbol = false;
+                    tokens.push(Token::new(
+                        symbuf.clone(),
+                        TokenType::Symbol,
+                        lineno,
+                        column - symbuf.len(),
+                    ));
+                    symbuf = String::new();
                 }
                 }
-                '}' => {
-                    tokens.push(Token::new("}".to_string(), TokenType::RightBrace, lineno, column));
-                    continue
-                }
-                '(' => {
-                    tokens.push(Token::new("(".to_string(), TokenType::LeftParen, lineno, column));
-                    continue
-                }
-                ')' => {
-                    tokens.push(Token::new(")".to_string(), TokenType::RightParen, lineno, column));
-                    continue
-                }
-                ',' => {
-                    tokens.push(Token::new(",".to_string(), TokenType::Comma, lineno, column));
-                    continue
-                }
-                ';' => {
-                    tokens.push(Token::new(";".to_string(), TokenType::Semicolon, lineno, column));
-                    continue
-                }
-                '=' => {
-                    tokens.push(Token::new("=".to_string(), TokenType::Assign, lineno, column));
-                    continue
+
+                match c {
+                    '{' => {
+                        tokens.push(Token::new(
+                            "{".to_string(),
+                            TokenType::LeftBrace,
+                            lineno,
+                            column,
+                        ));
+                        continue
+                    }
+                    '}' => {
+                        tokens.push(Token::new(
+                            "}".to_string(),
+                            TokenType::RightBrace,
+                            lineno,
+                            column,
+                        ));
+                        continue
+                    }
+                    '(' => {
+                        tokens.push(Token::new(
+                            "(".to_string(),
+                            TokenType::LeftParen,
+                            lineno,
+                            column,
+                        ));
+                        continue
+                    }
+                    ')' => {
+                        tokens.push(Token::new(
+                            ")".to_string(),
+                            TokenType::RightParen,
+                            lineno,
+                            column,
+                        ));
+                        continue
+                    }
+                    ',' => {
+                        tokens.push(Token::new(",".to_string(), TokenType::Comma, lineno, column));
+                        continue
+                    }
+                    ';' => {
+                        tokens.push(Token::new(
+                            ";".to_string(),
+                            TokenType::Semicolon,
+                            lineno,
+                            column,
+                        ));
+                        continue
+                    }
+                    '=' => {
+                        tokens.push(Token::new("=".to_string(), TokenType::Assign, lineno, column));
+                        continue
+                    }
+                    _ => self.error(format!("Invalid token `{}`", c), lineno, column - 1),
                 }
                 }
-                _ => lexer_error.invalid_token(c, lineno, column - 1),
+                continue
             }
             }
-            continue
+
+            self.error(format!("Invalid token `{}`", c), lineno, column - 1);
         }
         }
 
 
-        lexer_error.invalid_token(c, lineno, column - 1);
+        tokens
     }
     }
 
 
-    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 {
 fn is_letter(ch: char) -> bool {

+ 4 - 1
zkas/src/lib.rs

@@ -1,5 +1,8 @@
-pub mod error;
+/// Lexer module
 pub mod lexer;
 pub mod lexer;
+/// Language opcodes
 pub mod opcode;
 pub mod opcode;
+/// Parser module
 pub mod parser;
 pub mod parser;
+/// Language types
 pub mod types;
 pub mod types;

+ 358 - 153
zkas/src/parser.rs

@@ -1,205 +1,410 @@
-use std::{
-    collections::{hash_map, HashMap},
-    str::Chars,
-};
+use std::{collections::HashMap, io, io::Write, process, str::Chars};
 
 
 use itertools::Itertools;
 use itertools::Itertools;
+use termion::{color, style};
 
 
 use crate::{
 use crate::{
-    error::ParserError,
     lexer::{Token, TokenType},
     lexer::{Token, TokenType},
+    types::{Constant, Type, Witness},
 };
 };
 
 
-pub fn parse(filename: &str, source: Chars, tokens: Vec<Token>) {
-    // 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();
-    let parser_error = ParserError::new(filename, lines);
-
-    // We use these to keep state when iterating
-    let mut declaring_constant = false;
-    let mut declaring_contract = false;
-    let mut declaring_circuit = false;
-
-    let mut constant_tokens = vec![];
-    let mut contract_tokens = vec![];
-    let mut circuit_tokens = vec![];
-
-    let mut ast: HashMap<String, Vec<HashMap<String, HashMap<String, (Token, Token)>>>> =
-        HashMap::new();
-
-    let mut iter = tokens.iter();
-    while let Some(t) = iter.next() {
-        // Start by declaring a section
-        if !declaring_constant && !declaring_contract && !declaring_circuit {
-            if t.token_type != TokenType::Symbol {
-                // TODO: Revisit
-                // TODO: Visit this again when we are allowing imports
-                panic!();
-            }
-
-            // The sections we are declaring in our source code
-            match t.token.as_str() {
-                "constant" => {
-                    declaring_constant = true;
-                    for inner in iter.by_ref() {
-                        constant_tokens.push(inner.clone());
-                        if inner.token_type == TokenType::RightBrace {
-                            break
+pub type Ast = HashMap<String, HashMap<String, HashMap<String, (Token, Token)>>>;
+
+pub type UnparsedConstants = HashMap<String, (Token, Token)>;
+pub type Constants = Vec<Constant>;
+
+pub type UnparsedWitnesses = HashMap<String, (Token, Token)>;
+pub type Witnesses = Vec<Witness>;
+
+pub struct Parser {
+    file: String,
+    lines: Vec<String>,
+    tokens: Vec<Token>,
+}
+
+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 }
+    }
+
+    pub fn parse(self) -> (Constants, Witnesses, Ast) {
+        // We use these to keep state when iterating
+        let mut declaring_constant = false;
+        let mut declaring_contract = false;
+        let mut declaring_circuit = false;
+
+        let mut constant_tokens = vec![];
+        let mut contract_tokens = vec![];
+        let mut circuit_tokens = vec![];
+
+        let mut ast = HashMap::new();
+        let mut namespace = String::new();
+        let mut ast_inner = HashMap::new();
+        let mut namespace_found = false; // Nasty
+
+        let mut iter = self.tokens.iter();
+        while let Some(t) = iter.next() {
+            // Start by declaring a section
+            if !declaring_constant && !declaring_contract && !declaring_circuit {
+                if t.token_type != TokenType::Symbol {
+                    // TODO: Revisit
+                    // TODO: Visit this again when we are allowing imports
+                    unimplemented!();
+                }
+
+                // The sections we must be declaring in our source code
+                match t.token.as_str() {
+                    "constant" => {
+                        declaring_constant = true;
+                        // Eat all the tokens within the `constant` section
+                        for inner in iter.by_ref() {
+                            constant_tokens.push(inner.clone());
+                            if inner.token_type == TokenType::RightBrace {
+                                break
+                            }
                         }
                         }
                     }
                     }
-                }
 
 
-                "contract" => {
-                    declaring_contract = true;
-                    for inner in iter.by_ref() {
-                        contract_tokens.push(inner.clone());
-                        if inner.token_type == TokenType::RightBrace {
-                            break
+                    "contract" => {
+                        declaring_contract = true;
+                        // Eat all the tokens within the `contract` section
+                        for inner in iter.by_ref() {
+                            contract_tokens.push(inner.clone());
+                            if inner.token_type == TokenType::RightBrace {
+                                break
+                            }
                         }
                         }
                     }
                     }
-                }
 
 
-                "circuit" => {
-                    declaring_circuit = true;
-                    for inner in iter.by_ref() {
-                        circuit_tokens.push(inner.clone());
-                        if inner.token_type == TokenType::RightBrace {
-                            break
+                    "circuit" => {
+                        declaring_circuit = true;
+                        // Eat all the tokens within the `circuit` section
+                        for inner in iter.by_ref() {
+                            circuit_tokens.push(inner.clone());
+                            if inner.token_type == TokenType::RightBrace {
+                                break
+                            }
                         }
                         }
                     }
                     }
+
+                    x => self.error(format!("Unknown `{}` proof section", x), t.line, t.column),
                 }
                 }
+            }
 
 
-                _ => unreachable!(),
+            // We shouldn't be reaching these states
+            if declaring_constant && (declaring_contract || declaring_circuit) {
+                unreachable!()
+            }
+            if declaring_contract && (declaring_constant || declaring_circuit) {
+                unreachable!()
+            }
+            if declaring_circuit && (declaring_constant || declaring_contract) {
+                unreachable!()
             }
             }
-        }
 
 
-        // We shouldn't be reaching these states
-        if declaring_constant && (declaring_contract || declaring_circuit) {
-            unreachable!()
-        }
-        if declaring_contract && (declaring_constant || declaring_circuit) {
-            unreachable!()
-        }
-        if declaring_circuit && (declaring_constant || declaring_contract) {
-            unreachable!()
-        }
+            // Now go through the token vectors and work it through
+            if declaring_constant {
+                self.check_section_structure("constant", constant_tokens.clone());
+
+                // TODO: Do we need this?
+                if namespace_found && namespace != constant_tokens[0].token {
+                    self.error(
+                        format!(
+                            "Found `{}` namespace. Expected `{}`.",
+                            constant_tokens[0].token, namespace
+                        ),
+                        constant_tokens[0].line,
+                        constant_tokens[0].column,
+                    );
+                } else {
+                    namespace = constant_tokens[0].token.clone();
+                    namespace_found = true;
+                }
+
+                let constants_cloned = constant_tokens.clone();
+                let mut constants_map = HashMap::new();
+                // This is everything between the braces: { .. }
+                let mut constants_inner = constants_cloned[2..constant_tokens.len() - 1].iter();
+
+                while let Some((typ, name, comma)) = constants_inner.next_tuple() {
+                    if comma.token_type != TokenType::Comma {
+                        self.error(
+                            "Separator is not a comma".to_string(),
+                            comma.line,
+                            comma.column,
+                        );
+                    }
+
+                    if constants_map.contains_key(name.token.as_str()) {
+                        self.error(
+                            format!(
+                                "Section `constant` already contains the token `{}`.",
+                                &name.token
+                            ),
+                            name.line,
+                            name.column,
+                        );
+                    }
 
 
-        // Now go through the token vectors and work it through
-        if declaring_constant {
-            if let Some(err_msg) = check_section_structure(constant_tokens.clone()) {
-                parser_error.invalid_section_declaration(
-                    "constant",
-                    err_msg,
-                    constant_tokens[0].line,
-                    constant_tokens[0].column,
-                );
-            }
-
-            let namespace = constant_tokens[0].token.clone();
-            let mut constants_map = HashMap::new();
-
-            let constants_cloned = constant_tokens.clone();
-            let mut constants_inner = constants_cloned[2..constant_tokens.len() - 1].iter();
-            while let Some((typ, name, comma)) = constants_inner.next_tuple() {
-                if comma.token_type != TokenType::Comma {
-                    parser_error.separator_not_a_comma(comma.line, comma.column);
+                    constants_map.insert(name.token.clone(), (name.clone(), typ.clone()));
                 }
                 }
 
 
-                if constants_map.contains_key(name.token.as_str()) {
-                    parser_error.declaration_already_contains_token(
-                        "constant",
-                        &name.token,
-                        name.line,
-                        name.column,
+                ast_inner.insert("constant".to_string(), constants_map);
+                declaring_constant = false;
+            }
+
+            if declaring_contract {
+                self.check_section_structure("contract", contract_tokens.clone());
+
+                // TODO: Do we need this?
+                if namespace_found && namespace != contract_tokens[0].token {
+                    self.error(
+                        format!(
+                            "Found `{}` namespace. Expected `{}`.",
+                            contract_tokens[0].token, namespace
+                        ),
+                        contract_tokens[0].line,
+                        contract_tokens[0].column,
                     );
                     );
+                } else {
+                    namespace = contract_tokens[0].token.clone();
+                    namespace_found = true;
                 }
                 }
 
 
-                constants_map.insert(name.token.clone(), (name.clone(), typ.clone()));
-            }
+                let contract_cloned = contract_tokens.clone();
+                let mut contract_map = HashMap::new();
+                // This is everything between the braces: { .. }
+                let mut contract_inner = contract_cloned[2..contract_tokens.len() - 1].iter();
+
+                while let Some((typ, name, comma)) = contract_inner.next_tuple() {
+                    if comma.token_type != TokenType::Comma {
+                        self.error(
+                            "Separator is not a comma".to_string(),
+                            comma.line,
+                            comma.column,
+                        );
+                    }
 
 
-            let mut c_map = HashMap::new();
-            c_map.insert("constant".to_string(), constants_map);
+                    if contract_map.contains_key(name.token.as_str()) {
+                        self.error(
+                            format!(
+                                "Section `contract` already contains the token `{}`.",
+                                &name.token
+                            ),
+                            name.line,
+                            name.column,
+                        );
+                    }
+
+                    contract_map.insert(name.token.clone(), (name.clone(), typ.clone()));
+                }
 
 
-            if let hash_map::Entry::Vacant(e) = ast.entry(namespace.clone()) {
-                let v = vec![c_map];
-                e.insert(v);
-            } else {
-                let v = ast.get_mut(&namespace).unwrap();
-                v.push(c_map);
+                ast_inner.insert("contract".to_string(), contract_map);
+                declaring_contract = false;
             }
             }
 
 
-            declaring_constant = false;
+            if declaring_circuit {
+                declaring_circuit = false;
+            }
         }
         }
 
 
-        if declaring_contract {
-            if let Some(err_msg) = check_section_structure(contract_tokens.clone()) {
-                parser_error.invalid_section_declaration(
-                    "contract",
-                    err_msg,
-                    contract_tokens[0].line,
-                    contract_tokens[0].column,
-                );
-            }
+        ast.insert(namespace.clone(), ast_inner);
+        self.verify_initial_ast(&ast);
 
 
-            let namespace = contract_tokens[0].token.clone();
-            let mut contract_map = HashMap::new();
+        // Clean up the `constant` section
+        let (constants, err) =
+            Parser::parse_ast_constants(ast.get(&namespace).unwrap().get("constant").unwrap());
+        if let Some(err_msg) = err {
+            // TODO: Return problematic token from parse_ast_constants()
+            self.error(err_msg, 1, 1);
+        }
 
 
-            let contract_cloned = contract_tokens.clone();
-            let mut contract_inner = contract_cloned[2..contract_tokens.len() - 1].iter();
-            while let Some((typ, name, comma)) = contract_inner.next_tuple() {
-                if comma.token_type != TokenType::Comma {
-                    parser_error.separator_not_a_comma(comma.line, comma.column);
-                }
+        // Clean up the `contract section
+        let (contract, err) =
+            Parser::parse_ast_contract(ast.get(&namespace).unwrap().get("contract").unwrap());
+        if let Some(err_msg) = err {
+            // TODO: Return problematic token from parse_ast_contract()
+            self.error(err_msg, 1, 1);
+        }
 
 
-                if contract_map.contains_key(&name.token) {
-                    parser_error.declaration_already_contains_token(
-                        "contract",
-                        &name.token,
-                        name.line,
-                        name.column,
-                    );
-                }
+        // Return
+        (constants, contract, HashMap::new())
+    }
 
 
-                contract_map.insert(name.token.clone(), (name.clone(), typ.clone()));
+    fn verify_initial_ast(&self, ast: &Ast) {
+        // Verify that there are all 3 sections
+        for v in ast.values() {
+            if !v.contains_key("constant") {
+                self.error("Missing `constant` section in the source.".to_string(), 1, 1);
             }
             }
 
 
-            let mut c_map = HashMap::new();
-            c_map.insert("contract".to_string(), contract_map);
+            if !v.contains_key("contract") {
+                self.error("Missing `contract` section in the source.".to_string(), 1, 1);
+            }
 
 
-            if let hash_map::Entry::Vacant(e) = ast.entry(namespace.clone()) {
-                let v = vec![c_map];
-                e.insert(v);
-            } else {
-                let v = ast.get_mut(&namespace).unwrap();
-                v.push(c_map);
+            /*
+            if !v.contains_key("circuit") {
+                self.error("Missing `circuit` section in the source.".to_string(), 1, 1);
             }
             }
+            */
+        }
+    }
 
 
-            declaring_contract = false;
+    fn check_section_structure(&self, section: &str, tokens: Vec<Token>) {
+        if tokens[0].token_type != TokenType::String {
+            self.error(
+                format!("{} section declaration must start with a naming string.", section),
+                tokens[0].line,
+                tokens[0].column,
+            );
         }
         }
 
 
-        if declaring_circuit {
-            declaring_circuit = false;
+        if tokens[1].token_type != TokenType::LeftBrace {
+            self.error(
+                format!(
+                    "{} section opening is not correct. Must be opened with a left brace `{{`",
+                    section
+                ),
+                tokens[0].line,
+                tokens[0].column,
+            );
         }
         }
 
 
-        println!("{:#?}", ast);
-    }
-}
+        if tokens[tokens.len() - 1].token_type != TokenType::RightBrace {
+            self.error(
+                format!(
+                    "{} section closing is not correct. Must be closed with a right brace `}}`",
+                    section
+                ),
+                tokens[0].line,
+                tokens[0].column,
+            );
+        }
 
 
-fn check_section_structure(tokens: Vec<Token>) -> Option<&'static str> {
-    if tokens[0].token_type != TokenType::String {
-        return Some("Section declaration must start with a naming string.")
+        if tokens[2..tokens.len() - 1].len() % 3 != 0 {
+            self.error(
+                format!(
+                    "Invalid number of elements in `{}` section. Must be pairs of `type:name` separated with a comma `,`",
+                    section
+                ),
+                tokens[0].line,
+                tokens[0].column,
+            );
+        }
     }
     }
-    if tokens[1].token_type != TokenType::LeftBrace {
-        return Some("Section opening is not correct. Must be opened with a left brace `{`")
+
+    fn parse_ast_constants(ast: &UnparsedConstants) -> (Constants, Option<String>) {
+        let mut ret = vec![];
+
+        for (k, v) in ast {
+            if &v.0.token != k {
+                return (vec![], Some("Constant name doesn't match token".to_string()))
+            }
+
+            if v.0.token_type != TokenType::Symbol {
+                return (vec![], Some("Constant name is not a symbol".to_string()))
+            }
+
+            if v.1.token_type != TokenType::Symbol {
+                return (vec![], Some("Constant type is not a symbol".to_string()))
+            }
+
+            match v.1.token.as_str() {
+                "EcFixedPoint" => {
+                    ret.push(Constant {
+                        name: k.to_string(),
+                        typ: Type::EcFixedPoint,
+                        line: v.0.line,
+                        column: v.0.column,
+                    });
+                }
+
+                x => {
+                    let err_msg = format!("`{}` is an illegal constant type", x);
+                    return (vec![], Some(err_msg))
+                }
+            }
+        }
+
+        (ret, None)
     }
     }
-    if tokens[tokens.len() - 1].token_type != TokenType::RightBrace {
-        return Some("Section closing is not correct. Must be closed with a right brace `}`")
+
+    fn parse_ast_contract(ast: &UnparsedWitnesses) -> (Witnesses, Option<String>) {
+        let mut ret = vec![];
+
+        for (k, v) in ast {
+            if &v.0.token != k {
+                return (vec![], Some("Contract input name doesn't match token".to_string()))
+            }
+
+            if v.0.token_type != TokenType::Symbol {
+                return (vec![], Some("Contract input name is not a symbol".to_string()))
+            }
+
+            if v.1.token_type != TokenType::Symbol {
+                return (vec![], Some("Contract input type is not a symbol".to_string()))
+            }
+
+            match v.1.token.as_str() {
+                "Base" => {
+                    ret.push(Witness {
+                        name: k.to_string(),
+                        typ: Type::Base,
+                        line: v.0.line,
+                        column: v.0.column,
+                    });
+                }
+                "Scalar" => {
+                    ret.push(Witness {
+                        name: k.to_string(),
+                        typ: Type::Scalar,
+                        line: v.0.line,
+                        column: v.0.column,
+                    });
+                }
+                "MerklePath" => {
+                    ret.push(Witness {
+                        name: k.to_string(),
+                        typ: Type::MerklePath,
+                        line: v.0.line,
+                        column: v.0.column,
+                    });
+                }
+                x => {
+                    let err_msg = format!("`{}` is an illegal witness type", x);
+                    return (vec![], Some(err_msg))
+                }
+            }
+        }
+
+        (ret, None)
     }
     }
 
 
-    if tokens[2..tokens.len() - 1].len() % 3 != 0 {
-        return Some("Invalid number of elements in section. Must be pairs of `type:name` separated with a comma `,`")
+    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);
     }
     }
 
 
-    None
+    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);
+    }
 }
 }

+ 17 - 0
zkas/src/types.rs

@@ -1,6 +1,23 @@
+#[derive(Clone, Debug)]
 pub enum Type {
 pub enum Type {
     EcFixedPoint = 0x00,
     EcFixedPoint = 0x00,
     Base = 0x01,
     Base = 0x01,
     Scalar = 0x02,
     Scalar = 0x02,
     MerklePath = 0x03,
     MerklePath = 0x03,
 }
 }
+
+#[derive(Clone, Debug)]
+pub struct Constant {
+    pub name: String,
+    pub typ: Type,
+    pub line: usize,
+    pub column: usize,
+}
+
+#[derive(Clone, Debug)]
+pub struct Witness {
+    pub name: String,
+    pub typ: Type,
+    pub line: usize,
+    pub column: usize,
+}