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
// 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_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<PositiveNumber>);
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<String> {
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.

View File

@ -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",

View File

@ -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<F: Field + PrimeField, G: GroupType<F>> ConstrainedProgram<F, G> {
span: Span,
) -> Result<ConstrainedValue<F, G>, 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<F: Field + PrimeField, G: GroupType<F>> ConstrainedProgram<F, G> {
}
}
// 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<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));
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)),
}
}
}

View File

@ -260,9 +260,15 @@ impl<F: Field + PrimeField, G: GroupType<F>> ConstrainedProgram<F, G> {
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) => {

View File

@ -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<F: Field + PrimeField, G: GroupType<F>> ConstrainedProgram<F, G> {
function_scope: &str,
expected_type: Option<Type>,
tuple: Expression,
index: usize,
index: PositiveNumber,
span: &Span,
) -> Result<ConstrainedValue<F, G>, 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())
}
}

View File

@ -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<F: Field + PrimeField, G: GroupType<F>> ConstrainedProgram<F, G> {
cs: &mut CS,
name: &str,
array_type: Type,
array_dimensions: Vec<usize>,
mut array_dimensions: ArrayDimensions,
input_value: Option<InputValue>,
span: &Span,
) -> 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![];
match input_value {
@ -48,11 +67,10 @@ impl<F: Field + PrimeField, G: GroupType<F>> ConstrainedProgram<F, G> {
// 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<F: Field + PrimeField, G: GroupType<F>> ConstrainedProgram<F, G> {
// 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,
)?);
}
}
_ => {

View File

@ -83,8 +83,8 @@ impl<F: Field + PrimeField, G: GroupType<F>> ConstrainedProgram<F, G> {
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.

View File

@ -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<F: Field + PrimeField, G: GroupType<F>> ConstrainedProgram<F, G> {
cs: &mut CS,
indicator: Option<Boolean>,
name: &str,
index: usize,
index: PositiveNumber,
mut new_value: ConstrainedValue<F, G>,
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())),
}

View File

@ -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<F: Field + PrimeField, G: GroupType<F>> ConstrainedValue<F, G> {
// 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 {