diff --git a/zokrates_book/src/language/control_flow.md b/zokrates_book/src/language/control_flow.md index 9e47d289..38a57465 100644 --- a/zokrates_book/src/language/control_flow.md +++ b/zokrates_book/src/language/control_flow.md @@ -26,6 +26,12 @@ An if-expression allows you to branch your code depending on a boolean condition {{#include ../../../zokrates_cli/examples/book/if_else.zok}} ``` +The conditional expression can also be written using a ternary operator: + +```zokrates +{{#include ../../../zokrates_cli/examples/book/ternary.zok}} +``` + There are two important caveats when it comes to conditional expressions. Before we go into them, let's define two concepts: - for an execution of the program, *an executed branch* is a branch which has to be paid for when executing the program, generating proofs, etc. - for an execution of the program, *a logically executed branch* is a branch which is "chosen" by the condition of an if-expression. This is the more intuitive notion of execution, and there is only one for each if-expression. diff --git a/zokrates_cli/examples/book/ternary.zok b/zokrates_cli/examples/book/ternary.zok new file mode 100644 index 00000000..c90ecc5c --- /dev/null +++ b/zokrates_cli/examples/book/ternary.zok @@ -0,0 +1,3 @@ +def main(field x) -> field: + field y = x + 2 == 3 ? 1 : 5 + return y \ No newline at end of file diff --git a/zokrates_core/src/semantics.rs b/zokrates_core/src/semantics.rs index 937fbf17..3864aa5e 100644 --- a/zokrates_core/src/semantics.rs +++ b/zokrates_core/src/semantics.rs @@ -2225,7 +2225,7 @@ impl<'ast, T: Field> Checker<'ast, T> { ) .map_err(|(e1, e2)| ErrorInner { pos: Some(pos), - message: format!("{{consequence}} and {{alternative}} in `if/else` expression should have the same type, found {}, {}", e1.get_type(), e2.get_type()), + message: format!("{{consequence}} and {{alternative}} in conditional expression should have the same type, found {}, {}", e1.get_type(), e2.get_type()), })?; match condition_checked { @@ -2251,14 +2251,14 @@ impl<'ast, T: Field> Checker<'ast, T> { }, (c, a) => Err(ErrorInner { pos: Some(pos), - message: format!("{{consequence}} and {{alternative}} in `if/else` expression should have the same type, found {}, {}", c.get_type(), a.get_type()) + message: format!("{{consequence}} and {{alternative}} in conditional expression should have the same type, found {}, {}", c.get_type(), a.get_type()) }) } } c => Err(ErrorInner { pos: Some(pos), message: format!( - "{{condition}} after `if` should be a boolean, found {}", + "{{condition}} should be a boolean, found {}", c.get_type() ), }), diff --git a/zokrates_core_test/tests/tests/ternary.json b/zokrates_core_test/tests/tests/ternary.json new file mode 100644 index 00000000..3b23ef5a --- /dev/null +++ b/zokrates_core_test/tests/tests/ternary.json @@ -0,0 +1,36 @@ +{ + "entry_point": "./tests/tests/ternary.zok", + "max_constraint_count": 4, + "tests": [ + { + "input": { + "values": ["1", "0"] + }, + "output": { + "Ok": { + "values": ["1"] + } + } + }, + { + "input": { + "values": ["0", "1"] + }, + "output": { + "Ok": { + "values": ["2"] + } + } + }, + { + "input": { + "values": ["0", "0"] + }, + "output": { + "Ok": { + "values": ["3"] + } + } + } + ] +} diff --git a/zokrates_core_test/tests/tests/ternary.zok b/zokrates_core_test/tests/tests/ternary.zok new file mode 100644 index 00000000..a9b8cb57 --- /dev/null +++ b/zokrates_core_test/tests/tests/ternary.zok @@ -0,0 +1,2 @@ +def main(bool a, bool b) -> field: + return a ? 1 : b ? 2 : 3 // (a ? 1 : (b ? 2 : 3)) \ No newline at end of file diff --git a/zokrates_parser/src/zokrates.pest b/zokrates_parser/src/zokrates.pest index ce7499e7..108ee5cf 100644 --- a/zokrates_parser/src/zokrates.pest +++ b/zokrates_parser/src/zokrates.pest @@ -154,8 +154,10 @@ op_neg = {"-"} op_pos = {"+"} op_left_shift = @{"<<"} op_right_shift = @{">>"} +op_ternary = {"?" ~ expression ~ ":"} + // `op_pow` is *not* in `op_binary` because its precedence is handled in this parser rather than down the line in precedence climbing -op_binary = _ { op_or | op_and | op_bit_xor | op_bit_and | op_bit_or | op_left_shift | op_right_shift | op_equal | op_not_equal | op_lte | op_lt | op_gte | op_gt | op_add | op_sub | op_mul | op_div | op_rem } +op_binary = _ { op_or | op_and | op_bit_xor | op_bit_and | op_bit_or | op_left_shift | op_right_shift | op_equal | op_not_equal | op_lte | op_lt | op_gte | op_gt | op_add | op_sub | op_mul | op_div | op_rem | op_ternary } op_unary = { op_pos | op_neg | op_not } WHITESPACE = _{ " " | "\t" | "\\" ~ COMMENT? ~ NEWLINE} diff --git a/zokrates_pest_ast/src/lib.rs b/zokrates_pest_ast/src/lib.rs index 22b4d0d7..b2089fae 100644 --- a/zokrates_pest_ast/src/lib.rs +++ b/zokrates_pest_ast/src/lib.rs @@ -38,6 +38,7 @@ mod ast { // based on https://docs.python.org/3/reference/expressions.html#operator-precedence fn build_precedence_climber() -> PrecClimber { PrecClimber::new(vec![ + Operator::new(Rule::op_ternary, Assoc::Right), Operator::new(Rule::op_or, Assoc::Left), Operator::new(Rule::op_and, Assoc::Left), Operator::new(Rule::op_lt, Assoc::Left) @@ -89,6 +90,12 @@ mod ast { Rule::op_bit_or => Expression::binary(BinaryOperator::BitOr, lhs, rhs, span), Rule::op_right_shift => Expression::binary(BinaryOperator::RightShift, lhs, rhs, span), Rule::op_left_shift => Expression::binary(BinaryOperator::LeftShift, lhs, rhs, span), + Rule::op_ternary => dbg!(Expression::ternary( + lhs, + Box::new(Expression::from_pest(&mut pair.into_inner()).unwrap()), + rhs, + span, + )), _ => unreachable!(), }) }