From b6d00ed04fcae596c787d545ed53c7b3e62ee461 Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 20 Feb 2023 14:53:32 +0100 Subject: [PATCH] Split analyzer. --- src/analyzer/mod.rs | 603 +---------------------------------- src/analyzer/pil_analyzer.rs | 568 +++++++++++++++++++++++++++++++++ src/lib.rs | 1 + src/line_utils.rs | 37 +++ 4 files changed, 612 insertions(+), 597 deletions(-) create mode 100644 src/analyzer/pil_analyzer.rs create mode 100644 src/line_utils.rs diff --git a/src/analyzer/mod.rs b/src/analyzer/mod.rs index ebbf04b1e..edd524797 100644 --- a/src/analyzer/mod.rs +++ b/src/analyzer/mod.rs @@ -1,46 +1,16 @@ -use std::collections::{HashMap, HashSet}; -use std::fs; -use std::path::{Path, PathBuf}; +pub mod pil_analyzer; + +use std::collections::HashMap; +use std::path::Path; -use crate::parser; -use crate::parser::ast; pub use crate::parser::ast::{BinaryOperator, ConstantNumberType, UnaryOperator}; pub fn analyze(path: &Path) -> Analyzed { - let mut ctx = Context::new(); - ctx.process_file(path); - ctx.into() + pil_analyzer::process_pil_file(path) } pub fn analyze_string(contents: &str) -> Analyzed { - let mut ctx = Context::new(); - ctx.process_file_contents(Path::new("input"), contents); - ctx.into() -} - -#[derive(Default)] -struct Context { - namespace: String, - polynomial_degree: ConstantNumberType, - /// Constants are not namespaced! - constants: HashMap, - definitions: HashMap)>, - public_declarations: HashMap, - macros: HashMap, - identities: Vec, - /// The order in which definitions and identities - /// appear in the source. - source_order: Vec, - included_files: HashSet, - line_starts: Vec, - current_file: PathBuf, - commit_poly_counter: u64, - constant_poly_counter: u64, - intermediate_poly_counter: u64, - identity_counter: HashMap, - local_variables: HashMap, - /// If we are evaluating a macro, this holds the arguments. - macro_arguments: Option>, + pil_analyzer::process_pil_file_contents(contents) } pub enum StatementIdentifier { @@ -114,27 +84,6 @@ impl Analyzed { } } -impl From for Analyzed { - fn from( - Context { - constants, - definitions, - public_declarations, - identities, - source_order, - .. - }: Context, - ) -> Self { - Self { - constants, - definitions, - public_declarations, - identities, - source_order, - } - } -} - pub struct Polynomial { pub id: u64, pub source: SourceRef, @@ -213,548 +162,8 @@ pub enum PolynomialType { Constant, Intermediate, } - -#[derive(Debug)] -pub struct MacroDefinition { - pub source: SourceRef, - pub absolute_name: String, - pub parameters: Vec, - pub identities: Vec, - pub expression: Option, -} - #[derive(Debug, Clone, PartialEq)] pub struct SourceRef { pub file: String, // TODO should maybe be a shared pointer pub line: usize, } - -impl Context { - pub fn new() -> Context { - Context { - namespace: "Global".to_string(), - ..Default::default() - } - } - - pub fn process_file(&mut self, path: &Path) { - let path = path - .canonicalize() - .unwrap_or_else(|e| panic!("File {path:?} not found: {e}")); - if !self.included_files.insert(path.clone()) { - return; - } - let contents = fs::read_to_string(path.clone()).unwrap(); - self.process_file_contents(&path, &contents); - } - - pub fn process_file_contents(&mut self, path: &Path, contents: &str) { - let old_current_file = std::mem::take(&mut self.current_file); - let old_line_starts = std::mem::take(&mut self.line_starts); - - // TOOD make this work for other line endings - self.line_starts = compute_line_starts(contents); - self.current_file = path.to_path_buf(); - let pil_file = - parser::parse(Some(path.to_str().unwrap()), contents).unwrap_or_else(|err| { - eprintln!("Error parsing .pil file:"); - err.output_to_stderr(); - panic!(); - }); - - for statement in &pil_file.0 { - use ast::Statement; - match statement { - Statement::Include(_, include) => self.handle_include(include), - Statement::Namespace(_, name, degree) => self.handle_namespace(name, degree), - Statement::PolynomialDefinition(start, name, value) => { - self.handle_polynomial_definition( - self.to_source_ref(*start), - name, - &None, - PolynomialType::Intermediate, - None, - Some(value), - ); - } - Statement::PublicDeclaration(start, name, polynomial, index) => self - .handle_public_declaration(self.to_source_ref(*start), name, polynomial, index), - Statement::PolynomialConstantDeclaration(start, polynomials) => self - .handle_polynomial_declarations( - self.to_source_ref(*start), - polynomials, - PolynomialType::Constant, - ), - Statement::PolynomialConstantDefinition(start, name, parameters, value) => { - self.handle_polynomial_definition( - self.to_source_ref(*start), - name, - &None, - PolynomialType::Constant, - Some(parameters), - Some(value), - ); - } - Statement::PolynomialCommitDeclaration(start, polynomials) => self - .handle_polynomial_declarations( - self.to_source_ref(*start), - polynomials, - PolynomialType::Committed, - ), - Statement::ConstantDefinition(_, name, value) => { - self.handle_constant_definition(name, value) - } - Statement::MacroDefinition(start, name, params, statments, expression) => self - .handle_macro_definition( - self.to_source_ref(*start), - name, - params, - statments, - expression, - ), - _ => { - self.handle_identity_statement(statement); - } - } - } - - self.current_file = old_current_file; - self.line_starts = old_line_starts; - } - - fn to_source_ref(&self, start: usize) -> SourceRef { - let file = self.current_file.file_name().unwrap().to_str().unwrap(); - SourceRef { - line: offset_to_line(start, &self.line_starts), - file: file.to_string(), - } - } - - fn handle_identity_statement(&mut self, statement: &ast::Statement) { - if let ast::Statement::FunctionCall(_start, name, arguments) = statement { - if !self.macros.contains_key(name) { - panic!( - "Macro {name} not found - only macros allowed at this point, no fixed columns." - ); - } - // TODO check that it does not contain local variable references. - // But we also need to do some other well-formedness checks. - if self.process_macro_call(name, arguments).is_some() { - panic!("Invoked a macro in statement context with non-empty expression."); - } - return; - } - - let (start, kind, left, right) = match statement { - ast::Statement::PolynomialIdentity(start, expression) => ( - start, - IdentityKind::Polynomial, - SelectedExpressions { - selector: Some(self.process_expression(expression)), - expressions: vec![], - }, - SelectedExpressions::default(), - ), - ast::Statement::PlookupIdentity(start, key, haystack) => ( - start, - IdentityKind::Plookup, - self.process_selected_expression(key), - self.process_selected_expression(haystack), - ), - ast::Statement::PermutationIdentity(start, left, right) => ( - start, - IdentityKind::Permutation, - self.process_selected_expression(left), - self.process_selected_expression(right), - ), - ast::Statement::ConnectIdentity(start, left, right) => ( - start, - IdentityKind::Connect, - SelectedExpressions { - selector: None, - expressions: self.process_expressions(left), - }, - SelectedExpressions { - selector: None, - expressions: self.process_expressions(right), - }, - ), - // TODO at some point, these should all be caught by the type checker. - _ => { - panic!("Only identities allowed at this point.") - } - }; - let id = self.dispense_id(kind); - let identity = Identity { - id, - kind, - source: self.to_source_ref(*start), - left, - right, - }; - let id = self.identities.len(); - self.identities.push(identity); - self.source_order.push(StatementIdentifier::Identity(id)); - } - - fn handle_include(&mut self, path: &str) { - let mut dir = self.current_file.parent().unwrap().to_owned(); - dir.push(path); - self.process_file(&dir); - } - - fn handle_namespace(&mut self, name: &str, degree: &ast::Expression) { - self.polynomial_degree = self.evaluate_expression(degree).unwrap(); - self.namespace = name.to_owned(); - } - - fn handle_polynomial_declarations( - &mut self, - source: SourceRef, - polynomials: &[ast::PolynomialName], - polynomial_type: PolynomialType, - ) { - for ast::PolynomialName { name, array_size } in polynomials { - self.handle_polynomial_definition( - source.clone(), - name, - array_size, - polynomial_type, - None, - None, - ); - } - } - - fn handle_polynomial_definition( - &mut self, - source: SourceRef, - name: &String, - array_size: &Option, - polynomial_type: PolynomialType, - parameters: Option<&[String]>, - value: Option<&ast::Expression>, - ) -> u64 { - if parameters.is_some() { - assert!(array_size.is_none()); - assert!(polynomial_type == PolynomialType::Constant); - } - let length = array_size - .as_ref() - .map(|l| self.evaluate_expression(l).unwrap()); - let counter = match polynomial_type { - PolynomialType::Committed => &mut self.commit_poly_counter, - PolynomialType::Constant => &mut self.constant_poly_counter, - PolynomialType::Intermediate => &mut self.intermediate_poly_counter, - }; - let id = *counter; - *counter += length.unwrap_or(1) as u64; - let poly = Polynomial { - id, - source, - absolute_name: self.namespaced(name), - degree: self.polynomial_degree, - poly_type: polynomial_type, - length, - }; - let name = poly.absolute_name.clone(); - assert!(self.local_variables.is_empty()); - self.local_variables = parameters - .map(|p| { - p.iter() - .enumerate() - .map(|(i, p)| (p.clone(), i as u64)) - .collect() - }) - .unwrap_or_default(); - let value = value.map(|e| self.process_expression(e)); - self.local_variables.clear(); - let is_new = self - .definitions - .insert(name.clone(), (poly, value)) - .is_none(); - assert!(is_new); - self.source_order - .push(StatementIdentifier::Definition(name)); - id - } - - fn handle_public_declaration( - &mut self, - source: SourceRef, - name: &str, - poly: &ast::PolynomialReference, - index: &ast::Expression, - ) { - let id = self.public_declarations.len() as u64; - self.public_declarations.insert( - name.to_string(), - PublicDeclaration { - id, - source, - name: name.to_string(), - polynomial: self.process_polynomial_reference(poly), - index: self.evaluate_expression(index).unwrap(), - }, - ); - self.source_order - .push(StatementIdentifier::PublicDeclaration(name.to_string())); - } - - fn handle_constant_definition(&mut self, name: &str, value: &ast::Expression) { - // TODO does the order matter here? - let is_new = self - .constants - .insert(name.to_string(), self.evaluate_expression(value).unwrap()) - .is_none(); - assert!(is_new, "Constant {name} was defined twice."); - } - - fn dispense_id(&mut self, kind: IdentityKind) -> u64 { - let cnt = self.identity_counter.entry(kind).or_default(); - let id = *cnt; - *cnt += 1; - id - } - - fn handle_macro_definition( - &mut self, - source: SourceRef, - name: &String, - params: &[String], - statements: &[ast::Statement], - expression: &Option, - ) { - let is_new = self - .macros - .insert( - name.clone(), - MacroDefinition { - source, - absolute_name: self.namespaced(name), - parameters: params.to_vec(), - identities: statements.to_vec(), - expression: expression.clone(), - }, - ) - .is_none(); - assert!(is_new); - } - - fn namespaced(&self, name: &String) -> String { - self.namespaced_ref(&None, name) - } - - fn namespaced_ref(&self, namespace: &Option, name: &String) -> String { - format!("{}.{name}", namespace.as_ref().unwrap_or(&self.namespace)) - } - - fn process_selected_expression( - &mut self, - expr: &ast::SelectedExpressions, - ) -> SelectedExpressions { - SelectedExpressions { - selector: expr.selector.as_ref().map(|e| self.process_expression(e)), - expressions: self.process_expressions(&expr.expressions), - } - } - - fn process_expressions(&mut self, exprs: &[ast::Expression]) -> Vec { - exprs.iter().map(|e| self.process_expression(e)).collect() - } - - fn process_expression(&mut self, expr: &ast::Expression) -> Expression { - match expr { - ast::Expression::Constant(name) => Expression::Constant(name.clone()), - ast::Expression::PolynomialReference(poly) => { - if poly.namespace.is_none() && self.local_variables.contains_key(&poly.name) { - let id = self.local_variables[&poly.name]; - // TODO to make this work inside macros, "next" and "index" need to be - // their own ast nodes / operators. - assert!(!poly.next); - assert!(poly.index.is_none()); - if let Some(arguments) = &self.macro_arguments { - arguments[id as usize].clone() - } else { - Expression::LocalVariableReference(id) - } - } else { - Expression::PolynomialReference(self.process_polynomial_reference(poly)) - } - } - ast::Expression::PublicReference(name) => Expression::PublicReference(name.clone()), - ast::Expression::Number(n) => Expression::Number(*n), - ast::Expression::BinaryOperation(left, op, right) => { - if let Some(value) = self.evaluate_binary_operation(left, op, right) { - Expression::Number(value) - } else { - Expression::BinaryOperation( - Box::new(self.process_expression(left)), - *op, - Box::new(self.process_expression(right)), - ) - } - } - ast::Expression::UnaryOperation(op, value) => { - if let Some(value) = self.evaluate_unary_operation(op, value) { - Expression::Number(value) - } else { - Expression::UnaryOperation(*op, Box::new(self.process_expression(value))) - } - } - ast::Expression::FunctionCall(name, arguments) if self.macros.contains_key(name) => { - self.process_macro_call(name, arguments) - .expect("Invoked a macro in expression context with empty expression.") - } - ast::Expression::FunctionCall(name, arguments) => { - Expression::FunctionCall(self.namespaced(name), self.process_expressions(arguments)) - } - ast::Expression::FreeInput(_) => panic!(), - } - } - - fn process_macro_call( - &mut self, - name: &String, - arguments: &[ast::Expression], - ) -> Option { - let arguments = Some(self.process_expressions(arguments)); - let old_arguments = std::mem::replace(&mut self.macro_arguments, arguments); - - let old_locals = std::mem::take(&mut self.local_variables); - - let mac = &self - .macros - .get(name) - .unwrap_or_else(|| panic!("Macro {name} not found.")); - self.local_variables = mac - .parameters - .iter() - .enumerate() - .map(|(i, n)| (n.clone(), i as u64)) - .collect(); - // TODO avoid clones - let expression = mac.expression.clone(); - let identities = mac.identities.clone(); - for identity in &identities { - self.handle_identity_statement(identity); - } - let result = expression.map(|expr| self.process_expression(&expr)); - self.macro_arguments = old_arguments; - self.local_variables = old_locals; - result - } - - fn process_polynomial_reference(&self, poly: &ast::PolynomialReference) -> PolynomialReference { - let index = poly - .index - .as_ref() - .map(|i| self.evaluate_expression(i).unwrap() as u64); - PolynomialReference { - name: self.namespaced_ref(&poly.namespace, &poly.name), - index, - next: poly.next, - } - } - - fn evaluate_expression(&self, expr: &ast::Expression) -> Option { - match expr { - ast::Expression::Constant(name) => Some( - *self - .constants - .get(name) - .unwrap_or_else(|| panic!("Constant {name} not found.")), - ), - ast::Expression::PolynomialReference(_) => None, - ast::Expression::PublicReference(_) => None, - ast::Expression::Number(n) => Some(*n), - ast::Expression::BinaryOperation(left, op, right) => { - self.evaluate_binary_operation(left, op, right) - } - ast::Expression::UnaryOperation(op, value) => self.evaluate_unary_operation(op, value), - ast::Expression::FunctionCall(_, _) => None, - ast::Expression::FreeInput(_) => panic!(), - } - } - - fn evaluate_binary_operation( - &self, - left: &ast::Expression, - op: &BinaryOperator, - right: &ast::Expression, - ) -> Option { - // TODO handle owerflow and maybe use bigint instead. - if let (Some(left), Some(right)) = ( - self.evaluate_expression(left), - self.evaluate_expression(right), - ) { - Some(match op { - BinaryOperator::Add => left + right, - BinaryOperator::Sub => left - right, - BinaryOperator::Mul => left * right, - BinaryOperator::Div => left / right, - BinaryOperator::Pow => { - assert!(right <= u32::MAX.into()); - left.pow(right as u32) - } - BinaryOperator::Mod => left % right, - BinaryOperator::BinaryAnd => left & right, - BinaryOperator::BinaryOr => left | right, - BinaryOperator::ShiftLeft => left << right, - BinaryOperator::ShiftRight => left >> right, - }) - } else { - None - } - } - - fn evaluate_unary_operation( - &self, - op: &UnaryOperator, - value: &ast::Expression, - ) -> Option { - // TODO handle owerflow and maybe use bigint instead. - self.evaluate_expression(value).map(|v| match op { - UnaryOperator::Plus => v, - UnaryOperator::Minus => -v, - }) - } -} - -fn compute_line_starts(source: &str) -> Vec { - std::iter::once(0) - .chain(source.match_indices('\n').map(|(i, _)| i + 1)) - .collect::>() -} - -fn offset_to_line(offset: usize, line_starts: &[usize]) -> usize { - match line_starts.binary_search(&offset) { - Ok(line) => line + 1, - Err(next_line) => next_line, - } -} - -#[cfg(test)] -mod test { - use super::{compute_line_starts, offset_to_line}; - - #[test] - pub fn line_calc() { - let input = "abc\nde"; - let breaks = compute_line_starts(input); - let lines = (0..input.len()) - .map(|o| offset_to_line(o, &breaks)) - .collect::>(); - assert_eq!(lines, [1, 1, 1, 1, 2, 2]); - } - - #[test] - pub fn line_calc_empty_start() { - let input = "\nab\n\nc\nde\n"; - let breaks = compute_line_starts(input); - let lines = (0..input.len()) - .map(|o| offset_to_line(o, &breaks)) - .collect::>(); - assert_eq!(lines, [1, 2, 2, 2, 3, 4, 4, 5, 5, 5]); - } -} diff --git a/src/analyzer/pil_analyzer.rs b/src/analyzer/pil_analyzer.rs new file mode 100644 index 000000000..b5847d56f --- /dev/null +++ b/src/analyzer/pil_analyzer.rs @@ -0,0 +1,568 @@ +use std::collections::{HashMap, HashSet}; +use std::fs; +use std::path::{Path, PathBuf}; + +use crate::parser::ast; +pub use crate::parser::ast::{BinaryOperator, ConstantNumberType, UnaryOperator}; +use crate::{line_utils, parser}; + +use super::*; + +pub fn process_pil_file(path: &Path) -> Analyzed { + let mut ctx = PILContext::new(); + ctx.process_file(path); + ctx.into() +} + +pub fn process_pil_file_contents(contents: &str) -> Analyzed { + let mut ctx = PILContext::new(); + ctx.process_file_contents(Path::new("input"), contents); + ctx.into() +} + +#[derive(Default)] +struct PILContext { + namespace: String, + polynomial_degree: ConstantNumberType, + /// Constants are not namespaced! + constants: HashMap, + definitions: HashMap)>, + public_declarations: HashMap, + macros: HashMap, + identities: Vec, + /// The order in which definitions and identities + /// appear in the source. + source_order: Vec, + included_files: HashSet, + line_starts: Vec, + current_file: PathBuf, + commit_poly_counter: u64, + constant_poly_counter: u64, + intermediate_poly_counter: u64, + identity_counter: HashMap, + local_variables: HashMap, + /// If we are evaluating a macro, this holds the arguments. + macro_arguments: Option>, +} + +#[derive(Debug)] +pub struct MacroDefinition { + pub source: SourceRef, + pub absolute_name: String, + pub parameters: Vec, + pub identities: Vec, + pub expression: Option, +} + +impl From for Analyzed { + fn from( + PILContext { + constants, + definitions, + public_declarations, + identities, + source_order, + .. + }: PILContext, + ) -> Self { + Self { + constants, + definitions, + public_declarations, + identities, + source_order, + } + } +} + +impl PILContext { + pub fn new() -> PILContext { + PILContext { + namespace: "Global".to_string(), + ..Default::default() + } + } + + pub fn process_file(&mut self, path: &Path) { + let path = path + .canonicalize() + .unwrap_or_else(|e| panic!("File {path:?} not found: {e}")); + if !self.included_files.insert(path.clone()) { + return; + } + let contents = fs::read_to_string(path.clone()).unwrap(); + self.process_file_contents(&path, &contents); + } + + pub fn process_file_contents(&mut self, path: &Path, contents: &str) { + let old_current_file = std::mem::take(&mut self.current_file); + let old_line_starts = std::mem::take(&mut self.line_starts); + + // TOOD make this work for other line endings + self.line_starts = line_utils::compute_line_starts(contents); + self.current_file = path.to_path_buf(); + let pil_file = + parser::parse(Some(path.to_str().unwrap()), contents).unwrap_or_else(|err| { + eprintln!("Error parsing .pil file:"); + err.output_to_stderr(); + panic!(); + }); + + for statement in &pil_file.0 { + use ast::Statement; + match statement { + Statement::Include(_, include) => self.handle_include(include), + Statement::Namespace(_, name, degree) => self.handle_namespace(name, degree), + Statement::PolynomialDefinition(start, name, value) => { + self.handle_polynomial_definition( + self.to_source_ref(*start), + name, + &None, + PolynomialType::Intermediate, + None, + Some(value), + ); + } + Statement::PublicDeclaration(start, name, polynomial, index) => self + .handle_public_declaration(self.to_source_ref(*start), name, polynomial, index), + Statement::PolynomialConstantDeclaration(start, polynomials) => self + .handle_polynomial_declarations( + self.to_source_ref(*start), + polynomials, + PolynomialType::Constant, + ), + Statement::PolynomialConstantDefinition(start, name, parameters, value) => { + self.handle_polynomial_definition( + self.to_source_ref(*start), + name, + &None, + PolynomialType::Constant, + Some(parameters), + Some(value), + ); + } + Statement::PolynomialCommitDeclaration(start, polynomials) => self + .handle_polynomial_declarations( + self.to_source_ref(*start), + polynomials, + PolynomialType::Committed, + ), + Statement::ConstantDefinition(_, name, value) => { + self.handle_constant_definition(name, value) + } + Statement::MacroDefinition(start, name, params, statments, expression) => self + .handle_macro_definition( + self.to_source_ref(*start), + name, + params, + statments, + expression, + ), + _ => { + self.handle_identity_statement(statement); + } + } + } + + self.current_file = old_current_file; + self.line_starts = old_line_starts; + } + + fn to_source_ref(&self, start: usize) -> SourceRef { + let file = self.current_file.file_name().unwrap().to_str().unwrap(); + SourceRef { + line: line_utils::offset_to_line(start, &self.line_starts), + file: file.to_string(), + } + } + + fn handle_identity_statement(&mut self, statement: &ast::Statement) { + if let ast::Statement::FunctionCall(_start, name, arguments) = statement { + if !self.macros.contains_key(name) { + panic!( + "Macro {name} not found - only macros allowed at this point, no fixed columns." + ); + } + // TODO check that it does not contain local variable references. + // But we also need to do some other well-formedness checks. + if self.process_macro_call(name, arguments).is_some() { + panic!("Invoked a macro in statement context with non-empty expression."); + } + return; + } + + let (start, kind, left, right) = match statement { + ast::Statement::PolynomialIdentity(start, expression) => ( + start, + IdentityKind::Polynomial, + SelectedExpressions { + selector: Some(self.process_expression(expression)), + expressions: vec![], + }, + SelectedExpressions::default(), + ), + ast::Statement::PlookupIdentity(start, key, haystack) => ( + start, + IdentityKind::Plookup, + self.process_selected_expression(key), + self.process_selected_expression(haystack), + ), + ast::Statement::PermutationIdentity(start, left, right) => ( + start, + IdentityKind::Permutation, + self.process_selected_expression(left), + self.process_selected_expression(right), + ), + ast::Statement::ConnectIdentity(start, left, right) => ( + start, + IdentityKind::Connect, + SelectedExpressions { + selector: None, + expressions: self.process_expressions(left), + }, + SelectedExpressions { + selector: None, + expressions: self.process_expressions(right), + }, + ), + // TODO at some point, these should all be caught by the type checker. + _ => { + panic!("Only identities allowed at this point.") + } + }; + let id = self.dispense_id(kind); + let identity = Identity { + id, + kind, + source: self.to_source_ref(*start), + left, + right, + }; + let id = self.identities.len(); + self.identities.push(identity); + self.source_order.push(StatementIdentifier::Identity(id)); + } + + fn handle_include(&mut self, path: &str) { + let mut dir = self.current_file.parent().unwrap().to_owned(); + dir.push(path); + self.process_file(&dir); + } + + fn handle_namespace(&mut self, name: &str, degree: &ast::Expression) { + self.polynomial_degree = self.evaluate_expression(degree).unwrap(); + self.namespace = name.to_owned(); + } + + fn handle_polynomial_declarations( + &mut self, + source: SourceRef, + polynomials: &[ast::PolynomialName], + polynomial_type: PolynomialType, + ) { + for ast::PolynomialName { name, array_size } in polynomials { + self.handle_polynomial_definition( + source.clone(), + name, + array_size, + polynomial_type, + None, + None, + ); + } + } + + fn handle_polynomial_definition( + &mut self, + source: SourceRef, + name: &String, + array_size: &Option, + polynomial_type: PolynomialType, + parameters: Option<&[String]>, + value: Option<&ast::Expression>, + ) -> u64 { + if parameters.is_some() { + assert!(array_size.is_none()); + assert!(polynomial_type == PolynomialType::Constant); + } + let length = array_size + .as_ref() + .map(|l| self.evaluate_expression(l).unwrap()); + let counter = match polynomial_type { + PolynomialType::Committed => &mut self.commit_poly_counter, + PolynomialType::Constant => &mut self.constant_poly_counter, + PolynomialType::Intermediate => &mut self.intermediate_poly_counter, + }; + let id = *counter; + *counter += length.unwrap_or(1) as u64; + let poly = Polynomial { + id, + source, + absolute_name: self.namespaced(name), + degree: self.polynomial_degree, + poly_type: polynomial_type, + length, + }; + let name = poly.absolute_name.clone(); + assert!(self.local_variables.is_empty()); + self.local_variables = parameters + .map(|p| { + p.iter() + .enumerate() + .map(|(i, p)| (p.clone(), i as u64)) + .collect() + }) + .unwrap_or_default(); + let value = value.map(|e| self.process_expression(e)); + self.local_variables.clear(); + let is_new = self + .definitions + .insert(name.clone(), (poly, value)) + .is_none(); + assert!(is_new); + self.source_order + .push(StatementIdentifier::Definition(name)); + id + } + + fn handle_public_declaration( + &mut self, + source: SourceRef, + name: &str, + poly: &ast::PolynomialReference, + index: &ast::Expression, + ) { + let id = self.public_declarations.len() as u64; + self.public_declarations.insert( + name.to_string(), + PublicDeclaration { + id, + source, + name: name.to_string(), + polynomial: self.process_polynomial_reference(poly), + index: self.evaluate_expression(index).unwrap(), + }, + ); + self.source_order + .push(StatementIdentifier::PublicDeclaration(name.to_string())); + } + + fn handle_constant_definition(&mut self, name: &str, value: &ast::Expression) { + // TODO does the order matter here? + let is_new = self + .constants + .insert(name.to_string(), self.evaluate_expression(value).unwrap()) + .is_none(); + assert!(is_new, "Constant {name} was defined twice."); + } + + fn dispense_id(&mut self, kind: IdentityKind) -> u64 { + let cnt = self.identity_counter.entry(kind).or_default(); + let id = *cnt; + *cnt += 1; + id + } + + fn handle_macro_definition( + &mut self, + source: SourceRef, + name: &String, + params: &[String], + statements: &[ast::Statement], + expression: &Option, + ) { + let is_new = self + .macros + .insert( + name.clone(), + MacroDefinition { + source, + absolute_name: self.namespaced(name), + parameters: params.to_vec(), + identities: statements.to_vec(), + expression: expression.clone(), + }, + ) + .is_none(); + assert!(is_new); + } + + fn namespaced(&self, name: &String) -> String { + self.namespaced_ref(&None, name) + } + + fn namespaced_ref(&self, namespace: &Option, name: &String) -> String { + format!("{}.{name}", namespace.as_ref().unwrap_or(&self.namespace)) + } + + fn process_selected_expression( + &mut self, + expr: &ast::SelectedExpressions, + ) -> SelectedExpressions { + SelectedExpressions { + selector: expr.selector.as_ref().map(|e| self.process_expression(e)), + expressions: self.process_expressions(&expr.expressions), + } + } + + fn process_expressions(&mut self, exprs: &[ast::Expression]) -> Vec { + exprs.iter().map(|e| self.process_expression(e)).collect() + } + + fn process_expression(&mut self, expr: &ast::Expression) -> Expression { + match expr { + ast::Expression::Constant(name) => Expression::Constant(name.clone()), + ast::Expression::PolynomialReference(poly) => { + if poly.namespace.is_none() && self.local_variables.contains_key(&poly.name) { + let id = self.local_variables[&poly.name]; + // TODO to make this work inside macros, "next" and "index" need to be + // their own ast nodes / operators. + assert!(!poly.next); + assert!(poly.index.is_none()); + if let Some(arguments) = &self.macro_arguments { + arguments[id as usize].clone() + } else { + Expression::LocalVariableReference(id) + } + } else { + Expression::PolynomialReference(self.process_polynomial_reference(poly)) + } + } + ast::Expression::PublicReference(name) => Expression::PublicReference(name.clone()), + ast::Expression::Number(n) => Expression::Number(*n), + ast::Expression::BinaryOperation(left, op, right) => { + if let Some(value) = self.evaluate_binary_operation(left, op, right) { + Expression::Number(value) + } else { + Expression::BinaryOperation( + Box::new(self.process_expression(left)), + *op, + Box::new(self.process_expression(right)), + ) + } + } + ast::Expression::UnaryOperation(op, value) => { + if let Some(value) = self.evaluate_unary_operation(op, value) { + Expression::Number(value) + } else { + Expression::UnaryOperation(*op, Box::new(self.process_expression(value))) + } + } + ast::Expression::FunctionCall(name, arguments) if self.macros.contains_key(name) => { + self.process_macro_call(name, arguments) + .expect("Invoked a macro in expression context with empty expression.") + } + ast::Expression::FunctionCall(name, arguments) => { + Expression::FunctionCall(self.namespaced(name), self.process_expressions(arguments)) + } + ast::Expression::FreeInput(_) => panic!(), + } + } + + fn process_macro_call( + &mut self, + name: &String, + arguments: &[ast::Expression], + ) -> Option { + let arguments = Some(self.process_expressions(arguments)); + let old_arguments = std::mem::replace(&mut self.macro_arguments, arguments); + + let old_locals = std::mem::take(&mut self.local_variables); + + let mac = &self + .macros + .get(name) + .unwrap_or_else(|| panic!("Macro {name} not found.")); + self.local_variables = mac + .parameters + .iter() + .enumerate() + .map(|(i, n)| (n.clone(), i as u64)) + .collect(); + // TODO avoid clones + let expression = mac.expression.clone(); + let identities = mac.identities.clone(); + for identity in &identities { + self.handle_identity_statement(identity); + } + let result = expression.map(|expr| self.process_expression(&expr)); + self.macro_arguments = old_arguments; + self.local_variables = old_locals; + result + } + + fn process_polynomial_reference(&self, poly: &ast::PolynomialReference) -> PolynomialReference { + let index = poly + .index + .as_ref() + .map(|i| self.evaluate_expression(i).unwrap() as u64); + PolynomialReference { + name: self.namespaced_ref(&poly.namespace, &poly.name), + index, + next: poly.next, + } + } + + fn evaluate_expression(&self, expr: &ast::Expression) -> Option { + match expr { + ast::Expression::Constant(name) => Some( + *self + .constants + .get(name) + .unwrap_or_else(|| panic!("Constant {name} not found.")), + ), + ast::Expression::PolynomialReference(_) => None, + ast::Expression::PublicReference(_) => None, + ast::Expression::Number(n) => Some(*n), + ast::Expression::BinaryOperation(left, op, right) => { + self.evaluate_binary_operation(left, op, right) + } + ast::Expression::UnaryOperation(op, value) => self.evaluate_unary_operation(op, value), + ast::Expression::FunctionCall(_, _) => None, + ast::Expression::FreeInput(_) => panic!(), + } + } + + fn evaluate_binary_operation( + &self, + left: &ast::Expression, + op: &BinaryOperator, + right: &ast::Expression, + ) -> Option { + // TODO handle owerflow and maybe use bigint instead. + if let (Some(left), Some(right)) = ( + self.evaluate_expression(left), + self.evaluate_expression(right), + ) { + Some(match op { + BinaryOperator::Add => left + right, + BinaryOperator::Sub => left - right, + BinaryOperator::Mul => left * right, + BinaryOperator::Div => left / right, + BinaryOperator::Pow => { + assert!(right <= u32::MAX.into()); + left.pow(right as u32) + } + BinaryOperator::Mod => left % right, + BinaryOperator::BinaryAnd => left & right, + BinaryOperator::BinaryOr => left | right, + BinaryOperator::ShiftLeft => left << right, + BinaryOperator::ShiftRight => left >> right, + }) + } else { + None + } + } + + fn evaluate_unary_operation( + &self, + op: &UnaryOperator, + value: &ast::Expression, + ) -> Option { + // TODO handle owerflow and maybe use bigint instead. + self.evaluate_expression(value).map(|v| match op { + UnaryOperator::Plus => v, + UnaryOperator::Minus => -v, + }) + } +} diff --git a/src/lib.rs b/src/lib.rs index 4f766f607..f6ffa97d6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,4 +3,5 @@ pub mod commit_evaluator; pub mod compiler; pub mod constant_evaluator; pub mod json_exporter; +pub mod line_utils; pub mod parser; diff --git a/src/line_utils.rs b/src/line_utils.rs new file mode 100644 index 000000000..ac8de5609 --- /dev/null +++ b/src/line_utils.rs @@ -0,0 +1,37 @@ +pub fn compute_line_starts(source: &str) -> Vec { + std::iter::once(0) + .chain(source.match_indices('\n').map(|(i, _)| i + 1)) + .collect::>() +} + +pub fn offset_to_line(offset: usize, line_starts: &[usize]) -> usize { + match line_starts.binary_search(&offset) { + Ok(line) => line + 1, + Err(next_line) => next_line, + } +} + +#[cfg(test)] +mod test { + use super::{compute_line_starts, offset_to_line}; + + #[test] + pub fn line_calc() { + let input = "abc\nde"; + let breaks = compute_line_starts(input); + let lines = (0..input.len()) + .map(|o| offset_to_line(o, &breaks)) + .collect::>(); + assert_eq!(lines, [1, 1, 1, 1, 2, 2]); + } + + #[test] + pub fn line_calc_empty_start() { + let input = "\nab\n\nc\nde\n"; + let breaks = compute_line_starts(input); + let lines = (0..input.len()) + .map(|o| offset_to_line(o, &breaks)) + .collect::>(); + assert_eq!(lines, [1, 2, 2, 2, 3, 4, 4, 5, 5, 5]); + } +}