From d7ff80866065bc3de9110b3bc26145dd6c616317 Mon Sep 17 00:00:00 2001 From: collin Date: Fri, 6 Nov 2020 16:24:38 -0800 Subject: [PATCH] add support for refactored array type in compiler --- ast/src/common/array_dimensions.rs | 59 +++++++++++++++++++++- compiler/src/errors/expression.rs | 10 +++- compiler/src/expression/array/array.rs | 65 +++++++++++++++++++------ compiler/src/expression/array/index.rs | 2 +- compiler/src/expression/expression.rs | 12 +++-- compiler/src/expression/tuple/access.rs | 17 ++++--- compiler/src/function/input/array.rs | 37 +++++++++++--- compiler/src/statement/assign/assign.rs | 4 +- compiler/src/statement/assign/tuple.rs | 18 ++++--- compiler/src/value/value.rs | 5 +- 10 files changed, 183 insertions(+), 46 deletions(-) diff --git a/ast/src/common/array_dimensions.rs b/ast/src/common/array_dimensions.rs index ced54792c1..baa503b1b5 100644 --- a/ast/src/common/array_dimensions.rs +++ b/ast/src/common/array_dimensions.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with the Leo library. If not, see . -use crate::PositiveNumber; +use crate::{PositiveNumber, Span}; use leo_grammar::types::ArrayDimensions as GrammarArrayDimensions; use leo_input::types::ArrayDimensions as InputArrayDimensions; @@ -23,16 +23,71 @@ use std::fmt; /// A vector of positive numbers that represent array dimensions. /// Can be used in an array [`Type`] or an array initializer [`Expression`]. -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct ArrayDimensions(pub Vec); impl ArrayDimensions { + /// + /// Creates a new `PositiveNumber` from the given `usize` and `Span`. + /// Appends the new `PositiveNumber` to the array dimensions. + /// + pub fn push_usize(&mut self, number: usize, span: Span) { + let positive_number = PositiveNumber { + value: number.to_string(), + span, + }; + + self.0.push(positive_number) + } + + /// + /// Appends a vector of array dimensions to the self array dimensions. + /// + pub fn append(&mut self, other: &mut ArrayDimensions) { + self.0.append(&mut other.0) + } + /// /// Returns the array dimensions as strings. /// pub fn to_strings(&self) -> Vec { self.0.iter().map(|number| number.to_string()).collect() } + + /// + /// Returns `true` if the all array dimensions have been removed. + /// + /// This method is called after repeated calls to `remove_first`. + /// + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + /// + /// Returns the first dimension of the array. + /// + pub fn first(&self) -> Option<&PositiveNumber> { + self.0.first() + } + + /// + /// Attempts to remove the first dimension from the array. + /// + /// If the first dimension exists, then remove and return `Some(PositiveNumber)`. + /// If the first dimension does not exist, then return `None`. + /// + pub fn remove_first(&mut self) -> Option { + // If there are no dimensions in the array, then return None. + if self.0.get(0).is_none() { + return None; + } + + // Remove the first dimension. + let removed = self.0.remove(0); + + // Return the first dimension. + Some(removed) + } } /// Create a new [`ArrayDimensions`] from a [`GrammarArrayDimensions`] in a Leo program file. diff --git a/compiler/src/errors/expression.rs b/compiler/src/errors/expression.rs index bde8d04f4d..64648035b4 100644 --- a/compiler/src/errors/expression.rs +++ b/compiler/src/errors/expression.rs @@ -109,10 +109,10 @@ impl ExpressionError { Self::new_from_span(message, span) } - pub fn invalid_index(actual: String, span: Span) -> Self { + pub fn invalid_index(actual: String, span: &Span) -> Self { let message = format!("index must resolve to an integer, found `{}`", actual); - Self::new_from_span(message, span) + Self::new_from_span(message, span.to_owned()) } pub fn invalid_length(expected: usize, actual: usize, span: Span) -> Self { @@ -157,6 +157,12 @@ impl ExpressionError { Self::new_from_span(message, span) } + pub fn undefined_tuple(actual: String, span: Span) -> Self { + let message = format!("tuple `{}` must be declared before it is used in an expression", actual); + + Self::new_from_span(message, span) + } + pub fn undefined_circuit(actual: String, span: Span) -> Self { let message = format!( "circuit `{}` must be declared before it is used in an expression", diff --git a/compiler/src/expression/array/array.rs b/compiler/src/expression/array/array.rs index 7a0494fb9d..c87d6f2a40 100644 --- a/compiler/src/expression/array/array.rs +++ b/compiler/src/expression/array/array.rs @@ -22,7 +22,7 @@ use crate::{ value::ConstrainedValue, GroupType, }; -use leo_ast::{Expression, Span, SpreadOrExpression, Type}; +use leo_ast::{ArrayDimensions, Expression, PositiveNumber, Span, SpreadOrExpression, Type}; use snarkos_models::{ curves::{Field, PrimeField}, @@ -41,20 +41,28 @@ impl> ConstrainedProgram { span: Span, ) -> Result, ExpressionError> { // Check explicit array type dimension if given - let mut expected_dimensions = vec![]; + let mut expected_dimension = None; if let Some(type_) = expected_type { match type_ { - Type::Array(ref type_, ref dimensions) => { - let number = match dimensions.first() { - Some(number) => *number, + Type::Array(type_, mut dimensions) => { + // Remove the first dimension of the array. + let first = match dimensions.remove_first() { + Some(number) => { + // Parse the array dimension into a `usize`. + parse_index(&number, &span)? + } None => return Err(ExpressionError::unexpected_array(type_.to_string(), span)), }; - expected_dimensions.push(number); - expected_type = Some(type_.outer_dimension(dimensions)); + // Update the expected dimension to the first dimension. + expected_dimension = Some(first); + + // Update the expected type to a new array type with the first dimension removed. + expected_type = Some(inner_array_type(*type_, dimensions)); } ref type_ => { + // Return an error if the expected type is not an array. return Err(ExpressionError::unexpected_array(type_.to_string(), span)); } } @@ -88,15 +96,44 @@ impl> ConstrainedProgram { } } - // Check expected_dimensions if given - if !expected_dimensions.is_empty() && expected_dimensions[expected_dimensions.len() - 1] != result.len() { - return Err(ExpressionError::invalid_length( - expected_dimensions[expected_dimensions.len() - 1], - result.len(), - span, - )); + // Check expected_dimension if given. + if let Some(dimension) = expected_dimension { + // Return an error if the expected dimension != the actual dimension. + if dimension != result.len() { + return Err(ExpressionError::invalid_length(dimension, result.len(), span)); + } } Ok(ConstrainedValue::Array(result)) } } + +/// +/// Returns the index as a usize. +/// +pub fn parse_index(number: &PositiveNumber, span: &Span) -> Result { + number + .value + .parse::() + .map_err(|_| ExpressionError::invalid_index(number.value.to_owned(), span)) +} + +/// +/// Returns the type of the inner array given an array element and array dimensions. +/// +/// If the array has no dimensions, then an inner array does not exist. Simply return the given +/// element type. +/// +/// If the array has dimensions, then an inner array exists. Create a new type for the +/// inner array. The element type of the new array should be the same as the old array. The +/// dimensions of the new array should be the old array dimensions with the first dimension removed. +/// +pub fn inner_array_type(element_type: Type, dimensions: ArrayDimensions) -> Type { + if dimensions.is_empty() { + // The array has one dimension. + element_type + } else { + // The array has multiple dimensions. + Type::Array(Box::new(element_type), dimensions) + } +} diff --git a/compiler/src/expression/array/index.rs b/compiler/src/expression/array/index.rs index 52d2e863c8..02d361bae5 100644 --- a/compiler/src/expression/array/index.rs +++ b/compiler/src/expression/array/index.rs @@ -36,7 +36,7 @@ impl> ConstrainedProgram { let expected_type = Some(Type::IntegerType(IntegerType::U32)); match self.enforce_operand(cs, file_scope, function_scope, expected_type, index, &span)? { ConstrainedValue::Integer(number) => Ok(number.to_usize(span)?), - value => Err(ExpressionError::invalid_index(value.to_string(), span.to_owned())), + value => Err(ExpressionError::invalid_index(value.to_string(), span)), } } } diff --git a/compiler/src/expression/expression.rs b/compiler/src/expression/expression.rs index 9af7ab7c1c..5b842eeb95 100644 --- a/compiler/src/expression/expression.rs +++ b/compiler/src/expression/expression.rs @@ -260,9 +260,15 @@ impl> ConstrainedProgram { Expression::Tuple(tuple, span) => { self.enforce_tuple(cs, file_scope, function_scope, expected_type, tuple, span) } - Expression::TupleAccess(tuple, index, span) => { - self.enforce_tuple_access(cs, file_scope, function_scope, expected_type, *tuple, index, &span) - } + Expression::TupleAccess(tuple_w_index, span) => self.enforce_tuple_access( + cs, + file_scope, + function_scope, + expected_type, + tuple_w_index.0, + tuple_w_index.1, + &span, + ), // Circuits Expression::Circuit(circuit_name, members, span) => { diff --git a/compiler/src/expression/tuple/access.rs b/compiler/src/expression/tuple/access.rs index 33cc551c98..03cc03ebce 100644 --- a/compiler/src/expression/tuple/access.rs +++ b/compiler/src/expression/tuple/access.rs @@ -16,8 +16,8 @@ //! Enforces array access in a compiled Leo program. -use crate::{errors::ExpressionError, program::ConstrainedProgram, value::ConstrainedValue, GroupType}; -use leo_ast::{Expression, Span, Type}; +use crate::{errors::ExpressionError, parse_index, program::ConstrainedProgram, value::ConstrainedValue, GroupType}; +use leo_ast::{Expression, PositiveNumber, Span, Type}; use snarkos_models::{ curves::{Field, PrimeField}, @@ -33,18 +33,23 @@ impl> ConstrainedProgram { function_scope: &str, expected_type: Option, tuple: Expression, - index: usize, + index: PositiveNumber, span: &Span, ) -> Result, ExpressionError> { + // Get the tuple values. let tuple = match self.enforce_operand(cs, file_scope, function_scope, expected_type, tuple, &span)? { ConstrainedValue::Tuple(tuple) => tuple, value => return Err(ExpressionError::undefined_array(value.to_string(), span.to_owned())), }; - if index > tuple.len() - 1 { - return Err(ExpressionError::index_out_of_bounds(index, span.to_owned())); + // Parse the tuple index. + let index_usize = parse_index(&index, &span)?; + + // Check for out of bounds access. + if index_usize > tuple.len() - 1 { + return Err(ExpressionError::index_out_of_bounds(index_usize, span.to_owned())); } - Ok(tuple[index].to_owned()) + Ok(tuple[index_usize].to_owned()) } } diff --git a/compiler/src/function/input/array.rs b/compiler/src/function/input/array.rs index 8c3cf3a9e7..5158947d0c 100644 --- a/compiler/src/function/input/array.rs +++ b/compiler/src/function/input/array.rs @@ -18,13 +18,16 @@ use crate::{ errors::FunctionError, + inner_array_type, + parse_index, program::{new_scope, ConstrainedProgram}, value::ConstrainedValue, GroupType, }; -use leo_ast::{InputValue, Span, Type}; +use leo_ast::{ArrayDimensions, InputValue, Span, Type}; +use crate::errors::ExpressionError; use snarkos_models::{ curves::{Field, PrimeField}, gadgets::r1cs::ConstraintSystem, @@ -36,11 +39,27 @@ impl> ConstrainedProgram { cs: &mut CS, name: &str, array_type: Type, - array_dimensions: Vec, + mut array_dimensions: ArrayDimensions, input_value: Option, span: &Span, ) -> Result, FunctionError> { - let expected_length = array_dimensions[0]; + let expected_length = match array_dimensions.remove_first() { + Some(number) => { + // Parse the array dimension into a `usize`. + parse_index(&number, &span)? + } + None => { + return Err(FunctionError::ExpressionError(ExpressionError::unexpected_array( + array_type.to_string(), + span.to_owned(), + ))); + } + }; + + // Get the expected type for each array element. + let inner_array_type = inner_array_type(array_type, array_dimensions); + + // Build the array value using the expected types. let mut array_value = vec![]; match input_value { @@ -48,11 +67,10 @@ impl> ConstrainedProgram { // Allocate each value in the current row for (i, value) in arr.into_iter().enumerate() { let value_name = new_scope(&name, &i.to_string()); - let value_type = array_type.outer_dimension(&array_dimensions); array_value.push(self.allocate_main_function_input( cs, - value_type, + inner_array_type.clone(), &value_name, Some(value), span, @@ -63,9 +81,14 @@ impl> ConstrainedProgram { // Allocate all row values as none for i in 0..expected_length { let value_name = new_scope(&name, &i.to_string()); - let value_type = array_type.outer_dimension(&array_dimensions); - array_value.push(self.allocate_main_function_input(cs, value_type, &value_name, None, span)?); + array_value.push(self.allocate_main_function_input( + cs, + inner_array_type.clone(), + &value_name, + None, + span, + )?); } } _ => { diff --git a/compiler/src/statement/assign/assign.rs b/compiler/src/statement/assign/assign.rs index 2dad3ea1f8..c33bd43f79 100644 --- a/compiler/src/statement/assign/assign.rs +++ b/compiler/src/statement/assign/assign.rs @@ -83,8 +83,8 @@ impl> ConstrainedProgram { new_value, span, ), - AssigneeAccess::Tuple(index) => { - self.assign_tuple(cs, indicator, &variable_name, index, new_value, span) + AssigneeAccess::Tuple(index, span) => { + self.assign_tuple(cs, indicator, &variable_name, index, new_value, &span) } AssigneeAccess::Member(identifier) => { // Mutate a circuit variable using the self keyword. diff --git a/compiler/src/statement/assign/tuple.rs b/compiler/src/statement/assign/tuple.rs index 6fb7885255..5ecb0dd767 100644 --- a/compiler/src/statement/assign/tuple.rs +++ b/compiler/src/statement/assign/tuple.rs @@ -16,8 +16,8 @@ //! Enforces a tuple assignment statement in a compiled Leo program. -use crate::{errors::StatementError, program::ConstrainedProgram, value::ConstrainedValue, GroupType}; -use leo_ast::Span; +use crate::{errors::StatementError, parse_index, program::ConstrainedProgram, value::ConstrainedValue, GroupType}; +use leo_ast::{PositiveNumber, Span}; use snarkos_models::{ curves::{Field, PrimeField}, @@ -33,28 +33,32 @@ impl> ConstrainedProgram { cs: &mut CS, indicator: Option, name: &str, - index: usize, + index: PositiveNumber, mut new_value: ConstrainedValue, span: &Span, ) -> Result<(), StatementError> { + // Get the indicator value. let condition = indicator.unwrap_or(Boolean::Constant(true)); + // Parse the index. + let index_usize = parse_index(&index, &span)?; + // Modify the single value of the tuple in place match self.get_mutable_assignee(name, &span)? { ConstrainedValue::Tuple(old) => { - new_value.resolve_type(Some(old[index].to_type(&span)?), &span)?; + new_value.resolve_type(Some(old[index_usize].to_type(&span)?), &span)?; let selected_value = ConstrainedValue::conditionally_select( cs.ns(|| format!("select {} {}:{}", new_value, span.line, span.start)), &condition, &new_value, - &old[index], + &old[index_usize], ) .map_err(|_| { - StatementError::select_fail(new_value.to_string(), old[index].to_string(), span.to_owned()) + StatementError::select_fail(new_value.to_string(), old[index_usize].to_string(), span.to_owned()) })?; - old[index] = selected_value; + old[index_usize] = selected_value; } _ => return Err(StatementError::tuple_assign_index(span.to_owned())), } diff --git a/compiler/src/value/value.rs b/compiler/src/value/value.rs index b83cb79b71..0de94a7e65 100644 --- a/compiler/src/value/value.rs +++ b/compiler/src/value/value.rs @@ -26,7 +26,7 @@ use crate::{ GroupType, Integer, }; -use leo_ast::{Circuit, Function, GroupValue, Identifier, Span, Type}; +use leo_ast::{ArrayDimensions, Circuit, Function, GroupValue, Identifier, Span, Type}; use leo_core::Value; use snarkos_errors::gadgets::SynthesisError; @@ -114,7 +114,8 @@ impl> ConstrainedValue { // Data type wrappers ConstrainedValue::Array(array) => { let array_type = array[0].to_type(span)?; - let mut dimensions = vec![array.len()]; + let mut dimensions = ArrayDimensions::default(); + dimensions.push_usize(array.len(), span.to_owned()); // Nested array type if let Type::Array(inner_type, inner_dimensions) = &array_type {