mirror of
https://github.com/ProvableHQ/leo.git
synced 2025-01-06 10:37:19 +03:00
Merge remote-tracking branch 'origin/master' into feature/leo-path-cli
This commit is contained in:
commit
25551a7c13
@ -180,6 +180,14 @@ impl AsgConvertError {
|
|||||||
Self::new_from_span(format!("tuple index out of bounds: '{}'", index), span)
|
Self::new_from_span(format!("tuple index out of bounds: '{}'", index), span)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn array_index_out_of_bounds(index: usize, span: &Span) -> Self {
|
||||||
|
Self::new_from_span(format!("array index out of bounds: '{}'", index), span)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unknown_array_size(span: &Span) -> Self {
|
||||||
|
Self::new_from_span("array size cannot be inferred, add explicit types".to_string(), span)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn unexpected_call_argument_count(expected: usize, got: usize, span: &Span) -> Self {
|
pub fn unexpected_call_argument_count(expected: usize, got: usize, span: &Span) -> Self {
|
||||||
Self::new_from_span(
|
Self::new_from_span(
|
||||||
format!("function call expected {} arguments, got {}", expected, got),
|
format!("function call expected {} arguments, got {}", expected, got),
|
||||||
|
@ -89,8 +89,8 @@ impl<'a> FromAst<'a, leo_ast::ArrayAccessExpression> for ArrayAccessExpression<'
|
|||||||
&*value.array,
|
&*value.array,
|
||||||
Some(PartialType::Array(expected_type.map(Box::new), None)),
|
Some(PartialType::Array(expected_type.map(Box::new), None)),
|
||||||
)?;
|
)?;
|
||||||
match array.get_type() {
|
let array_len = match array.get_type() {
|
||||||
Some(Type::Array(..)) => (),
|
Some(Type::Array(_, len)) => len,
|
||||||
type_ => {
|
type_ => {
|
||||||
return Err(AsgConvertError::unexpected_type(
|
return Err(AsgConvertError::unexpected_type(
|
||||||
"array",
|
"array",
|
||||||
@ -98,7 +98,7 @@ impl<'a> FromAst<'a, leo_ast::ArrayAccessExpression> for ArrayAccessExpression<'
|
|||||||
&value.span,
|
&value.span,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
let index = <&Expression<'a>>::from_ast(
|
let index = <&Expression<'a>>::from_ast(
|
||||||
scope,
|
scope,
|
||||||
@ -106,10 +106,17 @@ impl<'a> FromAst<'a, leo_ast::ArrayAccessExpression> for ArrayAccessExpression<'
|
|||||||
Some(PartialType::Integer(None, Some(IntegerType::U32))),
|
Some(PartialType::Integer(None, Some(IntegerType::U32))),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if !index.is_consty() {
|
if let Some(index) = index
|
||||||
return Err(AsgConvertError::unexpected_nonconst(
|
.const_value()
|
||||||
&index.span().cloned().unwrap_or_default(),
|
.map(|x| x.int().map(|x| x.to_usize()).flatten())
|
||||||
));
|
.flatten()
|
||||||
|
{
|
||||||
|
if index >= array_len {
|
||||||
|
return Err(AsgConvertError::array_index_out_of_bounds(
|
||||||
|
index,
|
||||||
|
&array.span().cloned().unwrap_or_default(),
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(ArrayAccessExpression {
|
Ok(ArrayAccessExpression {
|
||||||
|
@ -26,6 +26,9 @@ pub struct ArrayRangeAccessExpression<'a> {
|
|||||||
pub array: Cell<&'a Expression<'a>>,
|
pub array: Cell<&'a Expression<'a>>,
|
||||||
pub left: Cell<Option<&'a Expression<'a>>>,
|
pub left: Cell<Option<&'a Expression<'a>>>,
|
||||||
pub right: Cell<Option<&'a Expression<'a>>>,
|
pub right: Cell<Option<&'a Expression<'a>>>,
|
||||||
|
// this is either const(right) - const(left) OR the length inferred by type checking
|
||||||
|
// special attention must be made to update this if semantic-altering changes are made to left or right.
|
||||||
|
pub length: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Node for ArrayRangeAccessExpression<'a> {
|
impl<'a> Node for ArrayRangeAccessExpression<'a> {
|
||||||
@ -55,25 +58,12 @@ impl<'a> ExpressionNode<'a> for ArrayRangeAccessExpression<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn get_type(&self) -> Option<Type<'a>> {
|
fn get_type(&self) -> Option<Type<'a>> {
|
||||||
let (element, array_len) = match self.array.get().get_type() {
|
let element = match self.array.get().get_type() {
|
||||||
Some(Type::Array(element, len)) => (element, len),
|
Some(Type::Array(element, _)) => element,
|
||||||
_ => return None,
|
_ => return None,
|
||||||
};
|
};
|
||||||
let const_left = match self.left.get().map(|x| x.const_value()) {
|
|
||||||
Some(Some(ConstValue::Int(x))) => x.to_usize()?,
|
|
||||||
None => 0,
|
|
||||||
_ => return None,
|
|
||||||
};
|
|
||||||
let const_right = match self.right.get().map(|x| x.const_value()) {
|
|
||||||
Some(Some(ConstValue::Int(x))) => x.to_usize()?,
|
|
||||||
None => array_len,
|
|
||||||
_ => return None,
|
|
||||||
};
|
|
||||||
if const_left > const_right || const_right > array_len {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(Type::Array(element, const_right - const_left))
|
Some(Type::Array(element, self.length))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_mut_ref(&self) -> bool {
|
fn is_mut_ref(&self) -> bool {
|
||||||
@ -113,9 +103,9 @@ impl<'a> FromAst<'a, leo_ast::ArrayRangeAccessExpression> for ArrayRangeAccessEx
|
|||||||
value: &leo_ast::ArrayRangeAccessExpression,
|
value: &leo_ast::ArrayRangeAccessExpression,
|
||||||
expected_type: Option<PartialType<'a>>,
|
expected_type: Option<PartialType<'a>>,
|
||||||
) -> Result<ArrayRangeAccessExpression<'a>, AsgConvertError> {
|
) -> Result<ArrayRangeAccessExpression<'a>, AsgConvertError> {
|
||||||
let expected_array = match expected_type {
|
let (expected_array, expected_len) = match expected_type.clone() {
|
||||||
Some(PartialType::Array(element, _len)) => Some(PartialType::Array(element, None)),
|
Some(PartialType::Array(element, len)) => (Some(PartialType::Array(element, None)), len),
|
||||||
None => None,
|
None => (None, None),
|
||||||
Some(x) => {
|
Some(x) => {
|
||||||
return Err(AsgConvertError::unexpected_type(
|
return Err(AsgConvertError::unexpected_type(
|
||||||
&x.to_string(),
|
&x.to_string(),
|
||||||
@ -126,8 +116,8 @@ impl<'a> FromAst<'a, leo_ast::ArrayRangeAccessExpression> for ArrayRangeAccessEx
|
|||||||
};
|
};
|
||||||
let array = <&Expression<'a>>::from_ast(scope, &*value.array, expected_array)?;
|
let array = <&Expression<'a>>::from_ast(scope, &*value.array, expected_array)?;
|
||||||
let array_type = array.get_type();
|
let array_type = array.get_type();
|
||||||
match array_type {
|
let (parent_element, parent_size) = match array_type {
|
||||||
Some(Type::Array(_, _)) => (),
|
Some(Type::Array(inner, size)) => (inner, size),
|
||||||
type_ => {
|
type_ => {
|
||||||
return Err(AsgConvertError::unexpected_type(
|
return Err(AsgConvertError::unexpected_type(
|
||||||
"array",
|
"array",
|
||||||
@ -135,7 +125,8 @@ impl<'a> FromAst<'a, leo_ast::ArrayRangeAccessExpression> for ArrayRangeAccessEx
|
|||||||
&value.span,
|
&value.span,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
let left = value
|
let left = value
|
||||||
.left
|
.left
|
||||||
.as_deref()
|
.as_deref()
|
||||||
@ -151,26 +142,72 @@ impl<'a> FromAst<'a, leo_ast::ArrayRangeAccessExpression> for ArrayRangeAccessEx
|
|||||||
})
|
})
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
|
|
||||||
if let Some(left) = left.as_ref() {
|
let const_left = match left.map(|x| x.const_value()) {
|
||||||
if !left.is_consty() {
|
Some(Some(ConstValue::Int(x))) => x.to_usize(),
|
||||||
return Err(AsgConvertError::unexpected_nonconst(
|
None => Some(0),
|
||||||
&left.span().cloned().unwrap_or_default(),
|
_ => None,
|
||||||
));
|
};
|
||||||
|
let const_right = match right.map(|x| x.const_value()) {
|
||||||
|
Some(Some(ConstValue::Int(value))) => {
|
||||||
|
let value = value.to_usize();
|
||||||
|
if let Some(value) = value {
|
||||||
|
if value > parent_size {
|
||||||
|
return Err(AsgConvertError::array_index_out_of_bounds(
|
||||||
|
value,
|
||||||
|
&right.unwrap().span().cloned().unwrap_or_default(),
|
||||||
|
));
|
||||||
|
} else if let Some(left) = const_left {
|
||||||
|
if left > value {
|
||||||
|
return Err(AsgConvertError::array_index_out_of_bounds(
|
||||||
|
value,
|
||||||
|
&right.unwrap().span().cloned().unwrap_or_default(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
value
|
||||||
}
|
}
|
||||||
}
|
None => Some(parent_size),
|
||||||
if let Some(right) = right.as_ref() {
|
_ => None,
|
||||||
if !right.is_consty() {
|
};
|
||||||
return Err(AsgConvertError::unexpected_nonconst(
|
|
||||||
&right.span().cloned().unwrap_or_default(),
|
let mut length = if let (Some(left), Some(right)) = (const_left, const_right) {
|
||||||
));
|
Some(right - left)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
if let Some(expected_len) = expected_len {
|
||||||
|
if let Some(length) = length {
|
||||||
|
if length != expected_len {
|
||||||
|
let concrete_type = Type::Array(parent_element, length);
|
||||||
|
return Err(AsgConvertError::unexpected_type(
|
||||||
|
&expected_type.as_ref().unwrap().to_string(),
|
||||||
|
Some(&concrete_type.to_string()),
|
||||||
|
&value.span,
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if let Some(value) = const_left {
|
||||||
|
if value + expected_len > parent_size {
|
||||||
|
return Err(AsgConvertError::array_index_out_of_bounds(
|
||||||
|
value,
|
||||||
|
&left.unwrap().span().cloned().unwrap_or_default(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
length = Some(expected_len);
|
||||||
}
|
}
|
||||||
|
if length.is_none() {
|
||||||
|
return Err(AsgConvertError::unknown_array_size(&value.span));
|
||||||
|
}
|
||||||
|
|
||||||
Ok(ArrayRangeAccessExpression {
|
Ok(ArrayRangeAccessExpression {
|
||||||
parent: Cell::new(None),
|
parent: Cell::new(None),
|
||||||
span: Some(value.span.clone()),
|
span: Some(value.span.clone()),
|
||||||
array: Cell::new(array),
|
array: Cell::new(array),
|
||||||
left: Cell::new(left),
|
left: Cell::new(left),
|
||||||
right: Cell::new(right),
|
right: Cell::new(right),
|
||||||
|
length: length.unwrap(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -74,6 +74,7 @@ pub trait ReconstructingReducerExpression<'a> {
|
|||||||
left: Cell::new(left),
|
left: Cell::new(left),
|
||||||
right: Cell::new(right),
|
right: Cell::new(right),
|
||||||
span: input.span,
|
span: input.span,
|
||||||
|
length: input.length,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,6 +67,18 @@ impl ExpressionError {
|
|||||||
Self::new_from_span(message, span)
|
Self::new_from_span(message, span)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn array_length_out_of_bounds(span: &Span) -> Self {
|
||||||
|
let message = "array length cannot be >= 2^32".to_string();
|
||||||
|
|
||||||
|
Self::new_from_span(message, span)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn array_index_out_of_legal_bounds(span: &Span) -> Self {
|
||||||
|
let message = "array index cannot be >= 2^32".to_string();
|
||||||
|
|
||||||
|
Self::new_from_span(message, span)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn conditional_boolean(actual: String, span: &Span) -> Self {
|
pub fn conditional_boolean(actual: String, span: &Span) -> Self {
|
||||||
let message = format!("if, else conditional must resolve to a boolean, found `{}`", actual);
|
let message = format!("if, else conditional must resolve to a boolean, found `{}`", actual);
|
||||||
|
|
||||||
@ -85,12 +97,24 @@ impl ExpressionError {
|
|||||||
Self::new_from_span(message, span)
|
Self::new_from_span(message, span)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn index_out_of_bounds(index: usize, span: &Span) -> Self {
|
pub fn tuple_index_out_of_bounds(index: usize, span: &Span) -> Self {
|
||||||
let message = format!("cannot access index {} of tuple out of bounds", index);
|
let message = format!("cannot access index {} of tuple out of bounds", index);
|
||||||
|
|
||||||
Self::new_from_span(message, span)
|
Self::new_from_span(message, span)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn array_index_out_of_bounds(index: usize, span: &Span) -> Self {
|
||||||
|
let message = format!("cannot access index {} of array out of bounds", index);
|
||||||
|
|
||||||
|
Self::new_from_span(message, span)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn array_invalid_slice_length(span: &Span) -> Self {
|
||||||
|
let message = "illegal length of slice".to_string();
|
||||||
|
|
||||||
|
Self::new_from_span(message, span)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn invalid_dimensions(expected: &ArrayDimensions, actual: &ArrayDimensions, span: &Span) -> Self {
|
pub fn invalid_dimensions(expected: &ArrayDimensions, actual: &ArrayDimensions, span: &Span) -> Self {
|
||||||
let message = format!(
|
let message = format!(
|
||||||
"expected array dimensions {}, found array dimensions {}",
|
"expected array dimensions {}, found array dimensions {}",
|
||||||
|
@ -61,6 +61,12 @@ impl StatementError {
|
|||||||
Self::new_from_span(message, span)
|
Self::new_from_span(message, span)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn array_assign_index_const(span: &Span) -> Self {
|
||||||
|
let message = "Cannot assign to non-const array index".to_string();
|
||||||
|
|
||||||
|
Self::new_from_span(message, span)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn array_assign_interior_index(span: &Span) -> Self {
|
pub fn array_assign_interior_index(span: &Span) -> Self {
|
||||||
let message = "Cannot assign single index to interior of array of values".to_string();
|
let message = "Cannot assign single index to interior of array of values".to_string();
|
||||||
|
|
||||||
@ -217,4 +223,10 @@ impl StatementError {
|
|||||||
|
|
||||||
Self::new_from_span(message, span)
|
Self::new_from_span(message, span)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn loop_index_const(span: &Span) -> Self {
|
||||||
|
let message = "iteration range must be const".to_string();
|
||||||
|
|
||||||
|
Self::new_from_span(message, span)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,10 +16,24 @@
|
|||||||
|
|
||||||
//! 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 std::convert::TryInto;
|
||||||
use leo_asg::{Expression, Span};
|
|
||||||
|
use crate::{
|
||||||
|
arithmetic::*,
|
||||||
|
errors::ExpressionError,
|
||||||
|
program::ConstrainedProgram,
|
||||||
|
relational::*,
|
||||||
|
value::{ConstrainedValue, Integer},
|
||||||
|
GroupType,
|
||||||
|
};
|
||||||
|
use leo_asg::{ConstInt, Expression, Span};
|
||||||
|
|
||||||
use snarkvm_fields::PrimeField;
|
use snarkvm_fields::PrimeField;
|
||||||
|
use snarkvm_gadgets::utilities::{
|
||||||
|
boolean::Boolean,
|
||||||
|
eq::{EqGadget, EvaluateEqGadget},
|
||||||
|
select::CondSelectGadget,
|
||||||
|
};
|
||||||
use snarkvm_r1cs::ConstraintSystem;
|
use snarkvm_r1cs::ConstraintSystem;
|
||||||
|
|
||||||
impl<'a, F: PrimeField, G: GroupType<F>> ConstrainedProgram<'a, F, G> {
|
impl<'a, F: PrimeField, G: GroupType<F>> ConstrainedProgram<'a, F, G> {
|
||||||
@ -31,13 +45,65 @@ impl<'a, F: PrimeField, G: GroupType<F>> ConstrainedProgram<'a, F, G> {
|
|||||||
index: &'a Expression<'a>,
|
index: &'a Expression<'a>,
|
||||||
span: &Span,
|
span: &Span,
|
||||||
) -> Result<ConstrainedValue<'a, F, G>, ExpressionError> {
|
) -> Result<ConstrainedValue<'a, F, G>, ExpressionError> {
|
||||||
let array = match self.enforce_expression(cs, array)? {
|
let mut array = match self.enforce_expression(cs, array)? {
|
||||||
ConstrainedValue::Array(array) => array,
|
ConstrainedValue::Array(array) => array,
|
||||||
value => return Err(ExpressionError::undefined_array(value.to_string(), span)),
|
value => return Err(ExpressionError::undefined_array(value.to_string(), span)),
|
||||||
};
|
};
|
||||||
|
|
||||||
let index_resolved = self.enforce_index(cs, index, span)?;
|
let index_resolved = self.enforce_index(cs, index, span)?;
|
||||||
Ok(array[index_resolved].to_owned())
|
if let Some(resolved) = index_resolved.to_usize() {
|
||||||
|
if resolved >= array.len() {
|
||||||
|
return Err(ExpressionError::array_index_out_of_bounds(resolved, span));
|
||||||
|
}
|
||||||
|
Ok(array[resolved].to_owned())
|
||||||
|
} else {
|
||||||
|
if array.is_empty() {
|
||||||
|
return Err(ExpressionError::array_index_out_of_bounds(0, span));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let array_len: u32 = array
|
||||||
|
.len()
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| ExpressionError::array_length_out_of_bounds(span))?;
|
||||||
|
let bounds_check = evaluate_lt::<F, G, CS>(
|
||||||
|
cs,
|
||||||
|
ConstrainedValue::Integer(index_resolved.clone()),
|
||||||
|
ConstrainedValue::Integer(Integer::new(&ConstInt::U32(array_len))),
|
||||||
|
span,
|
||||||
|
)?;
|
||||||
|
let bounds_check = match bounds_check {
|
||||||
|
ConstrainedValue::Boolean(b) => b,
|
||||||
|
_ => unimplemented!("illegal non-Integer returned from lt"),
|
||||||
|
};
|
||||||
|
let namespace_string = format!("evaluate array access bounds {}:{}", span.line_start, span.col_start);
|
||||||
|
let mut unique_namespace = cs.ns(|| namespace_string);
|
||||||
|
bounds_check
|
||||||
|
.enforce_equal(&mut unique_namespace, &Boolean::Constant(true))
|
||||||
|
.map_err(|e| ExpressionError::cannot_enforce("array bounds check".to_string(), e, span))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut current_value = array.pop().unwrap();
|
||||||
|
for (i, item) in array.into_iter().enumerate() {
|
||||||
|
let namespace_string = format!("evaluate array access eq {} {}:{}", i, span.line_start, span.col_start);
|
||||||
|
let eq_namespace = cs.ns(|| namespace_string);
|
||||||
|
|
||||||
|
let index_bounded = i
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| ExpressionError::array_index_out_of_legal_bounds(span))?;
|
||||||
|
let const_index = ConstInt::U32(index_bounded).cast_to(&index_resolved.get_type());
|
||||||
|
let index_comparison = index_resolved
|
||||||
|
.evaluate_equal(eq_namespace, &Integer::new(&const_index))
|
||||||
|
.map_err(|_| ExpressionError::cannot_evaluate("==".to_string(), span))?;
|
||||||
|
|
||||||
|
let unique_namespace =
|
||||||
|
cs.ns(|| format!("select array access {} {}:{}", i, span.line_start, span.col_start));
|
||||||
|
let value =
|
||||||
|
ConstrainedValue::conditionally_select(unique_namespace, &index_comparison, &item, ¤t_value)
|
||||||
|
.map_err(|e| ExpressionError::cannot_enforce("conditional select".to_string(), e, span))?;
|
||||||
|
current_value = value;
|
||||||
|
}
|
||||||
|
Ok(current_value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
@ -47,6 +113,7 @@ impl<'a, F: PrimeField, G: GroupType<F>> ConstrainedProgram<'a, F, G> {
|
|||||||
array: &'a Expression<'a>,
|
array: &'a Expression<'a>,
|
||||||
left: Option<&'a Expression<'a>>,
|
left: Option<&'a Expression<'a>>,
|
||||||
right: Option<&'a Expression<'a>>,
|
right: Option<&'a Expression<'a>>,
|
||||||
|
length: usize,
|
||||||
span: &Span,
|
span: &Span,
|
||||||
) -> Result<ConstrainedValue<'a, F, G>, ExpressionError> {
|
) -> Result<ConstrainedValue<'a, F, G>, ExpressionError> {
|
||||||
let array = match self.enforce_expression(cs, array)? {
|
let array = match self.enforce_expression(cs, array)? {
|
||||||
@ -56,12 +123,103 @@ impl<'a, F: PrimeField, G: GroupType<F>> ConstrainedProgram<'a, F, G> {
|
|||||||
|
|
||||||
let from_resolved = match left {
|
let from_resolved = match left {
|
||||||
Some(from_index) => self.enforce_index(cs, from_index, span)?,
|
Some(from_index) => self.enforce_index(cs, from_index, span)?,
|
||||||
None => 0usize, // Array slice starts at index 0
|
None => Integer::new(&ConstInt::U32(0)), // Array slice starts at index 0
|
||||||
};
|
};
|
||||||
let to_resolved = match right {
|
let to_resolved = match right {
|
||||||
Some(to_index) => self.enforce_index(cs, to_index, span)?,
|
Some(to_index) => self.enforce_index(cs, to_index, span)?,
|
||||||
None => array.len(), // Array slice ends at array length
|
None => {
|
||||||
|
let index_bounded: u32 = array
|
||||||
|
.len()
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| ExpressionError::array_length_out_of_bounds(span))?;
|
||||||
|
Integer::new(&ConstInt::U32(index_bounded))
|
||||||
|
} // Array slice ends at array length
|
||||||
};
|
};
|
||||||
Ok(ConstrainedValue::Array(array[from_resolved..to_resolved].to_owned()))
|
let const_dimensions = match (from_resolved.to_usize(), to_resolved.to_usize()) {
|
||||||
|
(Some(from), Some(to)) => Some((from, to)),
|
||||||
|
(Some(from), None) => Some((from, from + length)),
|
||||||
|
(None, Some(to)) => Some((to - length, to)),
|
||||||
|
(None, None) => None,
|
||||||
|
};
|
||||||
|
Ok(if let Some((left, right)) = const_dimensions {
|
||||||
|
if right - left != length {
|
||||||
|
return Err(ExpressionError::array_invalid_slice_length(span));
|
||||||
|
}
|
||||||
|
if right > array.len() {
|
||||||
|
return Err(ExpressionError::array_index_out_of_bounds(right, span));
|
||||||
|
}
|
||||||
|
ConstrainedValue::Array(array[left..right].to_owned())
|
||||||
|
} else {
|
||||||
|
{
|
||||||
|
let calc_len = enforce_sub::<F, G, _>(
|
||||||
|
cs,
|
||||||
|
ConstrainedValue::Integer(to_resolved.clone()),
|
||||||
|
ConstrainedValue::Integer(from_resolved.clone()),
|
||||||
|
span,
|
||||||
|
)?;
|
||||||
|
let calc_len = match calc_len {
|
||||||
|
ConstrainedValue::Integer(i) => i,
|
||||||
|
_ => unimplemented!("illegal non-Integer returned from sub"),
|
||||||
|
};
|
||||||
|
let namespace_string = format!(
|
||||||
|
"evaluate array range access length check {}:{}",
|
||||||
|
span.line_start, span.col_start
|
||||||
|
);
|
||||||
|
let mut unique_namespace = cs.ns(|| namespace_string);
|
||||||
|
calc_len
|
||||||
|
.enforce_equal(&mut unique_namespace, &Integer::new(&ConstInt::U32(length as u32)))
|
||||||
|
.map_err(|e| ExpressionError::cannot_enforce("array length check".to_string(), e, span))?;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let bounds_check = evaluate_le::<F, G, _>(
|
||||||
|
cs,
|
||||||
|
ConstrainedValue::Integer(to_resolved),
|
||||||
|
ConstrainedValue::Integer(Integer::new(&ConstInt::U32(array.len() as u32))),
|
||||||
|
span,
|
||||||
|
)?;
|
||||||
|
let bounds_check = match bounds_check {
|
||||||
|
ConstrainedValue::Boolean(b) => b,
|
||||||
|
_ => unimplemented!("illegal non-Integer returned from le"),
|
||||||
|
};
|
||||||
|
let namespace_string = format!(
|
||||||
|
"evaluate array range access bounds {}:{}",
|
||||||
|
span.line_start, span.col_start
|
||||||
|
);
|
||||||
|
let mut unique_namespace = cs.ns(|| namespace_string);
|
||||||
|
bounds_check
|
||||||
|
.enforce_equal(&mut unique_namespace, &Boolean::Constant(true))
|
||||||
|
.map_err(|e| ExpressionError::cannot_enforce("array bounds check".to_string(), e, span))?;
|
||||||
|
}
|
||||||
|
let mut windows = array.windows(length);
|
||||||
|
let mut result = ConstrainedValue::Array(vec![]);
|
||||||
|
|
||||||
|
for i in 0..length {
|
||||||
|
let window = if let Some(window) = windows.next() {
|
||||||
|
window
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
let array_value = ConstrainedValue::Array(window.to_vec());
|
||||||
|
let mut unique_namespace =
|
||||||
|
cs.ns(|| format!("array index eq-check {} {}:{}", i, span.line_start, span.col_start));
|
||||||
|
|
||||||
|
let equality = evaluate_eq::<F, G, _>(
|
||||||
|
&mut unique_namespace,
|
||||||
|
ConstrainedValue::Integer(from_resolved.clone()),
|
||||||
|
ConstrainedValue::Integer(Integer::new(&ConstInt::U32(i as u32))),
|
||||||
|
span,
|
||||||
|
)?;
|
||||||
|
let equality = match equality {
|
||||||
|
ConstrainedValue::Boolean(b) => b,
|
||||||
|
_ => unimplemented!("unexpected non-Boolean for evaluate_eq"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let unique_namespace =
|
||||||
|
unique_namespace.ns(|| format!("array index {} {}:{}", i, span.line_start, span.col_start));
|
||||||
|
result = ConstrainedValue::conditionally_select(unique_namespace, &equality, &array_value, &result)
|
||||||
|
.map_err(|e| ExpressionError::cannot_enforce("conditional select".to_string(), e, span))?;
|
||||||
|
}
|
||||||
|
result
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
//! Enforces an array index expression in a compiled Leo program.
|
//! Enforces an array index expression in a compiled Leo program.
|
||||||
|
|
||||||
use crate::{errors::ExpressionError, program::ConstrainedProgram, value::ConstrainedValue, GroupType};
|
use crate::{errors::ExpressionError, program::ConstrainedProgram, value::ConstrainedValue, GroupType, Integer};
|
||||||
use leo_asg::{Expression, Span};
|
use leo_asg::{Expression, Span};
|
||||||
|
|
||||||
use snarkvm_fields::PrimeField;
|
use snarkvm_fields::PrimeField;
|
||||||
@ -28,9 +28,9 @@ impl<'a, F: PrimeField, G: GroupType<F>> ConstrainedProgram<'a, F, G> {
|
|||||||
cs: &mut CS,
|
cs: &mut CS,
|
||||||
index: &'a Expression<'a>,
|
index: &'a Expression<'a>,
|
||||||
span: &Span,
|
span: &Span,
|
||||||
) -> Result<usize, ExpressionError> {
|
) -> Result<Integer, ExpressionError> {
|
||||||
match self.enforce_expression(cs, index)? {
|
match self.enforce_expression(cs, index)? {
|
||||||
ConstrainedValue::Integer(number) => Ok(number.to_usize(span)?),
|
ConstrainedValue::Integer(number) => Ok(number),
|
||||||
value => Err(ExpressionError::invalid_index(value.to_string(), span)),
|
value => Err(ExpressionError::invalid_index(value.to_string(), span)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,8 +32,15 @@ impl<'a, F: PrimeField, G: GroupType<F>> ConstrainedProgram<'a, F, G> {
|
|||||||
left: &'a Expression<'a>,
|
left: &'a Expression<'a>,
|
||||||
right: &'a Expression<'a>,
|
right: &'a Expression<'a>,
|
||||||
) -> Result<ConstrainedValuePair<'a, F, G>, ExpressionError> {
|
) -> Result<ConstrainedValuePair<'a, F, G>, ExpressionError> {
|
||||||
let resolved_left = self.enforce_expression(cs, left)?;
|
let resolved_left = {
|
||||||
let resolved_right = self.enforce_expression(cs, right)?;
|
let mut left_namespace = cs.ns(|| "left".to_string());
|
||||||
|
self.enforce_expression(&mut left_namespace, left)?
|
||||||
|
};
|
||||||
|
|
||||||
|
let resolved_right = {
|
||||||
|
let mut right_namespace = cs.ns(|| "right".to_string());
|
||||||
|
self.enforce_expression(&mut right_namespace, right)?
|
||||||
|
};
|
||||||
|
|
||||||
Ok((resolved_left, resolved_right))
|
Ok((resolved_left, resolved_right))
|
||||||
}
|
}
|
||||||
|
@ -133,9 +133,13 @@ impl<'a, F: PrimeField, G: GroupType<F>> ConstrainedProgram<'a, F, G> {
|
|||||||
Expression::ArrayAccess(ArrayAccessExpression { array, index, .. }) => {
|
Expression::ArrayAccess(ArrayAccessExpression { array, index, .. }) => {
|
||||||
self.enforce_array_access(cs, array.get(), index.get(), span)
|
self.enforce_array_access(cs, array.get(), index.get(), span)
|
||||||
}
|
}
|
||||||
Expression::ArrayRangeAccess(ArrayRangeAccessExpression { array, left, right, .. }) => {
|
Expression::ArrayRangeAccess(ArrayRangeAccessExpression {
|
||||||
self.enforce_array_range_access(cs, array.get(), left.get(), right.get(), span)
|
array,
|
||||||
}
|
left,
|
||||||
|
right,
|
||||||
|
length,
|
||||||
|
..
|
||||||
|
}) => self.enforce_array_range_access(cs, array.get(), left.get(), right.get(), *length, span),
|
||||||
|
|
||||||
// Tuples
|
// Tuples
|
||||||
Expression::TupleInit(TupleInitExpression { elements, .. }) => self.enforce_tuple(cs, &elements[..]),
|
Expression::TupleInit(TupleInitExpression { elements, .. }) => self.enforce_tuple(cs, &elements[..]),
|
||||||
|
@ -40,7 +40,7 @@ impl<'a, F: PrimeField, G: GroupType<F>> ConstrainedProgram<'a, F, G> {
|
|||||||
// Check for out of bounds access.
|
// Check for out of bounds access.
|
||||||
if index > tuple.len() - 1 {
|
if index > tuple.len() - 1 {
|
||||||
// probably safe to be a panic here
|
// probably safe to be a panic here
|
||||||
return Err(ExpressionError::index_out_of_bounds(index, span));
|
return Err(ExpressionError::tuple_index_out_of_bounds(index, span));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(tuple[index].to_owned())
|
Ok(tuple[index].to_owned())
|
||||||
|
@ -51,15 +51,31 @@ impl<'a, F: PrimeField, G: GroupType<F>> ConstrainedProgram<'a, F, G> {
|
|||||||
let start_index = left
|
let start_index = left
|
||||||
.get()
|
.get()
|
||||||
.map(|start| self.enforce_index(cs, start, span))
|
.map(|start| self.enforce_index(cs, start, span))
|
||||||
|
.transpose()?
|
||||||
|
.map(|x| {
|
||||||
|
x.to_usize()
|
||||||
|
.ok_or_else(|| StatementError::array_assign_index_const(span))
|
||||||
|
})
|
||||||
|
.transpose()?;
|
||||||
|
let stop_index = right
|
||||||
|
.get()
|
||||||
|
.map(|stop| self.enforce_index(cs, stop, span))
|
||||||
|
.transpose()?
|
||||||
|
.map(|x| {
|
||||||
|
x.to_usize()
|
||||||
|
.ok_or_else(|| StatementError::array_assign_index_const(span))
|
||||||
|
})
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
let stop_index = right.get().map(|stop| self.enforce_index(cs, stop, span)).transpose()?;
|
|
||||||
|
|
||||||
output.push(ResolvedAssigneeAccess::ArrayRange(start_index, stop_index));
|
output.push(ResolvedAssigneeAccess::ArrayRange(start_index, stop_index));
|
||||||
Ok(inner)
|
Ok(inner)
|
||||||
}
|
}
|
||||||
Expression::ArrayAccess(ArrayAccessExpression { array, index, .. }) => {
|
Expression::ArrayAccess(ArrayAccessExpression { array, index, .. }) => {
|
||||||
let inner = self.prepare_mut_access(cs, array.get(), span, output)?;
|
let inner = self.prepare_mut_access(cs, array.get(), span, output)?;
|
||||||
let index = self.enforce_index(cs, index.get(), span)?;
|
let index = self
|
||||||
|
.enforce_index(cs, index.get(), span)?
|
||||||
|
.to_usize()
|
||||||
|
.ok_or_else(|| StatementError::array_assign_index_const(span))?;
|
||||||
|
|
||||||
output.push(ResolvedAssigneeAccess::ArrayIndex(index));
|
output.push(ResolvedAssigneeAccess::ArrayIndex(index));
|
||||||
Ok(inner)
|
Ok(inner)
|
||||||
|
@ -45,19 +45,35 @@ impl<'a, F: PrimeField, G: GroupType<F>> ConstrainedProgram<'a, F, G> {
|
|||||||
let start_index = start
|
let start_index = start
|
||||||
.get()
|
.get()
|
||||||
.map(|start| self.enforce_index(cs, start, &span))
|
.map(|start| self.enforce_index(cs, start, &span))
|
||||||
|
.transpose()?
|
||||||
|
.map(|x| {
|
||||||
|
x.to_usize()
|
||||||
|
.ok_or_else(|| StatementError::array_assign_index_const(&span))
|
||||||
|
})
|
||||||
|
.transpose()?;
|
||||||
|
let stop_index = stop
|
||||||
|
.get()
|
||||||
|
.map(|stop| self.enforce_index(cs, stop, &span))
|
||||||
|
.transpose()?
|
||||||
|
.map(|x| {
|
||||||
|
x.to_usize()
|
||||||
|
.ok_or_else(|| StatementError::array_assign_index_const(&span))
|
||||||
|
})
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
let stop_index = stop.get().map(|stop| self.enforce_index(cs, stop, &span)).transpose()?;
|
|
||||||
Ok(ResolvedAssigneeAccess::ArrayRange(start_index, stop_index))
|
Ok(ResolvedAssigneeAccess::ArrayRange(start_index, stop_index))
|
||||||
}
|
}
|
||||||
AssignAccess::ArrayIndex(index) => {
|
AssignAccess::ArrayIndex(index) => {
|
||||||
let index = self.enforce_index(cs, index.get(), &span)?;
|
let index = self
|
||||||
|
.enforce_index(cs, index.get(), &span)?
|
||||||
|
.to_usize()
|
||||||
|
.ok_or_else(|| StatementError::array_assign_index_const(&span))?;
|
||||||
|
|
||||||
Ok(ResolvedAssigneeAccess::ArrayIndex(index))
|
Ok(ResolvedAssigneeAccess::ArrayIndex(index))
|
||||||
}
|
}
|
||||||
AssignAccess::Tuple(index) => Ok(ResolvedAssigneeAccess::Tuple(*index, span.clone())),
|
AssignAccess::Tuple(index) => Ok(ResolvedAssigneeAccess::Tuple(*index, span.clone())),
|
||||||
AssignAccess::Member(identifier) => Ok(ResolvedAssigneeAccess::Member(identifier.clone())),
|
AssignAccess::Member(identifier) => Ok(ResolvedAssigneeAccess::Member(identifier.clone())),
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>, crate::errors::ExpressionError>>()?;
|
.collect::<Result<Vec<_>, StatementError>>()?;
|
||||||
|
|
||||||
let variable = assignee.target_variable.get().borrow();
|
let variable = assignee.target_variable.get().borrow();
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
//! Enforces an iteration statement in a compiled Leo program.
|
//! Enforces an iteration statement in a compiled Leo program.
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
errors::StatementError,
|
||||||
program::ConstrainedProgram,
|
program::ConstrainedProgram,
|
||||||
value::ConstrainedValue,
|
value::ConstrainedValue,
|
||||||
GroupType,
|
GroupType,
|
||||||
@ -42,8 +43,14 @@ impl<'a, F: PrimeField, G: GroupType<F>> ConstrainedProgram<'a, F, G> {
|
|||||||
|
|
||||||
let span = statement.span.clone().unwrap_or_default();
|
let span = statement.span.clone().unwrap_or_default();
|
||||||
|
|
||||||
let from = self.enforce_index(cs, statement.start.get(), &span)?;
|
let from = self
|
||||||
let to = self.enforce_index(cs, statement.stop.get(), &span)?;
|
.enforce_index(cs, statement.start.get(), &span)?
|
||||||
|
.to_usize()
|
||||||
|
.ok_or_else(|| StatementError::loop_index_const(&span))?;
|
||||||
|
let to = self
|
||||||
|
.enforce_index(cs, statement.stop.get(), &span)?
|
||||||
|
.to_usize()
|
||||||
|
.ok_or_else(|| StatementError::loop_index_const(&span))?;
|
||||||
|
|
||||||
for i in from..to {
|
for i in from..to {
|
||||||
// Store index in current function scope.
|
// Store index in current function scope.
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
//! Enforces a statement in a compiled Leo program.
|
//! Enforces a statement in a compiled Leo program.
|
||||||
|
|
||||||
use crate::{errors::StatementError, program::ConstrainedProgram, value::ConstrainedValue, GroupType};
|
use crate::{errors::StatementError, program::ConstrainedProgram, value::ConstrainedValue, GroupType};
|
||||||
use leo_asg::Statement;
|
use leo_asg::{Node, Statement};
|
||||||
|
|
||||||
use snarkvm_fields::PrimeField;
|
use snarkvm_fields::PrimeField;
|
||||||
use snarkvm_gadgets::traits::utilities::boolean::Boolean;
|
use snarkvm_gadgets::traits::utilities::boolean::Boolean;
|
||||||
@ -42,6 +42,9 @@ impl<'a, F: PrimeField, G: GroupType<F>> ConstrainedProgram<'a, F, G> {
|
|||||||
statement: &'a Statement<'a>,
|
statement: &'a Statement<'a>,
|
||||||
) -> StatementResult<Vec<IndicatorAndConstrainedValue<'a, F, G>>> {
|
) -> StatementResult<Vec<IndicatorAndConstrainedValue<'a, F, G>>> {
|
||||||
let mut results = vec![];
|
let mut results = vec![];
|
||||||
|
let span = statement.span().cloned().unwrap_or_default();
|
||||||
|
let mut cs = cs.ns(|| format!("statement {}:{}", span.line_start, span.col_start));
|
||||||
|
let cs = &mut cs;
|
||||||
|
|
||||||
match statement {
|
match statement {
|
||||||
Statement::Return(statement) => {
|
Statement::Return(statement) => {
|
||||||
|
@ -111,15 +111,9 @@ impl Integer {
|
|||||||
match_integer!(integer => integer.get_value())
|
match_integer!(integer => integer.get_value())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_usize(&self, span: &Span) -> Result<usize, IntegerError> {
|
pub fn to_usize(&self) -> Option<usize> {
|
||||||
let unsigned_integer = self;
|
let unsigned_integer = self;
|
||||||
let value_option: Option<String> = match_unsigned_integer!(unsigned_integer => unsigned_integer.get_value());
|
match_unsigned_integer!(unsigned_integer => unsigned_integer.get_index())
|
||||||
|
|
||||||
let value = value_option.ok_or_else(|| IntegerError::invalid_index(span))?;
|
|
||||||
let value_usize = value
|
|
||||||
.parse::<usize>()
|
|
||||||
.map_err(|_| IntegerError::invalid_integer(value, span))?;
|
|
||||||
Ok(value_usize)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_type(&self) -> IntegerType {
|
pub fn get_type(&self) -> IntegerType {
|
||||||
@ -146,148 +140,17 @@ impl Integer {
|
|||||||
span: &Span,
|
span: &Span,
|
||||||
) -> Result<Self, IntegerError> {
|
) -> Result<Self, IntegerError> {
|
||||||
Ok(match integer_type {
|
Ok(match integer_type {
|
||||||
IntegerType::U8 => {
|
IntegerType::U8 => allocate_type!(u8, UInt8, Integer::U8, cs, name, option, span),
|
||||||
let u8_option = option.map(|s| {
|
IntegerType::U16 => allocate_type!(u16, UInt16, Integer::U16, cs, name, option, span),
|
||||||
s.parse::<u8>()
|
IntegerType::U32 => allocate_type!(u32, UInt32, Integer::U32, cs, name, option, span),
|
||||||
.map_err(|_| IntegerError::invalid_integer(s, span))
|
IntegerType::U64 => allocate_type!(u64, UInt64, Integer::U64, cs, name, option, span),
|
||||||
.unwrap()
|
IntegerType::U128 => allocate_type!(u128, UInt128, Integer::U128, cs, name, option, span),
|
||||||
});
|
|
||||||
|
|
||||||
let u8_result = UInt8::alloc(
|
IntegerType::I8 => allocate_type!(i8, Int8, Integer::I8, cs, name, option, span),
|
||||||
cs.ns(|| format!("`{}: u8` {}:{}", name, span.line_start, span.col_start)),
|
IntegerType::I16 => allocate_type!(i16, Int16, Integer::I16, cs, name, option, span),
|
||||||
|| u8_option.ok_or(SynthesisError::AssignmentMissing),
|
IntegerType::I32 => allocate_type!(i32, Int32, Integer::I32, cs, name, option, span),
|
||||||
)
|
IntegerType::I64 => allocate_type!(i64, Int64, Integer::I64, cs, name, option, span),
|
||||||
.map_err(|_| IntegerError::missing_integer(format!("{}: u8", name), span))?;
|
IntegerType::I128 => allocate_type!(i128, Int128, Integer::I128, cs, name, option, span),
|
||||||
|
|
||||||
Integer::U8(u8_result)
|
|
||||||
}
|
|
||||||
IntegerType::U16 => {
|
|
||||||
let u16_option = option.map(|s| {
|
|
||||||
s.parse::<u16>()
|
|
||||||
.map_err(|_| IntegerError::invalid_integer(s, span))
|
|
||||||
.unwrap()
|
|
||||||
});
|
|
||||||
let u16_result = UInt16::alloc(
|
|
||||||
cs.ns(|| format!("`{}: u16` {}:{}", name, span.line_start, span.col_start)),
|
|
||||||
|| u16_option.ok_or(SynthesisError::AssignmentMissing),
|
|
||||||
)
|
|
||||||
.map_err(|_| IntegerError::missing_integer(format!("{}: u16", name), span))?;
|
|
||||||
|
|
||||||
Integer::U16(u16_result)
|
|
||||||
}
|
|
||||||
IntegerType::U32 => {
|
|
||||||
let u32_option = option.map(|s| {
|
|
||||||
s.parse::<u32>()
|
|
||||||
.map_err(|_| IntegerError::invalid_integer(s, span))
|
|
||||||
.unwrap()
|
|
||||||
});
|
|
||||||
let u32_result = UInt32::alloc(
|
|
||||||
cs.ns(|| format!("`{}: u32` {}:{}", name, span.line_start, span.col_start)),
|
|
||||||
|| u32_option.ok_or(SynthesisError::AssignmentMissing),
|
|
||||||
)
|
|
||||||
.map_err(|_| IntegerError::missing_integer(format!("{}: u32", name), span))?;
|
|
||||||
|
|
||||||
Integer::U32(u32_result)
|
|
||||||
}
|
|
||||||
IntegerType::U64 => {
|
|
||||||
let u64_option = option.map(|s| {
|
|
||||||
s.parse::<u64>()
|
|
||||||
.map_err(|_| IntegerError::invalid_integer(s, span))
|
|
||||||
.unwrap()
|
|
||||||
});
|
|
||||||
let u64_result = UInt64::alloc(
|
|
||||||
cs.ns(|| format!("`{}: u64` {}:{}", name, span.line_start, span.col_start)),
|
|
||||||
|| u64_option.ok_or(SynthesisError::AssignmentMissing),
|
|
||||||
)
|
|
||||||
.map_err(|_| IntegerError::missing_integer(format!("{}: u64", name), span))?;
|
|
||||||
|
|
||||||
Integer::U64(u64_result)
|
|
||||||
}
|
|
||||||
IntegerType::U128 => {
|
|
||||||
let u128_option = option.map(|s| {
|
|
||||||
s.parse::<u128>()
|
|
||||||
.map_err(|_| IntegerError::invalid_integer(s, span))
|
|
||||||
.unwrap()
|
|
||||||
});
|
|
||||||
let u128_result = UInt128::alloc(
|
|
||||||
cs.ns(|| format!("`{}: u128` {}:{}", name, span.line_start, span.col_start)),
|
|
||||||
|| u128_option.ok_or(SynthesisError::AssignmentMissing),
|
|
||||||
)
|
|
||||||
.map_err(|_| IntegerError::missing_integer(format!("{}: u128", name), span))?;
|
|
||||||
|
|
||||||
Integer::U128(u128_result)
|
|
||||||
}
|
|
||||||
|
|
||||||
IntegerType::I8 => {
|
|
||||||
let i8_option = option.map(|s| {
|
|
||||||
s.parse::<i8>()
|
|
||||||
.map_err(|_| IntegerError::invalid_integer(s, span))
|
|
||||||
.unwrap()
|
|
||||||
});
|
|
||||||
let i8_result = Int8::alloc(
|
|
||||||
cs.ns(|| format!("`{}: i8` {}:{}", name, span.line_start, span.col_start)),
|
|
||||||
|| i8_option.ok_or(SynthesisError::AssignmentMissing),
|
|
||||||
)
|
|
||||||
.map_err(|_| IntegerError::missing_integer(format!("{}: i8", name), span))?;
|
|
||||||
|
|
||||||
Integer::I8(i8_result)
|
|
||||||
}
|
|
||||||
IntegerType::I16 => {
|
|
||||||
let i16_option = option.map(|s| {
|
|
||||||
s.parse::<i16>()
|
|
||||||
.map_err(|_| IntegerError::invalid_integer(s, span))
|
|
||||||
.unwrap()
|
|
||||||
});
|
|
||||||
let i16_result = Int16::alloc(
|
|
||||||
cs.ns(|| format!("`{}: i16` {}:{}", name, span.line_start, span.col_start)),
|
|
||||||
|| i16_option.ok_or(SynthesisError::AssignmentMissing),
|
|
||||||
)
|
|
||||||
.map_err(|_| IntegerError::missing_integer(format!("{}: i16", name), span))?;
|
|
||||||
|
|
||||||
Integer::I16(i16_result)
|
|
||||||
}
|
|
||||||
IntegerType::I32 => {
|
|
||||||
let i32_option = option.map(|s| {
|
|
||||||
s.parse::<i32>()
|
|
||||||
.map_err(|_| IntegerError::invalid_integer(s, span))
|
|
||||||
.unwrap()
|
|
||||||
});
|
|
||||||
let i32_result = Int32::alloc(
|
|
||||||
cs.ns(|| format!("`{}: i32` {}:{}", name, span.line_start, span.col_start)),
|
|
||||||
|| i32_option.ok_or(SynthesisError::AssignmentMissing),
|
|
||||||
)
|
|
||||||
.map_err(|_| IntegerError::missing_integer(format!("{}: i32", name), span))?;
|
|
||||||
|
|
||||||
Integer::I32(i32_result)
|
|
||||||
}
|
|
||||||
IntegerType::I64 => {
|
|
||||||
let i64_option = option.map(|s| {
|
|
||||||
s.parse::<i64>()
|
|
||||||
.map_err(|_| IntegerError::invalid_integer(s, span))
|
|
||||||
.unwrap()
|
|
||||||
});
|
|
||||||
let i64_result = Int64::alloc(
|
|
||||||
cs.ns(|| format!("`{}: i64` {}:{}", name, span.line_start, span.col_start)),
|
|
||||||
|| i64_option.ok_or(SynthesisError::AssignmentMissing),
|
|
||||||
)
|
|
||||||
.map_err(|_| IntegerError::missing_integer(format!("{}: i64", name), span))?;
|
|
||||||
|
|
||||||
Integer::I64(i64_result)
|
|
||||||
}
|
|
||||||
IntegerType::I128 => {
|
|
||||||
let i128_option = option.map(|s| {
|
|
||||||
s.parse::<i128>()
|
|
||||||
.map_err(|_| IntegerError::invalid_integer(s, span))
|
|
||||||
.unwrap()
|
|
||||||
});
|
|
||||||
let i128_result = Int128::alloc(
|
|
||||||
cs.ns(|| format!("`{}: i128` {}:{}", name, span.line_start, span.col_start)),
|
|
||||||
|| i128_option.ok_or(SynthesisError::AssignmentMissing),
|
|
||||||
)
|
|
||||||
.map_err(|_| IntegerError::missing_integer(format!("{}: i128", name), span))?;
|
|
||||||
|
|
||||||
Integer::I128(i128_result)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,3 +159,29 @@ macro_rules! match_integers_span {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macro_rules! allocate_type {
|
||||||
|
($rust_ty:ty, $gadget_ty:ty, $leo_ty:path, $cs:expr, $name:expr, $option:expr, $span:expr) => {{
|
||||||
|
let option = $option.map(|s| {
|
||||||
|
s.parse::<$rust_ty>()
|
||||||
|
.map_err(|_| IntegerError::invalid_integer(s, $span))
|
||||||
|
.unwrap()
|
||||||
|
});
|
||||||
|
|
||||||
|
let result = <$gadget_ty>::alloc(
|
||||||
|
$cs.ns(|| {
|
||||||
|
format!(
|
||||||
|
"`{}: {}` {}:{}",
|
||||||
|
$name,
|
||||||
|
stringify!($rust_ty),
|
||||||
|
$span.line_start,
|
||||||
|
$span.col_start
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
|| option.ok_or(SynthesisError::AssignmentMissing),
|
||||||
|
)
|
||||||
|
.map_err(|_| IntegerError::missing_integer(format!("{}: {}", $name, stringify!($rust_ty)), $span))?;
|
||||||
|
|
||||||
|
$leo_ty(result)
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
@ -539,3 +539,178 @@ fn test_variable_slice_fail() {
|
|||||||
|
|
||||||
expect_asg_error(error);
|
expect_asg_error(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_array_index() {
|
||||||
|
let program_string = r#"
|
||||||
|
function main(i: u32) {
|
||||||
|
let b = [1u8, 2, 3, 4];
|
||||||
|
|
||||||
|
console.assert(2 == b[i]);
|
||||||
|
console.assert(3 == b[2]);
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
let input_string = r#"
|
||||||
|
[main]
|
||||||
|
i: u32 = 1;
|
||||||
|
"#;
|
||||||
|
let program = parse_program_with_input(program_string, input_string).unwrap();
|
||||||
|
|
||||||
|
assert_satisfied(program);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_array_index_bounds_fail() {
|
||||||
|
let program_string = r#"
|
||||||
|
function main(i: u32) {
|
||||||
|
let b = [1u8, 2, 3, 4];
|
||||||
|
|
||||||
|
console.assert(2 == b[i]);
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
let input_string = r#"
|
||||||
|
[main]
|
||||||
|
i: u32 = 4;
|
||||||
|
"#;
|
||||||
|
let program = parse_program_with_input(program_string, input_string).unwrap();
|
||||||
|
|
||||||
|
expect_compiler_error(program);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_const_array_index_bounds_fail() {
|
||||||
|
let program_string = r#"
|
||||||
|
function main() {
|
||||||
|
let b = [1u8, 2, 3, 4];
|
||||||
|
const i: u32 = 4;
|
||||||
|
|
||||||
|
console.assert(2 == b[i]);
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
let error = parse_program(program_string).err().unwrap();
|
||||||
|
|
||||||
|
expect_asg_error(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_array_range_index() {
|
||||||
|
let program_string = r#"
|
||||||
|
function main(i: u32) {
|
||||||
|
let b = [1u8, 2, 3, 4];
|
||||||
|
|
||||||
|
console.assert([1u8, 2] == b[0..i]);
|
||||||
|
console.assert([3u8, 4] == b[i..4]);
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
let input_string = r#"
|
||||||
|
[main]
|
||||||
|
i: u32 = 2;
|
||||||
|
"#;
|
||||||
|
let program = parse_program_with_input(program_string, input_string).unwrap();
|
||||||
|
|
||||||
|
assert_satisfied(program);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_array_range_index_dyn() {
|
||||||
|
let program_string = r#"
|
||||||
|
function main(i: u32) {
|
||||||
|
let b = [1u8, 2, 3, 4];
|
||||||
|
|
||||||
|
console.assert([1u8, 2] == b[..i]);
|
||||||
|
console.assert([3u8, 4] == b[i..]);
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
let input_string = r#"
|
||||||
|
[main]
|
||||||
|
i: u32 = 2;
|
||||||
|
"#;
|
||||||
|
let program = parse_program_with_input(program_string, input_string).unwrap();
|
||||||
|
|
||||||
|
assert_satisfied(program);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_array_range_index_full_dyn() {
|
||||||
|
let program_string = r#"
|
||||||
|
function main(i: u32, y: u32) {
|
||||||
|
let b = [1u8, 2, 3, 4];
|
||||||
|
|
||||||
|
console.assert([3u8, 4] == b[i..y]);
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
let input_string = r#"
|
||||||
|
[main]
|
||||||
|
i: u32 = 2;
|
||||||
|
y: u32 = 4;
|
||||||
|
"#;
|
||||||
|
let program = parse_program_with_input(program_string, input_string).unwrap();
|
||||||
|
|
||||||
|
assert_satisfied(program);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_array_range_index_out_of_bounds_fail() {
|
||||||
|
let program_string = r#"
|
||||||
|
function main() {
|
||||||
|
let b = [1u8, 2, 3, 4];
|
||||||
|
|
||||||
|
console.assert([1, 2] == b[3..5]);
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
let error = parse_program(program_string).err().unwrap();
|
||||||
|
|
||||||
|
expect_asg_error(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_array_range_index_invalid_bounds_fail() {
|
||||||
|
let program_string = r#"
|
||||||
|
function main() {
|
||||||
|
let b = [1u8, 2, 3, 4];
|
||||||
|
|
||||||
|
console.assert([1, 2] == b[2..1]);
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
let error = parse_program(program_string).err().unwrap();
|
||||||
|
|
||||||
|
expect_asg_error(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_array_range_index_full_dyn_resized_fail() {
|
||||||
|
let program_string = r#"
|
||||||
|
function main(i: u32, y: u32) {
|
||||||
|
let b = [1u8, 2, 3, 4];
|
||||||
|
|
||||||
|
console.assert([3u8, 4] == b[i..y]);
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
let input_string = r#"
|
||||||
|
[main]
|
||||||
|
i: u32 = 1;
|
||||||
|
y: u32 = 4;
|
||||||
|
"#;
|
||||||
|
let program = parse_program_with_input(program_string, input_string).unwrap();
|
||||||
|
|
||||||
|
expect_compiler_error(program);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_array_range_index_full_dyn_bounds_fail() {
|
||||||
|
let program_string = r#"
|
||||||
|
function main(i: u32, y: u32) {
|
||||||
|
let b = [1u8, 2, 3, 4];
|
||||||
|
|
||||||
|
console.assert([3u8, 4] == b[i..y]);
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
let input_string = r#"
|
||||||
|
[main]
|
||||||
|
i: u32 = 3;
|
||||||
|
y: u32 = 5;
|
||||||
|
"#;
|
||||||
|
let program = parse_program_with_input(program_string, input_string).unwrap();
|
||||||
|
|
||||||
|
expect_compiler_error(program);
|
||||||
|
}
|
||||||
|
@ -5,5 +5,5 @@ function main() {
|
|||||||
|
|
||||||
do_nothing(arr);
|
do_nothing(arr);
|
||||||
do_nothing([...arr]);
|
do_nothing([...arr]);
|
||||||
do_nothing(arr[1u32..]);
|
do_nothing(arr[0u32..]);
|
||||||
}
|
}
|
2
examples/silly-sudoku/.gitignore
vendored
Normal file
2
examples/silly-sudoku/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
outputs/
|
||||||
|
/.leo
|
8
examples/silly-sudoku/Leo.toml
Normal file
8
examples/silly-sudoku/Leo.toml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
[project]
|
||||||
|
name = "silly-sudoku"
|
||||||
|
version = "0.1.3"
|
||||||
|
description = "A simple Sudoku puzzle grid"
|
||||||
|
license = "MIT"
|
||||||
|
|
||||||
|
[remote]
|
||||||
|
author = "howard"
|
23
examples/silly-sudoku/README.md
Normal file
23
examples/silly-sudoku/README.md
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# silly-sudoku
|
||||||
|
|
||||||
|
A simple Sudoku puzzle grid in Leo.
|
||||||
|
|
||||||
|
## Walkthrough
|
||||||
|
|
||||||
|
Start by defining a puzzle grid:
|
||||||
|
```
|
||||||
|
[[0, 4, 6],
|
||||||
|
[3, 0, 9],
|
||||||
|
[7, 5, 0]]
|
||||||
|
```
|
||||||
|
We treat all 0's as empty cells in the grid.
|
||||||
|
|
||||||
|
Next, generate an answer and construct it as a puzzle grid solution:
|
||||||
|
```
|
||||||
|
[[8, 4, 6],
|
||||||
|
[3, 1, 9],
|
||||||
|
[7, 5, 2]]
|
||||||
|
```
|
||||||
|
|
||||||
|
The SillySudoku circuit will proceed to verify that the solution grid matches the starting puzzle grid,
|
||||||
|
and check that each number between 1 - 9 is used exactly once.
|
12
examples/silly-sudoku/inputs/silly-sudoku.in
Normal file
12
examples/silly-sudoku/inputs/silly-sudoku.in
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// The program input for tmp-test/src/main.leo
|
||||||
|
[main]
|
||||||
|
puzzle: [u8; (3, 3)] = [[0, 2, 0],
|
||||||
|
[0, 0, 6],
|
||||||
|
[0, 8, 9]];
|
||||||
|
|
||||||
|
answer: [u8; (3, 3)] = [[1, 2, 3],
|
||||||
|
[4, 5, 6],
|
||||||
|
[7, 8, 9]];
|
||||||
|
|
||||||
|
[registers]
|
||||||
|
r: bool = false;
|
26
examples/silly-sudoku/inputs/silly-sudoku.state
Normal file
26
examples/silly-sudoku/inputs/silly-sudoku.state
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// The program state for tmp-test/src/main.leo
|
||||||
|
[[public]]
|
||||||
|
|
||||||
|
[state]
|
||||||
|
leaf_index: u32 = 0;
|
||||||
|
root: [u8; 32] = [0; 32];
|
||||||
|
|
||||||
|
[[private]]
|
||||||
|
|
||||||
|
[record]
|
||||||
|
serial_number: [u8; 64] = [0; 64];
|
||||||
|
commitment: [u8; 32] = [0; 32];
|
||||||
|
owner: address = aleo1daxej63vwrmn2zhl4dymygagh89k5d2vaw6rjauueme7le6k2q8sjn0ng9;
|
||||||
|
is_dummy: bool = false;
|
||||||
|
value: u64 = 0;
|
||||||
|
payload: [u8; 32] = [0; 32];
|
||||||
|
birth_program_id: [u8; 48] = [0; 48];
|
||||||
|
death_program_id: [u8; 48] = [0; 48];
|
||||||
|
serial_number_nonce: [u8; 32] = [0; 32];
|
||||||
|
commitment_randomness: [u8; 32] = [0; 32];
|
||||||
|
|
||||||
|
[state_leaf]
|
||||||
|
path: [u8; 128] = [0; 128];
|
||||||
|
memo: [u8; 32] = [0; 32];
|
||||||
|
network_id: u8 = 0;
|
||||||
|
leaf_randomness: [u8; 32] = [0; 32];
|
70
examples/silly-sudoku/src/lib.leo
Normal file
70
examples/silly-sudoku/src/lib.leo
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
/**
|
||||||
|
* The SillySudoku circuit
|
||||||
|
*
|
||||||
|
* This circuit generates a silly Sudoku puzzle,
|
||||||
|
* by constructing a 3x3 puzzle grid with some preset numbers 1-9,
|
||||||
|
* and requiring an answer where each number is used exactly once.
|
||||||
|
*
|
||||||
|
* -----------
|
||||||
|
* | 5 | 8 | 3 |
|
||||||
|
* |-----------|
|
||||||
|
* | 2 | 7 | 4 |
|
||||||
|
* |-----------|
|
||||||
|
* | 1 | 9 | 6 |
|
||||||
|
* -----------
|
||||||
|
*/
|
||||||
|
circuit SillySudoku {
|
||||||
|
// The starting grid values for the Sudoku puzzle.
|
||||||
|
// Unset cells on the puzzle grid are set to 0.
|
||||||
|
puzzle_grid: [u8; (3, 3)],
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if a given Sudoku answer is correct.
|
||||||
|
*
|
||||||
|
* Verifies a given answer by iterating through the Sudoku puzzle,
|
||||||
|
* and checking that each number is set exactly once.
|
||||||
|
*/
|
||||||
|
function solve(self, answer: [u8; (3, 3)]) -> bool {
|
||||||
|
// The result boolean is set to true, if the answer is correct.
|
||||||
|
let result = true;
|
||||||
|
// An array that tracks the numbers used on the Sudoku grid.
|
||||||
|
let seen = [false; 9];
|
||||||
|
|
||||||
|
// Iterate through the Sudoku grid and check each cell.
|
||||||
|
for i in 0..3 {
|
||||||
|
for j in 0..3 {
|
||||||
|
|
||||||
|
// Fetch the current cell value for the Sudoku grid.
|
||||||
|
let grid_value = self.puzzle_grid[i][j];
|
||||||
|
|
||||||
|
// Fetch the current cell value for the given answer.
|
||||||
|
let answer_value = answer[i][j];
|
||||||
|
|
||||||
|
// Set the index by subtracting 1 from the answer value.
|
||||||
|
let index = answer_value - 1;
|
||||||
|
|
||||||
|
// Check if this number has already been used on the grid.
|
||||||
|
let already_seen: bool = seen[index];
|
||||||
|
|
||||||
|
// If this number is already used, the answer is incorrect.
|
||||||
|
// Sets the result to false.
|
||||||
|
if already_seen {
|
||||||
|
result = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the cell is not empty, and the grid value doesn't match
|
||||||
|
// the answer value, the answer is incorrect.
|
||||||
|
// Sets the result to false.
|
||||||
|
if (grid_value != 0 && grid_value != answer_value) {
|
||||||
|
result = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sets the answer value as seen.
|
||||||
|
seen[index] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if all numbers 1-9 have been seen exactly once.
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
71
examples/silly-sudoku/src/main.leo
Normal file
71
examples/silly-sudoku/src/main.leo
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import lib.SillySudoku;
|
||||||
|
|
||||||
|
// The `silly-sudoku` main function
|
||||||
|
function main(puzzle: [u8; (3, 3)], answer: [u8; (3, 3)]) -> bool {
|
||||||
|
console.log("Starting Sudoku solver...");
|
||||||
|
console.log("{}", puzzle);
|
||||||
|
|
||||||
|
// Instantiate the Sudoku puzzle.
|
||||||
|
let sudoku = SillySudoku { puzzle_grid: puzzle };
|
||||||
|
|
||||||
|
console.log("Checking Sudoku answer...");
|
||||||
|
console.log("{}", answer);
|
||||||
|
|
||||||
|
// Evaluate the Sudoku puzzle with the given answer.
|
||||||
|
let result = sudoku.solve(answer);
|
||||||
|
|
||||||
|
console.log("The answer is {}.", result);
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests that the `silly-sudoku` circuit outputs true on a correct answer.
|
||||||
|
@test
|
||||||
|
function test_solve_pass() {
|
||||||
|
let puzzle: [u8; (3, 3)] = [[0, 2, 0],
|
||||||
|
[0, 0, 6],
|
||||||
|
[0, 8, 9]];
|
||||||
|
|
||||||
|
let answer: [u8; (3, 3)] = [[1, 2, 3],
|
||||||
|
[4, 5, 6],
|
||||||
|
[7, 8, 9]];
|
||||||
|
|
||||||
|
// Runs the Sudoku checker.
|
||||||
|
let result = main(puzzle, answer);
|
||||||
|
|
||||||
|
// Expects the result to be true.
|
||||||
|
console.assert(true == result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests that the `silly-sudoku` circuit outputs false on an incorrect answer.
|
||||||
|
@test
|
||||||
|
function test_solve_fail() {
|
||||||
|
let puzzle: [u8; (3, 3)] = [[0, 2, 0],
|
||||||
|
[0, 0, 6],
|
||||||
|
[0, 8, 0]];
|
||||||
|
|
||||||
|
let answer: [u8; (3, 3)] = [[1, 2, 3],
|
||||||
|
[4, 5, 6],
|
||||||
|
[7, 8, 8]]; // We have an extra `8` in this column!
|
||||||
|
|
||||||
|
// Runs the Sudoku checker.
|
||||||
|
let result = main(puzzle, answer);
|
||||||
|
|
||||||
|
// Expects the result to be false.
|
||||||
|
console.assert(false == result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that the `silly-sudoku` circuit outputs the expected value on a custom test input.
|
||||||
|
@test(test_input)
|
||||||
|
function test_solve_with_input(
|
||||||
|
puzzle: [u8; (3, 3)],
|
||||||
|
answer: [u8; (3, 3)],
|
||||||
|
expected: bool
|
||||||
|
) {
|
||||||
|
// Runs the Sudoku checker.
|
||||||
|
let result = main(puzzle, answer);
|
||||||
|
|
||||||
|
console.log("expected {}, got {}", expected, result);
|
||||||
|
|
||||||
|
console.assert(expected == result);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user