Forráskód Böngészése

zkas/analyzer: Implement type checking.

parazyd 4 éve
szülő
commit
0c39188ead
6 módosított fájl, 275 hozzáadás és 10 törlés
  1. 221 4
      zkas/src/analyzer.rs
  2. 8 0
      zkas/src/ast.rs
  3. 2 2
      zkas/src/main.rs
  4. 20 1
      zkas/src/opcode.rs
  5. 14 1
      zkas/src/parser.rs
  6. 10 2
      zkas/src/types.rs

+ 221 - 4
zkas/src/analyzer.rs

@@ -2,7 +2,13 @@ use std::{io, io::Write, process, str::Chars};
 
 use termion::{color, style};
 
-use crate::ast::{Constants, Statements, Witnesses};
+use crate::{
+    ast::{
+        Constant, Constants, StatementType, Statements, Var, Variable, Variables, Witness,
+        Witnesses,
+    },
+    types::Type,
+};
 
 pub struct Analyzer {
     file: String,
@@ -10,6 +16,7 @@ pub struct Analyzer {
     constants: Constants,
     witnesses: Witnesses,
     statements: Statements,
+    stack: Variables,
 }
 
 impl Analyzer {
@@ -23,11 +30,221 @@ impl Analyzer {
         // 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 }
+        Analyzer {
+            file: filename.to_string(),
+            lines,
+            constants,
+            witnesses,
+            statements,
+            stack: vec![],
+        }
     }
 
-    pub fn analyze(self) {
-        self.error("Semantic analyzer not implemented".to_string(), 1, 0);
+    pub fn analyze_types(&mut self) {
+        // To work around the pedantic safety, we'll make new vectors and
+        // then replace the `statements` and `stack` vectors from the
+        // Analyzer object when we're done.
+        let mut statements = vec![];
+        let mut stack = vec![];
+
+        for statement in &self.statements {
+            let mut stmt = statement.clone();
+
+            match statement.typ {
+                StatementType::Assignment => {
+                    let (return_types, arg_types) = statement.opcode.arg_types();
+                    let mut args = vec![];
+
+                    // For variable length args, we implement BaseArray.
+                    // It's kinda ugly.
+                    if arg_types[0] == Type::BaseArray {
+                        for i in &statement.args {
+                            if let Some(v) = self.lookup_var(&i.name) {
+                                let var_type = match v {
+                                    Var::Constant(c) => c.typ,
+                                    Var::Witness(c) => c.typ,
+                                    Var::Variable(c) => c.typ,
+                                };
+                                if var_type != Type::Base {
+                                    self.error(
+                                        "Incorrect argument type. Expected `Base`.".to_string(),
+                                        i.line,
+                                        i.column,
+                                    );
+                                }
+
+                                let mut arg = i.clone();
+                                arg.typ = var_type;
+                                args.push(arg);
+                            } else {
+                                self.error(
+                                    format!("Unknown argument reference `{}`.", i.name),
+                                    i.line,
+                                    i.column,
+                                );
+                            }
+                        }
+                    } else {
+                        for (idx, i) in statement.args.iter().enumerate() {
+                            if let Some(v) = self.lookup_var(&i.name) {
+                                let var_type = match v {
+                                    Var::Constant(c) => c.typ,
+                                    Var::Witness(c) => c.typ,
+                                    Var::Variable(c) => c.typ,
+                                };
+
+                                if var_type != arg_types[idx] {
+                                    self.error(
+                                        format!(
+                                            "Incorrect argument type. Expected `{:?}`.",
+                                            arg_types[idx]
+                                        ),
+                                        i.line,
+                                        i.column,
+                                    );
+                                }
+
+                                let mut arg = i.clone();
+                                arg.typ = var_type;
+                                args.push(arg);
+                            } else {
+                                self.error(
+                                    format!("Unknown argument reference `{}`.", i.name),
+                                    i.line,
+                                    i.column,
+                                );
+                            }
+                        }
+                    }
+
+                    // Currently we just support a single return type.
+                    let mut var = statement.variable.clone().unwrap();
+                    var.typ = return_types[0];
+                    stmt.variable = Some(var.clone());
+                    stack.push(var.clone());
+                    self.stack = stack.clone();
+                    stmt.args = args;
+                    statements.push(stmt);
+                }
+
+                StatementType::Call => {
+                    let (_, arg_types) = statement.opcode.arg_types();
+                    let mut args = vec![];
+
+                    // For variable length args, we implement BaseArray.
+                    // It's kinda ugly.
+                    if arg_types[0] == Type::BaseArray {
+                        for i in &statement.args {
+                            if let Some(v) = self.lookup_var(&i.name) {
+                                let var_type = match v {
+                                    Var::Constant(c) => c.typ,
+                                    Var::Witness(c) => c.typ,
+                                    Var::Variable(c) => c.typ,
+                                };
+                                if var_type != Type::Base {
+                                    self.error(
+                                        "Incorrect argument type. Expected `Base`.".to_string(),
+                                        i.line,
+                                        i.column,
+                                    );
+                                }
+
+                                let mut arg = i.clone();
+                                arg.typ = var_type;
+                                args.push(arg);
+                            } else {
+                                self.error(
+                                    format!("Unknown argument reference `{}`.", i.name),
+                                    i.line,
+                                    i.column,
+                                );
+                            }
+                        }
+                    } else {
+                        for (idx, i) in statement.args.iter().enumerate() {
+                            if let Some(v) = self.lookup_var(&i.name) {
+                                let var_type = match v {
+                                    Var::Constant(c) => c.typ,
+                                    Var::Witness(c) => c.typ,
+                                    Var::Variable(c) => c.typ,
+                                };
+
+                                if var_type != arg_types[idx] {
+                                    self.error(
+                                        format!(
+                                            "Incorrect argument type. Expected `{:?}`.",
+                                            arg_types[idx]
+                                        ),
+                                        i.line,
+                                        i.column,
+                                    );
+                                }
+
+                                let mut arg = i.clone();
+                                arg.typ = var_type;
+                                args.push(arg);
+                            } else {
+                                self.error(
+                                    format!("Unknown argument reference `{}`.", i.name),
+                                    i.line,
+                                    i.column,
+                                );
+                            }
+                        }
+                    }
+                    stmt.args = args;
+                    statements.push(stmt);
+                }
+                StatementType::Noop => unreachable!(),
+            }
+        }
+
+        self.statements = statements;
+
+        println!("{:#?}", self.statements);
+    }
+
+    fn lookup_var(&self, name: &str) -> Option<Var> {
+        if let Some(r) = self.lookup_constant(name) {
+            return Some(Var::Constant(r))
+        }
+
+        if let Some(r) = self.lookup_witness(name) {
+            return Some(Var::Witness(r))
+        }
+
+        if let Some(r) = self.lookup_stack(name) {
+            return Some(Var::Variable(r))
+        }
+
+        None
+    }
+
+    fn lookup_constant(&self, name: &str) -> Option<Constant> {
+        for i in &self.constants {
+            if i.name == name {
+                return Some(i.clone())
+            }
+        }
+        None
+    }
+
+    fn lookup_witness(&self, name: &str) -> Option<Witness> {
+        for i in &self.witnesses {
+            if i.name == name {
+                return Some(i.clone())
+            }
+        }
+        None
+    }
+
+    fn lookup_stack(&self, name: &str) -> Option<Variable> {
+        for i in &self.stack {
+            if i.name == name {
+                return Some(i.clone())
+            }
+        }
+        None
     }
 
     fn error(&self, msg: String, ln: usize, col: usize) {

+ 8 - 0
zkas/src/ast.rs

@@ -9,9 +9,16 @@ pub enum StatementType {
     Noop,
 }
 
+pub enum Var {
+    Constant(Constant),
+    Witness(Witness),
+    Variable(Variable),
+}
+
 #[derive(Clone, Debug)]
 pub struct Variable {
     pub name: String,
+    pub typ: Type,
     pub line: usize,
     pub column: usize,
 }
@@ -42,6 +49,7 @@ pub type UnparsedWitnesses = HashMap<String, (Token, Token)>;
 
 pub type Constants = Vec<Constant>;
 pub type Witnesses = Vec<Witness>;
+pub type Variables = Vec<Variable>;
 pub type Statements = Vec<Statement>;
 
 #[derive(Clone, Debug)]

+ 2 - 2
zkas/src/main.rs

@@ -41,8 +41,8 @@ fn main() -> Result<()> {
     // println!("{:#?}", witnesses);
     // println!("{:#?}", statements);
 
-    let analyzer = Analyzer::new(filename, source.chars(), constants, witnesses, statements);
-    analyzer.analyze();
+    let mut analyzer = Analyzer::new(filename, source.chars(), constants, witnesses, statements);
+    analyzer.analyze_types();
 
     Ok(())
 }

+ 20 - 1
zkas/src/opcode.rs

@@ -1,4 +1,6 @@
-// Opcodes supported by the VM
+use crate::types::Type;
+
+/// Opcodes supported by the VM
 #[derive(Clone, Debug)]
 pub enum Opcode {
     EcAdd = 0x00,
@@ -15,3 +17,20 @@ pub enum Opcode {
 
     Noop = 0xff,
 }
+
+impl Opcode {
+    pub fn arg_types(&self) -> (Vec<Type>, Vec<Type>) {
+        match self {
+            // (return_type, opcode_arg_types)
+            Opcode::EcAdd => (vec![Type::EcPoint], vec![Type::EcPoint, Type::EcPoint]),
+            Opcode::EcMul => (vec![Type::EcPoint], vec![Type::Scalar, Type::EcFixedPoint]),
+            Opcode::EcMulShort => (vec![Type::EcPoint], vec![Type::Base, Type::EcFixedPoint]),
+            Opcode::EcGetX => (vec![Type::Base], vec![Type::EcPoint]),
+            Opcode::EcGetY => (vec![Type::Base], vec![Type::EcPoint]),
+            Opcode::PoseidonHash => (vec![Type::Base], vec![Type::BaseArray]),
+            Opcode::CalculateMerkleRoot => (vec![Type::Base], vec![Type::MerklePath, Type::Base]),
+            Opcode::ConstrainInstance => (vec![], vec![Type::Base]),
+            Opcode::Noop => (vec![], vec![]),
+        }
+    }
+}

+ 14 - 1
zkas/src/parser.rs

@@ -251,6 +251,8 @@ 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();
@@ -470,6 +472,7 @@ impl Parser {
                             stmt.typ = StatementType::Assignment;
                             stmt.variable = Some(Variable {
                                 name: token.token.clone(),
+                                typ: Type::Dummy,
                                 line: token.line,
                                 column: token.column,
                             });
@@ -495,6 +498,11 @@ impl Parser {
                     }
                 }
 
+                // This matching could be moved over into the semantic analyzer.
+                // We could just parse any kind of symbol here, and then do lookup
+                // from the analyzer, to see if the calls actually exist and are
+                // supported.
+                // But for now, we'll just leave it here and expand later.
                 match token.token.as_str() {
                     "poseidon_hash" => {
                         stmt.args = self.parse_function_call(token, &mut iter);
@@ -613,7 +621,12 @@ impl Parser {
         // Eat up function arguments
         let mut args = vec![];
         while let Some((arg, sep)) = iter.next_tuple() {
-            args.push(Variable { name: arg.token.clone(), line: arg.line, column: arg.column });
+            args.push(Variable {
+                name: arg.token.clone(),
+                typ: Type::Dummy,
+                line: arg.line,
+                column: arg.column,
+            });
 
             if sep.token_type == TokenType::RightParen {
                 // Reached end of args

+ 10 - 2
zkas/src/types.rs

@@ -1,9 +1,17 @@
 /// Types supported by the VM
-#[derive(Clone, Debug)]
+#[derive(Copy, Clone, PartialEq, Debug)]
 pub enum Type {
     EcPoint = 0x00,
+
     EcFixedPoint = 0x01,
+
     Base = 0x10,
-    Scalar = 0x11,
+    BaseArray = 0x11,
+
+    Scalar = 0x12,
+    ScalarArray = 0x13,
+
     MerklePath = 0x20,
+
+    Dummy = 0xff,
 }