mirror of
https://github.com/ProvableHQ/leo.git
synced 2024-11-22 22:44:47 +03:00
Merge branch 'master' of github.com:AleoHQ/leo into bug/abnf-parser-bugs
This commit is contained in:
commit
d4d122e59e
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
@ -30,8 +30,6 @@ jobs:
|
||||
|
||||
- name: cargo fmt --check
|
||||
uses: actions-rs/cargo@v1
|
||||
env:
|
||||
CARGO_NET_GIT_FETCH_WITH_CLI: true
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
@ -120,7 +118,6 @@ jobs:
|
||||
command: test
|
||||
args: --all --features ci_skip
|
||||
env:
|
||||
CARGO_NET_GIT_FETCH_WITH_CLI: true
|
||||
CARGO_INCREMENTAL: "0"
|
||||
|
||||
- name: Install dependencies for code coverage
|
||||
|
@ -23,7 +23,7 @@ pub struct ConstantFolding<'a, 'b> {
|
||||
}
|
||||
|
||||
impl<'a, 'b> ExpressionVisitor<'a> for ConstantFolding<'a, 'b> {
|
||||
fn visit_expression(&mut self, input: &Cell<&Expression<'a>>) -> VisitResult {
|
||||
fn visit_expression(&mut self, input: &Cell<&'a Expression<'a>>) -> VisitResult {
|
||||
let expr = input.get();
|
||||
if let Some(const_value) = expr.const_value() {
|
||||
let folded_expr = Expression::Constant(Constant {
|
||||
|
@ -14,8 +14,9 @@
|
||||
// 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::{AsgConvertError, IntegerType, Span, Type};
|
||||
use crate::{AsgConvertError, Circuit, Identifier, IntegerType, Span, Type};
|
||||
|
||||
use indexmap::IndexMap;
|
||||
use num_bigint::BigInt;
|
||||
use std::{convert::TryInto, fmt};
|
||||
use tendril::StrTendril;
|
||||
@ -118,8 +119,8 @@ impl From<leo_ast::CharValue> for CharValue {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum ConstValue {
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub enum ConstValue<'a> {
|
||||
Int(ConstInt),
|
||||
Group(GroupValue),
|
||||
Field(BigInt),
|
||||
@ -128,8 +129,9 @@ pub enum ConstValue {
|
||||
Char(CharValue),
|
||||
|
||||
// compounds
|
||||
Tuple(Vec<ConstValue>),
|
||||
Array(Vec<ConstValue>),
|
||||
Tuple(Vec<ConstValue<'a>>),
|
||||
Array(Vec<ConstValue<'a>>),
|
||||
Circuit(&'a Circuit<'a>, IndexMap<String, (Identifier, ConstValue<'a>)>),
|
||||
}
|
||||
|
||||
macro_rules! const_int_op {
|
||||
@ -311,8 +313,8 @@ impl ConstInt {
|
||||
}
|
||||
}
|
||||
|
||||
impl ConstValue {
|
||||
pub fn get_type<'a>(&self) -> Option<Type<'a>> {
|
||||
impl<'a> ConstValue<'a> {
|
||||
pub fn get_type(&'a self) -> Option<Type<'a>> {
|
||||
Some(match self {
|
||||
ConstValue::Int(i) => i.get_type(),
|
||||
ConstValue::Group(_) => Type::Group,
|
||||
@ -324,6 +326,7 @@ impl ConstValue {
|
||||
Type::Tuple(sub_consts.iter().map(|x| x.get_type()).collect::<Option<Vec<Type>>>()?)
|
||||
}
|
||||
ConstValue::Array(values) => Type::Array(Box::new(values.get(0)?.get_type()?), values.len()),
|
||||
ConstValue::Circuit(circuit, _) => Type::Circuit(circuit),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -58,7 +58,7 @@ impl<'a> ExpressionNode<'a> for ArrayAccessExpression<'a> {
|
||||
self.array.get().is_mut_ref()
|
||||
}
|
||||
|
||||
fn const_value(&self) -> Option<ConstValue> {
|
||||
fn const_value(&self) -> Option<ConstValue<'a>> {
|
||||
let mut array = match self.array.get().const_value()? {
|
||||
ConstValue::Array(values) => values,
|
||||
_ => return None,
|
||||
|
@ -53,7 +53,7 @@ impl<'a> ExpressionNode<'a> for ArrayInitExpression<'a> {
|
||||
false
|
||||
}
|
||||
|
||||
fn const_value(&self) -> Option<ConstValue> {
|
||||
fn const_value(&self) -> Option<ConstValue<'a>> {
|
||||
let element = self.element.get().const_value()?;
|
||||
Some(ConstValue::Array(vec![element; self.len]))
|
||||
}
|
||||
|
@ -78,7 +78,7 @@ impl<'a> ExpressionNode<'a> for ArrayInlineExpression<'a> {
|
||||
false
|
||||
}
|
||||
|
||||
fn const_value(&self) -> Option<ConstValue> {
|
||||
fn const_value(&self) -> Option<ConstValue<'a>> {
|
||||
let mut const_values = vec![];
|
||||
for (expr, spread) in self.elements.iter() {
|
||||
if *spread {
|
||||
|
@ -70,7 +70,7 @@ impl<'a> ExpressionNode<'a> for ArrayRangeAccessExpression<'a> {
|
||||
self.array.get().is_mut_ref()
|
||||
}
|
||||
|
||||
fn const_value(&self) -> Option<ConstValue> {
|
||||
fn const_value(&self) -> Option<ConstValue<'a>> {
|
||||
let mut array = match self.array.get().const_value()? {
|
||||
ConstValue::Array(values) => values,
|
||||
_ => return None,
|
||||
@ -176,6 +176,7 @@ impl<'a> FromAst<'a, leo_ast::ArrayRangeAccessExpression> for ArrayRangeAccessEx
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(expected_len) = expected_len {
|
||||
if let Some(length) = length {
|
||||
if length != expected_len {
|
||||
|
@ -83,8 +83,14 @@ impl<'a> ExpressionNode<'a> for CircuitAccessExpression<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn const_value(&self) -> Option<ConstValue> {
|
||||
None
|
||||
fn const_value(&self) -> Option<ConstValue<'a>> {
|
||||
match self.target.get()?.const_value()? {
|
||||
ConstValue::Circuit(_, members) => {
|
||||
let (_, const_value) = members.get(&self.member.name.to_string())?.clone();
|
||||
Some(const_value)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_consty(&self) -> bool {
|
||||
|
@ -70,8 +70,17 @@ impl<'a> ExpressionNode<'a> for CircuitInitExpression<'a> {
|
||||
true
|
||||
}
|
||||
|
||||
fn const_value(&self) -> Option<ConstValue> {
|
||||
None
|
||||
fn const_value(&self) -> Option<ConstValue<'a>> {
|
||||
let mut members = IndexMap::new();
|
||||
for (identifier, member) in self.values.iter() {
|
||||
// insert by name because accessmembers identifiers are different.
|
||||
members.insert(
|
||||
identifier.name.to_string(),
|
||||
(identifier.clone(), member.get().const_value()?),
|
||||
);
|
||||
}
|
||||
// Store circuit as well for get_type.
|
||||
Some(ConstValue::Circuit(self.circuit.get(), members))
|
||||
}
|
||||
|
||||
fn is_consty(&self) -> bool {
|
||||
|
@ -36,7 +36,7 @@ use std::cell::Cell;
|
||||
pub struct Constant<'a> {
|
||||
pub parent: Cell<Option<&'a Expression<'a>>>,
|
||||
pub span: Option<Span>,
|
||||
pub value: ConstValue, // should not be compound constants
|
||||
pub value: ConstValue<'a>, // should not be compound constants
|
||||
}
|
||||
|
||||
impl<'a> Node for Constant<'a> {
|
||||
@ -56,7 +56,7 @@ impl<'a> ExpressionNode<'a> for Constant<'a> {
|
||||
|
||||
fn enforce_parents(&self, _expr: &'a Expression<'a>) {}
|
||||
|
||||
fn get_type(&self) -> Option<Type<'a>> {
|
||||
fn get_type(&'a self) -> Option<Type<'a>> {
|
||||
self.value.get_type()
|
||||
}
|
||||
|
||||
@ -64,7 +64,7 @@ impl<'a> ExpressionNode<'a> for Constant<'a> {
|
||||
false
|
||||
}
|
||||
|
||||
fn const_value(&self) -> Option<ConstValue> {
|
||||
fn const_value(&self) -> Option<ConstValue<'a>> {
|
||||
Some(self.value.clone())
|
||||
}
|
||||
|
||||
@ -267,6 +267,7 @@ impl<'a> Into<leo_ast::ValueExpression> for &Constant<'a> {
|
||||
),
|
||||
ConstValue::Tuple(_) => unimplemented!(),
|
||||
ConstValue::Array(_) => unimplemented!(),
|
||||
ConstValue::Circuit(_, _) => unimplemented!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -124,9 +124,9 @@ pub trait ExpressionNode<'a>: Node {
|
||||
fn get_parent(&self) -> Option<&'a Expression<'a>>;
|
||||
fn enforce_parents(&self, expr: &'a Expression<'a>);
|
||||
|
||||
fn get_type(&self) -> Option<Type<'a>>;
|
||||
fn get_type(&'a self) -> Option<Type<'a>>;
|
||||
fn is_mut_ref(&self) -> bool;
|
||||
fn const_value(&self) -> Option<ConstValue>; // todo: memoize
|
||||
fn const_value(&'a self) -> Option<ConstValue>; // todo: memoize
|
||||
fn is_consty(&self) -> bool;
|
||||
}
|
||||
|
||||
@ -194,7 +194,7 @@ impl<'a> ExpressionNode<'a> for Expression<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_type(&self) -> Option<Type<'a>> {
|
||||
fn get_type(&'a self) -> Option<Type<'a>> {
|
||||
use Expression::*;
|
||||
match self {
|
||||
VariableRef(x) => x.get_type(),
|
||||
@ -236,7 +236,7 @@ impl<'a> ExpressionNode<'a> for Expression<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn const_value(&self) -> Option<ConstValue> {
|
||||
fn const_value(&'a self) -> Option<ConstValue<'a>> {
|
||||
use Expression::*;
|
||||
match self {
|
||||
VariableRef(x) => x.const_value(),
|
||||
|
@ -56,7 +56,7 @@ impl<'a> ExpressionNode<'a> for TernaryExpression<'a> {
|
||||
self.if_true.get().is_mut_ref() && self.if_false.get().is_mut_ref()
|
||||
}
|
||||
|
||||
fn const_value(&self) -> Option<ConstValue> {
|
||||
fn const_value(&self) -> Option<ConstValue<'a>> {
|
||||
if let Some(ConstValue::Boolean(switch)) = self.condition.get().const_value() {
|
||||
if switch {
|
||||
self.if_true.get().const_value()
|
||||
|
@ -56,7 +56,7 @@ impl<'a> ExpressionNode<'a> for TupleAccessExpression<'a> {
|
||||
self.tuple_ref.get().is_mut_ref()
|
||||
}
|
||||
|
||||
fn const_value(&self) -> Option<ConstValue> {
|
||||
fn const_value(&self) -> Option<ConstValue<'a>> {
|
||||
let tuple_const = self.tuple_ref.get().const_value()?;
|
||||
match tuple_const {
|
||||
ConstValue::Tuple(sub_consts) => sub_consts.get(self.index).cloned(),
|
||||
|
@ -58,7 +58,7 @@ impl<'a> ExpressionNode<'a> for TupleInitExpression<'a> {
|
||||
false
|
||||
}
|
||||
|
||||
fn const_value(&self) -> Option<ConstValue> {
|
||||
fn const_value(&self) -> Option<ConstValue<'a>> {
|
||||
let mut consts = vec![];
|
||||
for element in self.elements.iter() {
|
||||
if let Some(const_value) = element.get().const_value() {
|
||||
|
@ -66,7 +66,7 @@ impl<'a> ExpressionNode<'a> for VariableRef<'a> {
|
||||
}
|
||||
|
||||
// todo: we can use use hacky ssa here to catch more cases, or just enforce ssa before asg generation finished
|
||||
fn const_value(&self) -> Option<ConstValue> {
|
||||
fn const_value(&self) -> Option<ConstValue<'a>> {
|
||||
let variable = self.variable.borrow();
|
||||
if variable.mutable || variable.assignments.len() != 1 {
|
||||
return None;
|
||||
|
@ -23,7 +23,7 @@ use crate::{
|
||||
program::ConstrainedProgram,
|
||||
relational::*,
|
||||
resolve_core_circuit,
|
||||
value::{Address, Char, CharType, ConstrainedValue, Integer},
|
||||
value::{Address, Char, CharType, ConstrainedCircuitMember, ConstrainedValue, Integer},
|
||||
FieldType,
|
||||
GroupType,
|
||||
};
|
||||
@ -37,7 +37,7 @@ impl<'a, F: PrimeField, G: GroupType<F>> ConstrainedProgram<'a, F, G> {
|
||||
pub(crate) fn enforce_const_value<CS: ConstraintSystem<F>>(
|
||||
&mut self,
|
||||
cs: &mut CS,
|
||||
value: &ConstValue,
|
||||
value: &'a ConstValue<'a>,
|
||||
span: &Span,
|
||||
) -> Result<ConstrainedValue<'a, F, G>, ExpressionError> {
|
||||
Ok(match value {
|
||||
@ -75,6 +75,17 @@ impl<'a, F: PrimeField, G: GroupType<F>> ConstrainedProgram<'a, F, G> {
|
||||
.map(|x| self.enforce_const_value(cs, x, span))
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
),
|
||||
ConstValue::Circuit(circuit, members) => {
|
||||
let mut constrained_members = Vec::new();
|
||||
for (_, (identifier, member)) in members.iter() {
|
||||
constrained_members.push(ConstrainedCircuitMember(
|
||||
identifier.clone(),
|
||||
self.enforce_const_value(cs, member, span)?,
|
||||
));
|
||||
}
|
||||
|
||||
ConstrainedValue::CircuitExpression(circuit, constrained_members)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -19,12 +19,9 @@ DRAFT
|
||||
|
||||
This proposal provides support for casts among integer types in Leo.
|
||||
The syntax is similar to Rust.
|
||||
Two possible semantics are discussed:
|
||||
_value-preserving casts_,
|
||||
which just serve to change types
|
||||
but cause errors when values are not representable in the new types;
|
||||
and _values-changing casts_,
|
||||
which never cause errors but may change the mathematical values.
|
||||
The semantics is _value-preserving_,
|
||||
i.e. the casts just serve to change types
|
||||
but cause errors when the mathematical values are not representable in the new types.
|
||||
|
||||
# Motivation
|
||||
|
||||
@ -33,7 +30,7 @@ arithmetic integer operations require operands of the same type
|
||||
and return results of the same type.
|
||||
There are no implicit or explicit ways to turn, for example,
|
||||
a `u8` into a `u16`, even though
|
||||
every non-negative integers that fits in 8 bits also fits in 16 bits.
|
||||
every non-negative integer that fits in 8 bits also fits in 16 bits.
|
||||
However, the ability to convert values between different (integer) types
|
||||
is a useful feature that is normally found in programming languages.
|
||||
|
||||
@ -68,7 +65,7 @@ The proposed syntax is
|
||||
```
|
||||
where `<expression>` must have an integer type.
|
||||
|
||||
The ABNF grammar is modified as follows:
|
||||
The ABNF grammar of Leo is modified as follows:
|
||||
```
|
||||
; add this rule:
|
||||
cast-expression = unary-expression
|
||||
@ -112,21 +109,33 @@ but the value in question is in the intersection of the two ranges.
|
||||
|
||||
When the mathematical integer value of the expression
|
||||
is not representable in the type that the expression is cast to,
|
||||
there are two possible approaches, discussed below.
|
||||
there are two possible approaches:
|
||||
_value-preserving casts_,
|
||||
which just serve to change types
|
||||
but cause errors when values are not representable in the new types;
|
||||
and _values-changing casts_,
|
||||
which never cause errors but may change the mathematical values.
|
||||
|
||||
### Value-Preserving Casts
|
||||
Based on discussion and consensus within the Leo team,
|
||||
this RFC proposes value-preserving casts;
|
||||
value-changing casts are discussed in the 'Alternatives' section,
|
||||
for completeness.
|
||||
|
||||
The first approach is to deem that situation erroneous.
|
||||
That is, to require casts to always preserve the mathematical integer values.
|
||||
With value-preserving casts,
|
||||
when the mathematical integer value of the expression
|
||||
is not representable in the type that the expression is cast to,
|
||||
it is an error.
|
||||
That is, we require casts to always preserve the mathematical integer values.
|
||||
Recall that all inputs are known at compile time in Leo,
|
||||
so these checks can be performed easily.
|
||||
|
||||
In this approach, casts only serve to change types, never values.
|
||||
Thus integer casts only serve to change types, never values.
|
||||
When values are to be changed, separate (built-in) functions can be used,
|
||||
e.g. to mask bits and achieve the same effect as
|
||||
the value-changing casts discussed below.
|
||||
|
||||
From a point of view, this approach seems to match Leo's
|
||||
treatment of potentially erroneous situations like integer overflows:
|
||||
the principle is that developers should explicitly use
|
||||
This approach Leo's treatment of potentially erroneous situations like integer overflows.
|
||||
The principle is that developers should explicitly use
|
||||
operations that may overflow if that is their intention,
|
||||
rather than having those situation possibly occur unexpectedly.
|
||||
|
||||
@ -159,9 +168,32 @@ let r_low16: u32 = r & 0xFFFF; // assuming we have bitwise ops and hex literals
|
||||
let s: u16 = r_low16 as u16; // no value change here
|
||||
```
|
||||
|
||||
### Value-Changing Casts
|
||||
## Compilation to R1CS
|
||||
|
||||
The second approach is the following:
|
||||
It may be more efficient (in terms of number of R1CS constraints)
|
||||
to compile Leo casts as if they had a value-changing semantics.
|
||||
If the R1CS constraints represent Leo integers as bits,
|
||||
the bits of the new value can be determined from the bits of the old value,
|
||||
with additional zero or sign extension bits when needed
|
||||
(see the details of the value-changing semantics in the 'Alternatives' section).
|
||||
There is no need to add checks to the R1CS constraints
|
||||
because the compiler ensures that the cast values do not actually change given the known inputs,
|
||||
and therefore the value-changing and value-preserving semantics are equivalent on the known inputs.
|
||||
The idea is that the R1CS constraints can have a "don't care" behavior on inputs that cause errors in Leo.
|
||||
|
||||
# Drawbacks
|
||||
|
||||
This proposal does not appear to bring any drawbacks,
|
||||
other than making the language and compiler inevitably more complex.
|
||||
But the benefits to support type casts justifies the extra complexity.
|
||||
|
||||
# Effect on Ecosystem
|
||||
|
||||
This proposal does not appear to have any direct effects on the ecosystem.
|
||||
|
||||
# Alternatives
|
||||
|
||||
As mentioned above, an alternative semantics for casts is value-changing:
|
||||
1. `uN` to `uM` with `N < M`: just change type of value.
|
||||
2. `uN` to `uM` with `N > M`: take low `M` bits of value.
|
||||
3. `iN` to `iM` with `N < M`: just change type of value.
|
||||
@ -185,7 +217,7 @@ considerations that apply to typical programming languages
|
||||
do not necessarily apply to Leo.
|
||||
|
||||
Back to the somewhat abstract example in the section on value-preserving casts,
|
||||
with value-changing casts, the expectation that the final result fits in `u16`
|
||||
note that, with value-changing casts, the expectation that the final result fits in `u16`
|
||||
would have to be checked with explicit code:
|
||||
```
|
||||
... // some computations on u32 values, which could not be done with u16
|
||||
@ -197,35 +229,3 @@ let s: u16 = r as u16; // could change value in principle, but does not here
|
||||
```
|
||||
However, it would be easy for a developer to neglect to add the checking code,
|
||||
and thus have the Leo code silently produce an unexpected result.
|
||||
|
||||
## Compilation to R1CS
|
||||
|
||||
It should be possible to compile Leo casts to the same R1CS constraints
|
||||
whether we choose the value-preserving or value-changing semantics.
|
||||
If the R1CS constraints represent Leo integers as bits,
|
||||
the bits of the new value can be determined from the bits of the old value,
|
||||
with additional zero or sign extension bits when needed.
|
||||
This is clearly the value-changing behavior.
|
||||
With the value-preserving behavior,
|
||||
all casts for the known inputs are checked,
|
||||
and thus we value-changing behavior coincides
|
||||
with the value-preserving behavior if the checks succeed.
|
||||
Thus, if he behavior of the R1CS constraints is "don't care"
|
||||
for Leo program inputs that cause errors (such as cast errors),
|
||||
the compilation strategy for value-changing casts
|
||||
should be also adequate for value-preserving casts.
|
||||
|
||||
# Drawbacks
|
||||
|
||||
This proposal does not appear to bring any drawbacks,
|
||||
other than making the language and compiler inevitably more complex.
|
||||
But the benefits to support type casts justifies the extra complexity.
|
||||
|
||||
# Effect on Ecosystem
|
||||
|
||||
This proposal does not appear to have any direct effects on the ecosystem.
|
||||
|
||||
# Alternatives
|
||||
|
||||
The 'Design' section above already discusses two alternative semantics.
|
||||
After we settle on one, the other one could be mentioned in this section.
|
||||
|
135
docs/rfc/005-countdown-loops.md
Normal file
135
docs/rfc/005-countdown-loops.md
Normal file
@ -0,0 +1,135 @@
|
||||
# Leo RFC 005: Countdown Loops
|
||||
|
||||
## Authors
|
||||
|
||||
- Max Bruce
|
||||
- Collin Chin
|
||||
- Alessandro Coglio
|
||||
- Eric McCarthy
|
||||
- Jon Pavlik
|
||||
- Damir Shamanaev
|
||||
- Damon Sicore
|
||||
- Howard Wu
|
||||
|
||||
## Status
|
||||
|
||||
DRAFT
|
||||
|
||||
# Summary
|
||||
|
||||
This proposal suggests adding countdown loops and inclusive loop ranges into Leo language.
|
||||
|
||||
# Motivation
|
||||
|
||||
In the current design of the language only incremental ranges are allowed. Though
|
||||
in some cases there's a need for loops going in the reverse direction. This example
|
||||
demonstrates the shaker sort algorithm where countdown loops are mocked:
|
||||
|
||||
```ts
|
||||
function shaker_sort(a: [u32; 10], const rounds: u32) -> [u32; 10] {
|
||||
for k in 0..rounds {
|
||||
for i in 0..9 {
|
||||
if a[i] > a[i + 1] {
|
||||
let tmp = a[i];
|
||||
a[i] = a[i + 1];
|
||||
a[i + 1] = tmp;
|
||||
}
|
||||
|
||||
}
|
||||
for j in 0..9 { // j goes from 0 to 8
|
||||
let i = 8 - j; // j is flipped
|
||||
if a[i] > a[i + 1] {
|
||||
let tmp = a[i];
|
||||
a[i] = a[i + 1];
|
||||
a[i + 1] = tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
return a;
|
||||
}
|
||||
```
|
||||
|
||||
Having a countdown loop in the example above could improve readability and
|
||||
usability of the language by making it more natural to the developer.
|
||||
|
||||
However, if we imagined this example using a countdown loop, we would see that
|
||||
it wouldn't be possible to count to 0; because the first bound of the range is
|
||||
inclusive and the second is exclusive, and loops ranges must use only unsigned integers.
|
||||
|
||||
```ts
|
||||
// loop goes 0,1,2,3,4,5,6,7,8
|
||||
for i in 0..9 { /* ... */ }
|
||||
|
||||
// loop goes 9,8,7,6,5,4,3,2,1
|
||||
for i in 9..0 { /* ... */ }
|
||||
```
|
||||
|
||||
Hence direct implementation of the coundown loop ranges would create asymmetry (1)
|
||||
and would not allow loops to count down to 0 (2). To implement coundown loops and
|
||||
solve these two problems we suggest adding an inclusive range bounds.
|
||||
|
||||
# Design
|
||||
|
||||
## Coundown loops
|
||||
|
||||
Countdown ranges do not need any changes to the existing syntax. However their
|
||||
functionality needs to be implemented in the compiler.
|
||||
|
||||
```ts
|
||||
for i in 5..0 {}
|
||||
```
|
||||
|
||||
## Inclusive ranges
|
||||
|
||||
To solve loop asymmetry and to improve loop ranges in general we suggest adding
|
||||
inclusive range operator to Leo. Inclusive range would extend the second bound
|
||||
of the loop making it inclusive (instead of default - exclusive)
|
||||
therefore allowing countdown loops to reach 0 value.
|
||||
|
||||
```ts
|
||||
// default loop: 0,1,2,3,4
|
||||
for i in 0..5 {}
|
||||
|
||||
// inclusive range: 0,1,2,3,4,5
|
||||
for i in 0..=5 {}
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
The code example demostrated in the Motivation part of this document
|
||||
could be extended (or simplified) with the suggested syntax:
|
||||
|
||||
```ts
|
||||
function shaker_sort(a: [u32; 10], const rounds: u32) -> [u32; 10] {
|
||||
for k in 0..rounds {
|
||||
for i in 0..9 { // i goes from 0 to 8
|
||||
if a[i] > a[i + 1] {
|
||||
let tmp = a[i];
|
||||
a[i] = a[i + 1];
|
||||
a[i + 1] = tmp;
|
||||
}
|
||||
|
||||
}
|
||||
for i in 8..=0 { // i goes from 8 to 0
|
||||
if a[i] > a[i + 1] {
|
||||
let tmp = a[i];
|
||||
a[i] = a[i + 1];
|
||||
a[i + 1] = tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
return a;
|
||||
}
|
||||
```
|
||||
|
||||
# Drawbacks
|
||||
|
||||
-
|
||||
|
||||
# Effect on Ecosystem
|
||||
|
||||
Suggested change should have no effect on ecosystem because of its backward compatibility.
|
||||
|
||||
# Alternatives
|
||||
|
||||
Coundown loops can be mocked manually.
|
@ -5,6 +5,10 @@ input_file:
|
||||
- input/complex_access.in
|
||||
*/
|
||||
|
||||
circuit Circ {
|
||||
f: u32
|
||||
}
|
||||
|
||||
function main (a: [u8; 8], b: u32, c: [[u8; 3]; 3], d: [(u8, u32); 1], e: [u8; (3, 4)] ) -> bool {
|
||||
a[0..3][b] = 93;
|
||||
a[2..6][1] = 87;
|
||||
@ -16,6 +20,7 @@ function main (a: [u8; 8], b: u32, c: [[u8; 3]; 3], d: [(u8, u32); 1], e: [u8; (
|
||||
c[0..2][0] = [1u8; 3];
|
||||
c[1..][1][1..2][0] = 126;
|
||||
c[1..][0] = [42, 43, 44];
|
||||
c[Circ {f: 0}.f..1][0][0] += 2;
|
||||
|
||||
d[..1][0].1 = 1;
|
||||
|
||||
@ -24,7 +29,7 @@ function main (a: [u8; 8], b: u32, c: [[u8; 3]; 3], d: [(u8, u32); 1], e: [u8; (
|
||||
|
||||
return
|
||||
a == [200u8, 93, 42, 174, 5, 6, 43, 8]
|
||||
&& c == [[1u8, 1, 1], [42, 43, 44], [7, 126, 9]]
|
||||
&& c == [[3u8, 1, 1], [42, 43, 44], [7, 126, 9]]
|
||||
&& d == [(0u8, 1u32)]
|
||||
&& e == [[33u8, 22, 22, 22], [0, 0, 0, 0], [0, 0, 0, 0]];
|
||||
}
|
Loading…
Reference in New Issue
Block a user