add support for refactored array type in compiler

This commit is contained in:
collin 2020-11-06 16:24:38 -08:00
parent 1daf6c9831
commit d7ff808660
10 changed files with 183 additions and 46 deletions

View File

@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>. // along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
use crate::PositiveNumber; use crate::{PositiveNumber, Span};
use leo_grammar::types::ArrayDimensions as GrammarArrayDimensions; use leo_grammar::types::ArrayDimensions as GrammarArrayDimensions;
use leo_input::types::ArrayDimensions as InputArrayDimensions; use leo_input::types::ArrayDimensions as InputArrayDimensions;
@ -23,16 +23,71 @@ use std::fmt;
/// A vector of positive numbers that represent array dimensions. /// A vector of positive numbers that represent array dimensions.
/// Can be used in an array [`Type`] or an array initializer [`Expression`]. /// 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<PositiveNumber>); pub struct ArrayDimensions(pub Vec<PositiveNumber>);
impl ArrayDimensions { 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. /// Returns the array dimensions as strings.
/// ///
pub fn to_strings(&self) -> Vec<String> { pub fn to_strings(&self) -> Vec<String> {
self.0.iter().map(|number| number.to_string()).collect() 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<PositiveNumber> {
// 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. /// Create a new [`ArrayDimensions`] from a [`GrammarArrayDimensions`] in a Leo program file.

View File

@ -109,10 +109,10 @@ impl ExpressionError {
Self::new_from_span(message, span) 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); 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 { pub fn invalid_length(expected: usize, actual: usize, span: Span) -> Self {
@ -157,6 +157,12 @@ impl ExpressionError {
Self::new_from_span(message, span) 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 { pub fn undefined_circuit(actual: String, span: Span) -> Self {
let message = format!( let message = format!(
"circuit `{}` must be declared before it is used in an expression", "circuit `{}` must be declared before it is used in an expression",

View File

@ -22,7 +22,7 @@ use crate::{
value::ConstrainedValue, value::ConstrainedValue,
GroupType, GroupType,
}; };
use leo_ast::{Expression, Span, SpreadOrExpression, Type}; use leo_ast::{ArrayDimensions, Expression, PositiveNumber, Span, SpreadOrExpression, Type};
use snarkos_models::{ use snarkos_models::{
curves::{Field, PrimeField}, curves::{Field, PrimeField},
@ -41,20 +41,28 @@ impl<F: Field + PrimeField, G: GroupType<F>> ConstrainedProgram<F, G> {
span: Span, span: Span,
) -> Result<ConstrainedValue<F, G>, ExpressionError> { ) -> Result<ConstrainedValue<F, G>, ExpressionError> {
// Check explicit array type dimension if given // Check explicit array type dimension if given
let mut expected_dimensions = vec![]; let mut expected_dimension = None;
if let Some(type_) = expected_type { if let Some(type_) = expected_type {
match type_ { match type_ {
Type::Array(ref type_, ref dimensions) => { Type::Array(type_, mut dimensions) => {
let number = match dimensions.first() { // Remove the first dimension of the array.
Some(number) => *number, 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)), None => return Err(ExpressionError::unexpected_array(type_.to_string(), span)),
}; };
expected_dimensions.push(number); // Update the expected dimension to the first dimension.
expected_type = Some(type_.outer_dimension(dimensions)); 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_ => { ref type_ => {
// Return an error if the expected type is not an array.
return Err(ExpressionError::unexpected_array(type_.to_string(), span)); return Err(ExpressionError::unexpected_array(type_.to_string(), span));
} }
} }
@ -88,15 +96,44 @@ impl<F: Field + PrimeField, G: GroupType<F>> ConstrainedProgram<F, G> {
} }
} }
// Check expected_dimensions if given // Check expected_dimension if given.
if !expected_dimensions.is_empty() && expected_dimensions[expected_dimensions.len() - 1] != result.len() { if let Some(dimension) = expected_dimension {
return Err(ExpressionError::invalid_length( // Return an error if the expected dimension != the actual dimension.
expected_dimensions[expected_dimensions.len() - 1], if dimension != result.len() {
result.len(), return Err(ExpressionError::invalid_length(dimension, result.len(), span));
span, }
));
} }
Ok(ConstrainedValue::Array(result)) Ok(ConstrainedValue::Array(result))
} }
} }
///
/// Returns the index as a usize.
///
pub fn parse_index(number: &PositiveNumber, span: &Span) -> Result<usize, ExpressionError> {
number
.value
.parse::<usize>()
.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)
}
}

View File

@ -36,7 +36,7 @@ impl<F: Field + PrimeField, G: GroupType<F>> ConstrainedProgram<F, G> {
let expected_type = Some(Type::IntegerType(IntegerType::U32)); let expected_type = Some(Type::IntegerType(IntegerType::U32));
match self.enforce_operand(cs, file_scope, function_scope, expected_type, index, &span)? { match self.enforce_operand(cs, file_scope, function_scope, expected_type, index, &span)? {
ConstrainedValue::Integer(number) => Ok(number.to_usize(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)),
} }
} }
} }

View File

@ -260,9 +260,15 @@ impl<F: Field + PrimeField, G: GroupType<F>> ConstrainedProgram<F, G> {
Expression::Tuple(tuple, span) => { Expression::Tuple(tuple, span) => {
self.enforce_tuple(cs, file_scope, function_scope, expected_type, tuple, span) self.enforce_tuple(cs, file_scope, function_scope, expected_type, tuple, span)
} }
Expression::TupleAccess(tuple, index, span) => { Expression::TupleAccess(tuple_w_index, span) => self.enforce_tuple_access(
self.enforce_tuple_access(cs, file_scope, function_scope, expected_type, *tuple, index, &span) cs,
} file_scope,
function_scope,
expected_type,
tuple_w_index.0,
tuple_w_index.1,
&span,
),
// Circuits // Circuits
Expression::Circuit(circuit_name, members, span) => { Expression::Circuit(circuit_name, members, span) => {

View File

@ -16,8 +16,8 @@
//! Enforces array access in a compiled Leo program. //! Enforces array access in a compiled Leo program.
use crate::{errors::ExpressionError, program::ConstrainedProgram, value::ConstrainedValue, GroupType}; use crate::{errors::ExpressionError, parse_index, program::ConstrainedProgram, value::ConstrainedValue, GroupType};
use leo_ast::{Expression, Span, Type}; use leo_ast::{Expression, PositiveNumber, Span, Type};
use snarkos_models::{ use snarkos_models::{
curves::{Field, PrimeField}, curves::{Field, PrimeField},
@ -33,18 +33,23 @@ impl<F: Field + PrimeField, G: GroupType<F>> ConstrainedProgram<F, G> {
function_scope: &str, function_scope: &str,
expected_type: Option<Type>, expected_type: Option<Type>,
tuple: Expression, tuple: Expression,
index: usize, index: PositiveNumber,
span: &Span, span: &Span,
) -> Result<ConstrainedValue<F, G>, ExpressionError> { ) -> Result<ConstrainedValue<F, G>, ExpressionError> {
// Get the tuple values.
let tuple = match self.enforce_operand(cs, file_scope, function_scope, expected_type, tuple, &span)? { let tuple = match self.enforce_operand(cs, file_scope, function_scope, expected_type, tuple, &span)? {
ConstrainedValue::Tuple(tuple) => tuple, ConstrainedValue::Tuple(tuple) => tuple,
value => return Err(ExpressionError::undefined_array(value.to_string(), span.to_owned())), value => return Err(ExpressionError::undefined_array(value.to_string(), span.to_owned())),
}; };
if index > tuple.len() - 1 { // Parse the tuple index.
return Err(ExpressionError::index_out_of_bounds(index, span.to_owned())); 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())
} }
} }

View File

@ -18,13 +18,16 @@
use crate::{ use crate::{
errors::FunctionError, errors::FunctionError,
inner_array_type,
parse_index,
program::{new_scope, ConstrainedProgram}, program::{new_scope, ConstrainedProgram},
value::ConstrainedValue, value::ConstrainedValue,
GroupType, GroupType,
}; };
use leo_ast::{InputValue, Span, Type}; use leo_ast::{ArrayDimensions, InputValue, Span, Type};
use crate::errors::ExpressionError;
use snarkos_models::{ use snarkos_models::{
curves::{Field, PrimeField}, curves::{Field, PrimeField},
gadgets::r1cs::ConstraintSystem, gadgets::r1cs::ConstraintSystem,
@ -36,11 +39,27 @@ impl<F: Field + PrimeField, G: GroupType<F>> ConstrainedProgram<F, G> {
cs: &mut CS, cs: &mut CS,
name: &str, name: &str,
array_type: Type, array_type: Type,
array_dimensions: Vec<usize>, mut array_dimensions: ArrayDimensions,
input_value: Option<InputValue>, input_value: Option<InputValue>,
span: &Span, span: &Span,
) -> Result<ConstrainedValue<F, G>, FunctionError> { ) -> Result<ConstrainedValue<F, G>, 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![]; let mut array_value = vec![];
match input_value { match input_value {
@ -48,11 +67,10 @@ impl<F: Field + PrimeField, G: GroupType<F>> ConstrainedProgram<F, G> {
// Allocate each value in the current row // Allocate each value in the current row
for (i, value) in arr.into_iter().enumerate() { for (i, value) in arr.into_iter().enumerate() {
let value_name = new_scope(&name, &i.to_string()); 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( array_value.push(self.allocate_main_function_input(
cs, cs,
value_type, inner_array_type.clone(),
&value_name, &value_name,
Some(value), Some(value),
span, span,
@ -63,9 +81,14 @@ impl<F: Field + PrimeField, G: GroupType<F>> ConstrainedProgram<F, G> {
// Allocate all row values as none // Allocate all row values as none
for i in 0..expected_length { for i in 0..expected_length {
let value_name = new_scope(&name, &i.to_string()); 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,
)?);
} }
} }
_ => { _ => {

View File

@ -83,8 +83,8 @@ impl<F: Field + PrimeField, G: GroupType<F>> ConstrainedProgram<F, G> {
new_value, new_value,
span, span,
), ),
AssigneeAccess::Tuple(index) => { AssigneeAccess::Tuple(index, span) => {
self.assign_tuple(cs, indicator, &variable_name, index, new_value, span) self.assign_tuple(cs, indicator, &variable_name, index, new_value, &span)
} }
AssigneeAccess::Member(identifier) => { AssigneeAccess::Member(identifier) => {
// Mutate a circuit variable using the self keyword. // Mutate a circuit variable using the self keyword.

View File

@ -16,8 +16,8 @@
//! Enforces a tuple assignment statement in a compiled Leo program. //! Enforces a tuple assignment statement in a compiled Leo program.
use crate::{errors::StatementError, program::ConstrainedProgram, value::ConstrainedValue, GroupType}; use crate::{errors::StatementError, parse_index, program::ConstrainedProgram, value::ConstrainedValue, GroupType};
use leo_ast::Span; use leo_ast::{PositiveNumber, Span};
use snarkos_models::{ use snarkos_models::{
curves::{Field, PrimeField}, curves::{Field, PrimeField},
@ -33,28 +33,32 @@ impl<F: Field + PrimeField, G: GroupType<F>> ConstrainedProgram<F, G> {
cs: &mut CS, cs: &mut CS,
indicator: Option<Boolean>, indicator: Option<Boolean>,
name: &str, name: &str,
index: usize, index: PositiveNumber,
mut new_value: ConstrainedValue<F, G>, mut new_value: ConstrainedValue<F, G>,
span: &Span, span: &Span,
) -> Result<(), StatementError> { ) -> Result<(), StatementError> {
// Get the indicator value.
let condition = indicator.unwrap_or(Boolean::Constant(true)); 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 // Modify the single value of the tuple in place
match self.get_mutable_assignee(name, &span)? { match self.get_mutable_assignee(name, &span)? {
ConstrainedValue::Tuple(old) => { 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( let selected_value = ConstrainedValue::conditionally_select(
cs.ns(|| format!("select {} {}:{}", new_value, span.line, span.start)), cs.ns(|| format!("select {} {}:{}", new_value, span.line, span.start)),
&condition, &condition,
&new_value, &new_value,
&old[index], &old[index_usize],
) )
.map_err(|_| { .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())), _ => return Err(StatementError::tuple_assign_index(span.to_owned())),
} }

View File

@ -26,7 +26,7 @@ use crate::{
GroupType, GroupType,
Integer, 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 leo_core::Value;
use snarkos_errors::gadgets::SynthesisError; use snarkos_errors::gadgets::SynthesisError;
@ -114,7 +114,8 @@ impl<F: Field + PrimeField, G: GroupType<F>> ConstrainedValue<F, G> {
// Data type wrappers // Data type wrappers
ConstrainedValue::Array(array) => { ConstrainedValue::Array(array) => {
let array_type = array[0].to_type(span)?; 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 // Nested array type
if let Type::Array(inner_type, inner_dimensions) = &array_type { if let Type::Array(inner_type, inner_dimensions) = &array_type {