Просмотр исходного кода

zkas/parser: Implement circuit section parsing.

Luther Blissett 4 лет назад
Родитель
Сommit
ed1bc8c30f
1 измененных файлов с 279 добавлено и 22 удалено
  1. 279 22
      src/zkas/parser.rs

+ 279 - 22
src/zkas/parser.rs

@@ -1,13 +1,13 @@
-use std::str::Chars;
+use std::{iter::Peekable, str::Chars};
 
 use indexmap::IndexMap;
 use itertools::Itertools;
 
 use super::{
-    ast::{Constant, Statement, Witness},
+    ast::{Arg, Constant, Literal, Statement, StatementType, Variable, Witness},
     error::ErrorEmitter,
     lexer::{Token, TokenType},
-    LitType, VarType,
+    LitType, Opcode, VarType,
 };
 
 /// zkas language builtin keywords.
@@ -532,25 +532,6 @@ impl Parser {
     }
 
     fn parse_ast_circuit(&self, statements: Vec<Vec<Token>>) -> Vec<Statement> {
-        let mut ret = vec![];
-
-        // In here, we want to support nested function calls, e.g.:
-        //
-        //      constrain_instance(ec_get_x(token_commit));
-        //
-        // The inner call's result would still get pushed on the stack,
-        // but it will not be accessible in any other scope.
-
-        // In certain opcodes, we also support literal types, and the
-        // opcodes can return a variable type after running the operation.
-        // e.g.
-        //            one = witness_base(1);
-        //           zero = witness_base(0);
-        //
-        // The literal type is used only in the function call's scope, but
-        // the result is then accessible on the stack to be used by further
-        // computation.
-
         // The statement layouts/syntax in the language are as follows:
         //
         // C = poseidon_hash(pub_x, pub_y, value, token, serial, coin_blind);
@@ -570,6 +551,282 @@ impl Parser {
         //                        |                 |
         //                        V                 V
         //                     opcode          arg as opcode
+        //
+        // In the latter, we want to support nested function calls, e.g.:
+        //
+        //          constrain_instance(ec_get_x(token_commit));
+        //
+        // The inner call's result would still get pushed on the stack,
+        // but it will not be accessible in any other scope.
+        //
+        // In certain opcodes, we also support literal types, and the
+        // opcodes can return a variable type after running the operation.
+        // e.g.
+        //            one = witness_base(1);
+        //           zero = witness_base(0);
+        //
+        // The literal type is used only in the function call's scope, but
+        // the result is then accessible on the stack to be used by further
+        // computation.
+        //
+        // Regarding multiple return values from opcodes, this is perhaps
+        // not necessary for the current language scope, as this is a low
+        // level representation. Note that it could be relatively easy to
+        // modify the parsing logic to support that here. For now we'll
+        // defer it, and if at some point we decide that the language is
+        // too expressive and noisy, we'll consider having multiple return
+        // types. It also very much depends on the type of functions/opcodes
+        // that we want to support.
+
+        // Vec of statements to return from this entire parsing operation.
+        let mut ret = vec![];
+
+        // Here, our statements tokens have been parsed and delimited by
+        // semicolons (;) in the source file. This iterator contains each
+        // of those statements as an array of tokens we then consume and
+        // build the AST further.
+        for statement in statements {
+            if statement.is_empty() {
+                continue
+            }
+
+            let (mut left_paren, mut right_paren) = (0, 0);
+            for i in &statement {
+                match i.token.as_str() {
+                    "(" => left_paren += 1,
+                    ")" => right_paren += 1,
+                    _ => {}
+                }
+            }
+
+            if left_paren != right_paren || (left_paren == 0 || right_paren == 0) {
+                self.error.abort(
+                    "Incorrect number of left and right parenthesis for statement.",
+                    statement[0].line,
+                    statement[0].column,
+                );
+            }
+
+            // Peekable iterator so we can see tokens in advance
+            // without consuming the iterator.
+            let mut iter = statement.iter().peekable();
+
+            // Dummy statement that we'll hopefully fill now.
+            let mut stmt = Statement::default();
+
+            let mut parsing = false;
+            while let Some(token) = iter.next() {
+                if !parsing {
+                    // TODO: MAKE SURE IT'S A SYMBOL
+
+                    // This logic must be changed if we want to support
+                    // multiple return values.
+                    if let Some(next_token) = iter.peek() {
+                        if next_token.token_type == TokenType::Assign {
+                            stmt.line = token.line;
+                            stmt.typ = StatementType::Assign;
+                            stmt.rhs = vec![];
+                            stmt.lhs = Some(Variable {
+                                name: token.token.clone(),
+                                typ: VarType::Dummy,
+                                line: token.line,
+                                column: token.column,
+                            });
+
+                            // Skip over the `=` token.
+                            iter.next();
+                            parsing = true;
+                            continue
+                        }
+
+                        if next_token.token_type == TokenType::LeftParen {
+                            stmt.line = token.line;
+                            stmt.typ = StatementType::Call;
+                            stmt.rhs = vec![];
+                            stmt.lhs = None;
+                            parsing = true;
+                        }
+
+                        if !parsing {
+                            self.error.abort(
+                                &format!("Illegal token `{}`.", next_token.token),
+                                next_token.line,
+                                next_token.column,
+                            );
+                        }
+                    }
+                }
+
+                // If parsing == true, we now know if we're making a variable
+                // assignment or a function call without a return value.
+                // Let's dig deeper to see what the statement's call is, and
+                // what it contains as arguments. With this we'll fill `rhs`.
+                // The arguments could be literal types, other variables, or
+                // even nested function calls.
+                // For now, we don't care if the params are valid, as this is
+                // the job of the semantic analyzer which comes after the
+                // parsing module.
+
+                // The assumption here is that the current token is a function
+                // call, so we check if it's legit and start digging.
+                let func_name = token.token.as_str();
+
+                // TODO: MAKE SURE IT'S A SYMBOL
+                if let Some(op) = Opcode::from_name(&func_name) {
+                    let rhs = self.parse_function_call(token, &mut iter);
+                    stmt.opcode = op;
+                    stmt.rhs = rhs;
+                } else {
+                    self.error.abort(
+                        &format!("Unimplemented opcode `{}`.", func_name),
+                        token.line,
+                        token.column,
+                    );
+                }
+
+                ret.push(stmt);
+                stmt = Statement::default();
+            }
+        }
+
+        ret
+    }
+
+    fn parse_function_call(
+        &self,
+        token: &Token,
+        iter: &mut Peekable<std::slice::Iter<'_, Token>>,
+    ) -> Vec<Arg> {
+        if let Some(next_token) = iter.peek() {
+            if next_token.token_type != TokenType::LeftParen {
+                self.error.abort(
+                    "Invalid function call opening. Must start with a '('.",
+                    next_token.line,
+                    next_token.column,
+                );
+            }
+            // Skip the opening parenthesis
+            iter.next();
+        } else {
+            self.error.abort("Premature ending of statement.", token.line, token.column);
+        }
+
+        let mut ret = vec![];
+
+        // The next element in the iter now hopefully contains an opcode
+        // argument. If it's another opcode, we'll recurse into this
+        // function's logic.
+        // Otherwise, we look for variable and literal types.
+        while let Some(arg) = iter.next() {
+            // ============================
+            // Parse a nested function call
+            // ============================
+            if let Some(op_inner) = Opcode::from_name(&arg.token) {
+                if let Some(paren) = iter.peek() {
+                    if paren.token_type != TokenType::LeftParen {
+                        self.error.abort(
+                            "Invalid function call opening. Must start with a '('.",
+                            paren.line,
+                            paren.column,
+                        );
+                    }
+
+                    // Recurse this function to get the params of the nested one.
+                    let args = self.parse_function_call(arg, iter);
+
+                    // Then we assign a "fake" variable that serves as a stack
+                    // reference.
+                    let var = Variable {
+                        name: format!("_op_inner_{}_{}", arg.line, arg.column),
+                        typ: VarType::Dummy,
+                        line: arg.line,
+                        column: arg.column,
+                    };
+
+                    let arg = Arg::Func(Statement {
+                        typ: StatementType::Assign,
+                        opcode: op_inner,
+                        lhs: Some(var),
+                        rhs: args,
+                        line: arg.line,
+                    });
+
+                    ret.push(arg);
+                    continue
+                }
+
+                self.error.abort(
+                    "Missing tokens in statement, there's a syntax error here.",
+                    arg.line,
+                    arg.column,
+                );
+            }
+
+            // ==========================================
+            // Parse normal argument, not a function call
+            // ==========================================
+            if let Some(sep) = iter.next() {
+                // See if we have a variable or a literal type.
+                match arg.token_type {
+                    TokenType::Symbol => ret.push(Arg::Var(Variable {
+                        name: arg.token.clone(),
+                        typ: VarType::Dummy,
+                        line: arg.line,
+                        column: arg.column,
+                    })),
+
+                    TokenType::Number => {
+                        // Check if we can actually convert this into a number.
+                        match u64::from_str_radix(&arg.token, 10) {
+                            Ok(_) => {}
+                            Err(e) => {
+                                self.error.abort(
+                                    &format!("Failed to convert literal into u64: {}", e),
+                                    arg.line,
+                                    arg.column,
+                                );
+                            }
+                        };
+
+                        ret.push(Arg::Lit(Literal {
+                            name: arg.token.clone(),
+                            typ: LitType::Uint64,
+                            line: arg.line,
+                            column: arg.column,
+                        }))
+                    }
+
+                    TokenType::RightParen => {
+                        if let Some(comma) = iter.peek() {
+                            if comma.token_type == TokenType::Comma {
+                                iter.next();
+                            }
+                        }
+                        break
+                    }
+
+                    x => unimplemented!("{:#?}", x),
+                };
+
+                if sep.token_type == TokenType::RightParen {
+                    if let Some(comma) = iter.peek() {
+                        if comma.token_type == TokenType::Comma {
+                            iter.next();
+                        }
+                    }
+                    // Reached end of args
+                    break
+                }
+
+                if sep.token_type != TokenType::Comma {
+                    self.error.abort(
+                        "Argument separator is not a comma (`,`)",
+                        sep.line,
+                        sep.column,
+                    );
+                }
+            }
+        }
 
         ret
     }