zkas/analyzer: Implement type checking.

This commit is contained in:
parazyd
2022-01-02 23:06:04 +01:00
parent aa00491572
commit 0c39188ead
6 changed files with 275 additions and 10 deletions

View File

@@ -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) {

View File

@@ -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)]

View File

@@ -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(())
}

View File

@@ -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![]),
}
}
}

View File

@@ -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

View File

@@ -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,
}