/* This file is part of DarkFi (https://dark.fi) * * Copyright (C) 2020-2023 Dyne.org foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ use std::{ io::{stdin, stdout, Read, Result, Write}, str::Chars, }; use super::{ ast::{Arg, Constant, Literal, Statement, StatementType, Var, Variable, Witness}, error::ErrorEmitter, Opcode, VarType, }; pub struct Analyzer { pub constants: Vec, pub witnesses: Vec, pub statements: Vec, pub literals: Vec, pub heap: Vec, error: ErrorEmitter, } impl Analyzer { pub fn new( filename: &str, source: Chars, constants: Vec, witnesses: Vec, statements: Vec, ) -> Self { // For nice error reporting, we'll load everything into a string // vector so we have references to lines. let lines: Vec = source.as_str().lines().map(|x| x.to_string()).collect(); let error = ErrorEmitter::new("Semantic", filename, lines); Self { constants, witnesses, statements, literals: vec![], heap: vec![], error } } pub fn analyze_types(&mut self) -> Result<()> { // To work around the pedantic safety, we'll make new vectors and then // replace the `statements` and `heap` vectors from the `Analyzer` // object when we are done. let mut statements = vec![]; let mut heap = vec![]; for statement in &self.statements { //println!("{:?}", statement); let mut stmt = statement.clone(); let (return_types, arg_types) = statement.opcode.arg_types(); let mut rhs = vec![]; // This handling is kinda limiting, but it'll do for now. if !(arg_types[0] == VarType::BaseArray || arg_types[0] == VarType::ScalarArray) { // Check that number of args is correct if statement.rhs.len() != arg_types.len() { return Err(self.error.abort( &format!( "Incorrect number of arguments for statement. Expected {}, got {}.", arg_types.len(), statement.rhs.len() ), statement.line, 1, )) } } else { // In case of arrays, check there's at least one element. if statement.rhs.is_empty() { return Err(self.error.abort( "Expected at least one element for statement using arrays.", statement.line, 1, )) } } // Edge-cases for some opcodes #[allow(clippy::single_match)] match &statement.opcode { Opcode::RangeCheck => { if let Arg::Lit(arg0) = &statement.rhs[0] { if &arg0.name != "64" && &arg0.name != "253" { return Err(self.error.abort( "Supported range checks are only 64 and 253 bits.", arg0.line, arg0.column, )) } } else { return Err(self.error.abort( "Invalid argument for range_check opcode.", statement.line, 0, )) } } _ => {} } for (idx, arg) in statement.rhs.iter().enumerate() { // In case an argument is a function call, we will first // convert it to another statement that will get executed // before this one. An important assumption is that this // opcode has a return value. When executed we will push // this value onto the heap and use it as a reference to // the actual statement we're parsing at this moment. // TODO: This needs a recursive algorithm, as this only // allows a single nested function. if let Arg::Func(func) = arg { let (f_return_types, f_arg_types) = func.opcode.arg_types(); if f_return_types.is_empty() { return Err(self.error.abort( &format!( "Used a function argument which doesn't have a return value: {:?}", func.opcode ), statement.line, 1, )) } let v = Variable { name: func.lhs.clone().unwrap().name, typ: f_return_types[0], line: func.lhs.clone().unwrap().line, column: func.lhs.clone().unwrap().column, }; if arg_types[0] == VarType::BaseArray { if f_return_types[0] != VarType::Base { return Err(self.error.abort( &format!( "Function passed as argument returns wrong type. Expected `{:?}`, got `{:?}`.", VarType::Base, f_return_types[0], ), v.line, v.column, )) } } else if arg_types[0] == VarType::ScalarArray { if f_return_types[0] != VarType::Scalar { return Err(self.error.abort( &format!( "Function passed as argument returns wrong type. Expected `{:?}`, got `{:?}`.", VarType::Scalar, f_return_types[0], ), v.line, v.column, )); } } else if f_return_types[0] != arg_types[idx] { return Err(self.error.abort( &format!( "Function passed as argument returns wrong type. Expected `{:?}`, got `{:?}`.", arg_types[idx], f_return_types[0], ), v.line, v.column, )) } // Replace the statement function call with the variable from // the statement we just created to represent this nest. stmt.rhs[idx] = Arg::Var(v.clone()); let mut rhs_inner = vec![]; for (inner_idx, i) in func.rhs.iter().enumerate() { // TODO: Implement cases where `i` is type Arg::Literal // TODO: Implement cases where `i` is type Arg::Func if let Arg::Var(v) = i { if let Some(var_ref) = self.lookup_var(&v.name) { let (var_type, ln, col) = match var_ref { Var::Constant(c) => (c.typ, c.line, c.column), Var::Witness(c) => (c.typ, c.line, c.column), Var::Variable(c) => (c.typ, c.line, c.column), }; if var_type != f_arg_types[inner_idx] { return Err(self.error.abort( &format!( "Incorrect argument type. Expected `{:?}`, got `{:?}`.", f_arg_types[inner_idx], var_type ), ln, col, )) } // Apply the proper type. let mut v_new = v.clone(); v_new.typ = var_type; rhs_inner.push(Arg::Var(v_new)); continue } return Err(self.error.abort( &format!("Unknown variable reference `{}`.", v.name), v.line, v.column, )) } else if let Arg::Lit(l) = i { return Err(self.error.abort( &format!("Expected argument `{}` to be of type Variable. Literals are not yet supported in nested function calls.", l.name), l.line, l.column, )) } else if let Arg::Func(f) = i { return Err(self.error.abort( &format!("Expected argument `{}` to be of type Variable. Nested function calls are not yet supported beyond a depth of 1.", Opcode::name(&f.opcode)), f.line, 0, )) } else { unreachable!(); } } let s = Statement { typ: func.typ, opcode: func.opcode, lhs: Some(v.clone()), rhs: rhs_inner, line: func.line, }; // The lhs of the inner function call becomes rhs of the outer one. rhs.push(Arg::Var(v.clone())); // Add this to the list of statements. statements.push(s); // We replace self.heap here so we can do proper heap lookups. heap.push(v.clone()); self.heap = heap.clone(); //println!("{:#?}", heap); //println!("{:#?}", statements); continue } // <-- Arg::Func // The literals get pushed on their own "heap", and // then the compiler will reference them by their own // index when it comes to running the statement that // requires the literal type. if let Arg::Lit(v) = arg { // Match this literal type to a VarType for // type checking. let var_type = v.typ.to_vartype(); // TODO: Refactor the Array type checks here and in the Arg::Var // section so that there is less repetition. // Validation for Array types if arg_types[0] == VarType::BaseArray { if var_type != VarType::Base { return Err(self.error.abort( &format!( "Incorrect argument type. Expected `{:?}`, got `{:?}`.", VarType::Base, var_type ), v.line, v.column, )) } } else if arg_types[0] == VarType::ScalarArray && var_type != VarType::Scalar { return Err(self.error.abort( &format!( "Incorrect argument type. Expected `{:?}`, got `{:?}`.", VarType::Scalar, var_type ), v.line, v.column, )) } // Validation for non-Array types if var_type != arg_types[idx] { return Err(self.error.abort( &format!( "Incorrect argument type. Expected `{:?}`, got `{:?}`.", arg_types[idx], var_type ), v.line, v.column, )) } self.literals.push(v.clone()); rhs.push(Arg::Lit(v.clone())); continue } if let Arg::Var(v) = arg { // Look up variable and check if type is correct. if let Some(s_var) = self.lookup_var(&v.name) { let (var_type, _ln, _col) = match s_var { Var::Constant(c) => (c.typ, c.line, c.column), Var::Witness(c) => (c.typ, c.line, c.column), Var::Variable(c) => (c.typ, c.line, c.column), }; if arg_types[0] == VarType::BaseArray { if var_type != VarType::Base { return Err(self.error.abort( &format!( "Incorrect argument type. Expected `{:?}`, got `{:?}`.", VarType::Base, var_type ), v.line, v.column, )) } } else if arg_types[0] == VarType::ScalarArray { if var_type != VarType::Scalar { return Err(self.error.abort( &format!( "Incorrect argument type. Expected `{:?}`, got `{:?}`.", VarType::Scalar, var_type ), v.line, v.column, )) } } else if var_type != arg_types[idx] && arg_types[idx] != VarType::Any { return Err(self.error.abort( &format!( "Incorrect argument type. Expected `{:?}`, got `{:?}`.", arg_types[idx], var_type ), v.line, v.column, )) } // Replace Dummy type with correct type. let mut v_new = v.clone(); v_new.typ = var_type; rhs.push(Arg::Var(v_new)); continue } return Err(self.error.abort( &format!("Unknown variable reference `{}`.", v.name), v.line, v.column, )) } } // <-- statement.rhs.iter().enumerate() // We now type-checked and assigned types to the statement rhs, // so now we apply it to the statement. stmt.rhs = rhs; // In case this statement is an assignment, we will push its // result on the heap. if statement.typ == StatementType::Assign { let mut var = statement.lhs.clone().unwrap(); // Since we are doing an assignment, ensure that there is a return type. if return_types.is_empty() { return Err(self.error.abort( "Cannot perform assignment without a return type", var.line, var.column, )) } var.typ = return_types[0]; stmt.lhs = Some(var.clone()); heap.push(var.clone()); self.heap = heap.clone(); } //println!("{:#?}", stmt); statements.push(stmt); } // <-- for statement in &self.statements // Here we replace the self.statements and self.heap with what we // built so far. These can be used later on by the compiler after // this function is finished. self.statements = statements; self.heap = heap; //println!("=================STATEMENTS===============\n{:#?}", self.statements); //println!("====================HEAP==================\n{:#?}", self.heap); //println!("==================LITERALS================\n{:#?}", self.literals); Ok(()) } fn lookup_var(&self, name: &str) -> Option { 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_heap(name) { return Some(Var::Variable(r)) } None } fn lookup_constant(&self, name: &str) -> Option { for i in &self.constants { if i.name == name { return Some(i.clone()) } } None } fn lookup_witness(&self, name: &str) -> Option { for i in &self.witnesses { if i.name == name { return Some(i.clone()) } } None } fn lookup_heap(&self, name: &str) -> Option { for i in &self.heap { if i.name == name { return Some(i.clone()) } } None } pub fn analyze_semantic(&mut self) -> Result<()> { let mut heap = vec![]; println!("Loading constants...\n-----"); for i in &self.constants { println!("Adding `{}` to heap", i.name); heap.push(&i.name); Analyzer::pause(); } println!("Heap:\n{:#?}\n-----", heap); println!("Loading witnesses...\n-----"); for i in &self.witnesses { println!("Adding `{}` to heap", i.name); heap.push(&i.name); Analyzer::pause(); } println!("Heap:\n{:#?}\n-----", heap); println!("Loading circuit..."); for i in &self.statements { let mut argnames = vec![]; for arg in &i.rhs { if let Arg::Var(arg) = arg { argnames.push(arg.name.clone()); } else if let Arg::Lit(lit) = arg { argnames.push(lit.name.clone()); } else { unreachable!() } } println!("Executing: {:?}({:?})", i.opcode, argnames); Analyzer::pause(); for arg in &i.rhs { if let Arg::Var(arg) = arg { print!("Looking up `{}` on the heap... ", arg.name); if let Some(index) = heap.iter().position(|&r| r == &arg.name) { println!("Found at heap index {}", index); } else { return Err(self.error.abort( &format!("Could not find `{}` on the heap", arg.name), arg.line, arg.column, )) } } else if let Arg::Lit(lit) = arg { println!("Using literal `{}`", lit.name); } else { println!("{:#?}", arg); unreachable!(); } Analyzer::pause(); } match i.typ { StatementType::Assign => { println!("Pushing result as `{}` to heap", &i.lhs.as_ref().unwrap().name); heap.push(&i.lhs.as_ref().unwrap().name); println!("Heap:\n{:#?}\n-----", heap); } StatementType::Call => { println!("-----"); } _ => unreachable!(), } } Ok(()) } fn pause() { let msg = b"[Press Enter to continue]\r"; let mut stdout = stdout(); let _ = stdout.write(msg).unwrap(); stdout.flush().unwrap(); let _ = stdin().read(&mut [0]).unwrap(); write!(stdout, "\x1b[1A\r\x1b[K\r").unwrap(); } }