Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Arrays of anything #434

Merged
merged 44 commits into from
Sep 23, 2019
Merged
Show file tree
Hide file tree
Changes from 40 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
be37507
generalize arrays. wip, basic boolean array mvp
Schaeff Jun 21, 2019
8b431f0
Merge branch 'add-boolean-constants' of github.com:Zokrates/ZoKrates …
Schaeff Jun 22, 2019
dfabfa0
Merge branch 'add-boolean-propagation' of github.com:Zokrates/ZoKrate…
Schaeff Jun 22, 2019
17f9473
make arrays generic over types. wip
Schaeff Jun 22, 2019
c7c20c4
Merge branch 'optimize-zero-quadratic-comb' into generalize-arrays
Schaeff Jun 22, 2019
bd25cac
add example
Schaeff Jun 22, 2019
efc46e7
make tests pass
Schaeff Jun 25, 2019
3ba608c
create optimizer structure
Schaeff Jun 25, 2019
ecac773
implement deduplication
Schaeff Jun 25, 2019
011ae45
Merge branch 'develop' of github.com:Zokrates/ZoKrates into duplicate…
Schaeff Jun 25, 2019
3c093fb
Merge branch 'duplicate-optimizer' of github.com:Zokrates/ZoKrates in…
Schaeff Jun 25, 2019
003bc5e
remove core lib, reimplement if_else manually in flattening
Schaeff Jul 1, 2019
51a8cf1
revert example
Schaeff Jul 1, 2019
8291612
Merge branch 'develop' of github.com:Zokrates/ZoKrates into remove-co…
Schaeff Jul 1, 2019
0f74b49
Merge branch 'develop' of github.com:Zokrates/ZoKrates into generaliz…
Schaeff Jul 10, 2019
84b35ce
deduplicate code using Flatten trait
Schaeff Jul 10, 2019
574f1f1
fix bound
Schaeff Jul 10, 2019
f6bae35
remove unused
Schaeff Jul 10, 2019
2cd3943
remove unimplemented
Schaeff Jul 10, 2019
6e6d90f
make flatten_array_expr generic over inner type
Schaeff Jul 11, 2019
e89d60b
make if_else generic
Schaeff Jul 11, 2019
34f41dc
merge modules
Schaeff Jul 15, 2019
89917c0
fully recursive arrays
Schaeff Jul 16, 2019
b2fcb4c
fully recursive arrays
Schaeff Jul 16, 2019
3e6fb14
Merge branch 'rec-arrays' of github.com:Zokrates/ZoKrates into rec-ar…
Schaeff Jul 16, 2019
5be2d2c
remove example
Schaeff Jul 16, 2019
3fc3408
fix equality for wasm solvers
Schaeff Jul 16, 2019
22619be
Merge branch 'generalize-arrays' of github.com:Zokrates/ZoKrates into…
Schaeff Jul 16, 2019
2e17229
add tests
Schaeff Jul 16, 2019
5a2860c
merge dev
Schaeff Aug 26, 2019
acbb0d6
implement spreads for any type
Schaeff Aug 26, 2019
b901055
introduce annotate function to restrict access to Array fields
Schaeff Aug 27, 2019
a634b1c
simplify pest ast, add tests for from_ast
Schaeff Aug 28, 2019
52f33f0
implement unimplemented rec array update and boolean ifelse
Schaeff Aug 29, 2019
7ad2e89
add unroll test for rec array, extract IfElse and Select from Flatten…
Schaeff Aug 29, 2019
05b5939
remove unreachable parts of flattening, make sure we crash on multidi…
Schaeff Aug 29, 2019
ca6d92b
implement multidim updates
Schaeff Sep 9, 2019
5b4f581
add integration test
Schaeff Sep 9, 2019
c8080e9
small tweaks, simplify and improve propagation
Schaeff Sep 9, 2019
3b22838
add comments
Schaeff Sep 11, 2019
b17f34e
Documentation on extended array types
JacobEberhardt Sep 17, 2019
c49a97c
reverse array declaration dimensions, fix tests
Schaeff Sep 18, 2019
57afbb0
Merge branch 'develop' of github.com:Zokrates/ZoKrates into rec-arrays
Schaeff Sep 23, 2019
805b429
address review comments
Schaeff Sep 23, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions zokrates_cli/examples/arrays/boolean_array.code
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
def main(bool[3] a) -> (field[3]):
bool[3] c = [true, true || false, true]
a[1] = true || a[2]
a[2] = a[0]
field[3] result = [0; 3]
for field i in 0..3 do
result[i] = if a[i] then 33 else 0 fi
endfor
return result
12 changes: 12 additions & 0 deletions zokrates_cli/examples/arrays/cube.code
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
def main(field[2][2][2] cube) -> (field):
field res = 0

for field i in 0..2 do
for field j in 0..2 do
for field k in 0..2 do
res = res + cube[i][j][k]
endfor
endfor
endfor

return res
4 changes: 4 additions & 0 deletions zokrates_cli/examples/arrays/multidim_update.code
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
def main(field[10][10][10] a, field i, field j, field k) -> (field[3]):
a[i][j][k] = 42
field[3][3] b = [[1, 2, 3], [1, 2, 3], [1, 2, 3]]
return b[0]
10 changes: 10 additions & 0 deletions zokrates_cli/examples/arrays/value.code
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
def main() -> ():
field[3] a = [1, 2, 3]
bool[3] b = [true, true, false]
field[2][3] c = [[1, 2], [3, 4], [5, 6]]

field[3] aa = [...a]
bool[3] bb = [...b]
field[2][3] cc = [...c]

return
6 changes: 6 additions & 0 deletions zokrates_cli/tests/code/multidim_update.arguments.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[
0,
0,
0,
0
]
3 changes: 3 additions & 0 deletions zokrates_cli/tests/code/multidim_update.code
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
def main(field[2][2] a) -> (field[2][2]):
a[1][1] = 42
return a
4 changes: 4 additions & 0 deletions zokrates_cli/tests/code/multidim_update.expected.witness
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
~out_0 0
~out_1 0
~out_2 0
~out_3 42
286 changes: 241 additions & 45 deletions zokrates_core/src/absy/from_ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -448,22 +448,30 @@ impl<'ast, T: Field> From<pest::PostfixExpression<'ast>> for absy::ExpressionNod
fn from(expression: pest::PostfixExpression<'ast>) -> absy::ExpressionNode<'ast, T> {
use absy::NodeValue;

assert!(expression.access.len() == 1); // we only allow a single access: function call or array access

match expression.access[0].clone() {
pest::Access::Call(a) => absy::Expression::FunctionCall(
&expression.id.span.as_str(),
a.expressions
.into_iter()
.map(|e| absy::ExpressionNode::from(e))
.collect(),
),
pest::Access::Select(a) => absy::Expression::Select(
box absy::ExpressionNode::from(expression.id),
box absy::RangeOrExpression::from(a.expression),
),
}
.span(expression.span)
let id_str = expression.id.span.as_str();
let id = absy::ExpressionNode::from(expression.id);

// pest::PostFixExpression contains an array of "accesses": `a(34)[42]` is represented as `[a, [Call(34), Select(42)]]`, but absy::ExpressionNode
// is recursive, so it is `Select(Call(a, 34), 42)`. We apply this transformation here

// we start with the id, and we fold the array of accesses by wrapping the current value
expression.accesses.into_iter().fold(id, |acc, a| match a {
pest::Access::Call(a) => match acc.value {
absy::Expression::Identifier(_) => absy::Expression::FunctionCall(
&id_str,
a.expressions
.into_iter()
.map(|e| absy::ExpressionNode::from(e))
.collect(),
),
e => unimplemented!("only identifiers are callable, found \"{}\"", e),
}
.span(a.span),
pest::Access::Select(a) => {
absy::Expression::Select(box acc, box absy::RangeOrExpression::from(a.expression))
.span(a.span)
}
})
}
}

Expand Down Expand Up @@ -501,15 +509,15 @@ impl<'ast, T: Field> From<pest::Assignee<'ast>> for absy::AssigneeNode<'ast, T>
use absy::NodeValue;

let a = absy::AssigneeNode::from(assignee.id);
match assignee.indices.len() {
0 => a,
1 => absy::Assignee::ArrayElement(
box a,
box absy::RangeOrExpression::from(assignee.indices[0].clone()),
)
.span(assignee.span),
n => unimplemented!("Array should have one dimension, found {} in {}", n, a),
}
let span = assignee.span;

assignee
.indices
.into_iter()
.map(|i| absy::RangeOrExpression::from(i))
.fold(a, |acc, s| {
absy::Assignee::Select(box acc, box s).span(span.clone())
})
}
}

Expand All @@ -521,27 +529,33 @@ impl<'ast> From<pest::Type<'ast>> for Type {
pest::BasicType::Boolean(_) => Type::Boolean,
},
pest::Type::Array(t) => {
let size = match t.size {
pest::Expression::Constant(c) => match c {
pest::ConstantExpression::DecimalNumber(n) => {
str::parse::<usize>(&n.value).unwrap()
}
_ => unimplemented!(
"Array size should be a decimal number, found {}",
c.span().as_str()
),
},
e => {
unimplemented!("Array size should be constant, found {}", e.span().as_str())
}
let inner_type = match t.ty {
pest::BasicType::Field(_) => Type::FieldElement,
pest::BasicType::Boolean(_) => Type::Boolean,
};
match t.ty {
pest::BasicType::Field(_) => Type::FieldElementArray(size),
_ => unimplemented!(
"Array elements should be field elements, found {}",
t.span.as_str()
),
}

t.dimensions
.into_iter()
.map(|s| match s {
pest::Expression::Constant(c) => match c {
pest::ConstantExpression::DecimalNumber(n) => {
str::parse::<usize>(&n.value).unwrap()
}
_ => unimplemented!(
"Array size should be a decimal number, found {}",
c.span().as_str()
),
},
e => unimplemented!(
"Array size should be constant, found {}",
e.span().as_str()
),
})
.fold(None, |acc, s| match acc {
None => Some(Type::array(inner_type.clone(), s)),
Some(acc) => Some(Type::array(acc, s)),
})
.unwrap()
}
}
}
Expand Down Expand Up @@ -658,4 +672,186 @@ mod tests {

assert_eq!(absy::Module::<FieldPrime>::from(ast), expected);
}

mod types {
use super::*;

/// Helper method to generate the ast for `def main(private {ty} a) -> (): return` which we use to check ty
fn wrap(ty: types::Type) -> absy::Module<'static, FieldPrime> {
absy::Module {
functions: vec![absy::FunctionDeclaration {
id: "main",
symbol: absy::FunctionSymbol::Here(
absy::Function {
arguments: vec![absy::Parameter::private(
absy::Variable::new("a", ty.clone()).into(),
)
.into()],
statements: vec![absy::Statement::Return(
absy::ExpressionList {
expressions: vec![],
}
.into(),
)
.into()],
signature: absy::Signature::new().inputs(vec![ty]),
}
.into(),
),
}
.into()],
imports: vec![],
}
}

#[test]
fn array() {
let vectors = vec![
("field", types::Type::FieldElement),
("bool", types::Type::Boolean),
(
"field[2]",
types::Type::Array(box types::Type::FieldElement, 2),
),
(
"field[2][3]",
types::Type::Array(box Type::Array(box types::Type::FieldElement, 2), 3),
),
(
"bool[2][3]",
types::Type::Array(box Type::Array(box types::Type::Boolean, 2), 3),
),
];

for (ty, expected) in vectors {
let source = format!("def main(private {} a) -> (): return", ty);
let expected = wrap(expected);
let ast = pest::generate_ast(&source).unwrap();
assert_eq!(absy::Module::<FieldPrime>::from(ast), expected);
}
}
}

mod postfix {
use super::*;
fn wrap(expression: absy::Expression<'static, FieldPrime>) -> absy::Module<FieldPrime> {
absy::Module {
functions: vec![absy::FunctionDeclaration {
id: "main",
symbol: absy::FunctionSymbol::Here(
absy::Function {
arguments: vec![],
statements: vec![absy::Statement::Return(
absy::ExpressionList {
expressions: vec![expression.into()],
}
.into(),
)
.into()],
signature: absy::Signature::new(),
}
.into(),
),
}
.into()],
imports: vec![],
}
}

#[test]
fn success() {
// we basically accept `()?[]*` : an optional call at first, then only array accesses

let vectors = vec![
("a", absy::Expression::Identifier("a").into()),
(
"a[3]",
absy::Expression::Select(
box absy::Expression::Identifier("a").into(),
box absy::RangeOrExpression::Expression(
absy::Expression::FieldConstant(FieldPrime::from(3)).into(),
)
.into(),
),
),
(
"a[3][4]",
absy::Expression::Select(
box absy::Expression::Select(
box absy::Expression::Identifier("a").into(),
box absy::RangeOrExpression::Expression(
absy::Expression::FieldConstant(FieldPrime::from(3)).into(),
)
.into(),
)
.into(),
box absy::RangeOrExpression::Expression(
absy::Expression::FieldConstant(FieldPrime::from(4)).into(),
)
.into(),
),
),
(
"a(3)[4]",
absy::Expression::Select(
box absy::Expression::FunctionCall(
"a",
vec![absy::Expression::FieldConstant(FieldPrime::from(3)).into()],
)
.into(),
box absy::RangeOrExpression::Expression(
absy::Expression::FieldConstant(FieldPrime::from(4)).into(),
)
.into(),
),
),
(
"a(3)[4][5]",
absy::Expression::Select(
box absy::Expression::Select(
box absy::Expression::FunctionCall(
"a",
vec![absy::Expression::FieldConstant(FieldPrime::from(3)).into()],
)
.into(),
box absy::RangeOrExpression::Expression(
absy::Expression::FieldConstant(FieldPrime::from(4)).into(),
)
.into(),
)
.into(),
box absy::RangeOrExpression::Expression(
absy::Expression::FieldConstant(FieldPrime::from(5)).into(),
)
.into(),
),
),
];

for (source, expected) in vectors {
let source = format!("def main() -> (): return {}", source);
let expected = wrap(expected);
let ast = pest::generate_ast(&source).unwrap();
assert_eq!(absy::Module::<FieldPrime>::from(ast), expected);
}
}

#[test]
#[should_panic]
fn call_array_element() {
// a call after an array access should be rejected
let source = "def main() -> (): return a[2](3)";
let ast = pest::generate_ast(&source).unwrap();
absy::Module::<FieldPrime>::from(ast);
}

#[test]
#[should_panic]
fn call_call_result() {
// a call after a call should be rejected
let source = "def main() -> (): return a(2)(3)";
let ast = pest::generate_ast(&source).unwrap();
absy::Module::<FieldPrime>::from(ast);
}
}
}
Loading