mirror of
https://github.com/darkrenaissance/darkfi.git
synced 2026-01-08 22:28:12 -05:00
zkas/analyzer: Implement type checking.
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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![]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user