Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 11 additions & 0 deletions compiler/ast/src/expressions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ pub use async_::*;
mod array;
pub use array::*;

mod slice;
pub use slice::*;

mod binary;
pub use binary::*;

Expand Down Expand Up @@ -90,6 +93,8 @@ pub enum Expression {
Async(AsyncExpression),
/// An array expression, e.g., `[true, false, true, false]`.
Array(ArrayExpression),
/// A slice expression, e.g., `arr[2..=4]``.
Slice(Box<Slice>),
/// A binary expression, e.g., `42 + 24`.
Binary(Box<BinaryExpression>),
/// A call expression, e.g., `my_fun(args)`.
Expand Down Expand Up @@ -135,6 +140,7 @@ impl Node for Expression {
match self {
ArrayAccess(n) => n.span(),
Array(n) => n.span(),
Slice(n) => n.span(),
AssociatedConstant(n) => n.span(),
AssociatedFunction(n) => n.span(),
Async(n) => n.span(),
Expand All @@ -161,6 +167,7 @@ impl Node for Expression {
match self {
ArrayAccess(n) => n.set_span(span),
Array(n) => n.set_span(span),
Slice(n) => n.set_span(span),
AssociatedConstant(n) => n.set_span(span),
AssociatedFunction(n) => n.set_span(span),
Async(n) => n.set_span(span),
Expand All @@ -187,6 +194,7 @@ impl Node for Expression {
match self {
Array(n) => n.id(),
ArrayAccess(n) => n.id(),
Slice(n) => n.id(),
AssociatedConstant(n) => n.id(),
AssociatedFunction(n) => n.id(),
Async(n) => n.id(),
Expand All @@ -213,6 +221,7 @@ impl Node for Expression {
match self {
Array(n) => n.set_id(id),
ArrayAccess(n) => n.set_id(id),
Slice(n) => n.set_id(id),
AssociatedConstant(n) => n.set_id(id),
AssociatedFunction(n) => n.set_id(id),
Async(n) => n.set_id(id),
Expand Down Expand Up @@ -241,6 +250,7 @@ impl fmt::Display for Expression {
match &self {
Array(n) => n.fmt(f),
ArrayAccess(n) => n.fmt(f),
Slice(n) => n.fmt(f),
AssociatedConstant(n) => n.fmt(f),
AssociatedFunction(n) => n.fmt(f),
Async(n) => n.fmt(f),
Expand Down Expand Up @@ -279,6 +289,7 @@ impl Expression {
Ternary(_) => 0,
Array(_)
| ArrayAccess(_)
| Slice(_)
| AssociatedConstant(_)
| AssociatedFunction(_)
| Async(_)
Expand Down
61 changes: 61 additions & 0 deletions compiler/ast/src/expressions/slice.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright (C) 2019-2025 Provable Inc.
// This file is part of the Leo library.

// The Leo library is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// The Leo library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// 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 std::fmt::{self, Debug};

use leo_span::Span;
use serde::{Deserialize, Serialize};

use crate::{Expression, Node, NodeID};

/// The slice expression, e.g., `arr[2..=4]``.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Slice {
/// The expression evaluating to some array type, e.g., `[false, true]`.
pub source_array: Expression,
/// The lower_bound to use, if any, when slicing the array.
pub start: Option<Expression>,
/// The upper_bound to use, if any, when slicing the array.
pub stop: Option<Expression>,
/// Whether `stop` is inclusive or not.
/// Signified with `=` when parsing.
pub clusivity: bool,
/// The span for the entire expression `foo[start..=stop]`.
pub span: Span,
/// The ID of the node.
pub id: NodeID,
}

impl fmt::Display for Slice {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}[{}..{}{}]",
self.source_array,
self.start.clone().map(|start| start.to_string()).unwrap_or("".into()),
if self.clusivity { "=" } else { "" },
self.stop.clone().map(|stop| stop.to_string()).unwrap_or("".into())
)
}
}

impl From<Slice> for Expression {
fn from(value: Slice) -> Self {
Expression::Slice(Box::new(value))
}
}

crate::simple_node_impl!(Slice);
586 changes: 296 additions & 290 deletions compiler/ast/src/interpreter_value/evaluate.rs

Large diffs are not rendered by default.

28 changes: 28 additions & 0 deletions compiler/ast/src/interpreter_value/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,25 @@ impl Value {
Some(array[i].clone().into())
}

pub fn array_slice(&self, start: usize, end_exclusive: Option<usize>) -> Option<Self> {
let plaintext: &SvmPlaintext = self.try_as_ref()?;
let Plaintext::Array(array, ..) = plaintext else {
return None;
};

let range = match end_exclusive {
Some(end) if end <= start || end > array.len() => return None,
Some(end) => start..end,
None if start > array.len() => return None,
None => start..array.len(),
};

Some(Value::from(ValueVariants::Svm(SvmValue::Plaintext(SvmPlaintext::Array(
array[range].to_vec(),
Default::default(),
)))))
}

pub fn array_index_set(&mut self, i: usize, value: Self) -> Option<()> {
let plaintext_rhs: SvmPlaintext = value.try_into().ok()?;

Expand All @@ -614,6 +633,15 @@ impl Value {
Some(())
}

pub fn array_len(&self) -> Option<usize> {
let plaintext: &SvmPlaintext = self.try_as_ref()?;
let Plaintext::Array(array, ..) = plaintext else {
return None;
};

Some(array.len())
}

pub fn tuple_len(&self) -> Option<usize> {
let ValueVariants::Tuple(tuple) = &self.contents else {
return None;
Expand Down
3 changes: 3 additions & 0 deletions compiler/ast/src/passes/consumer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pub trait ExpressionConsumer {
match input {
Expression::Array(array) => self.consume_array(array),
Expression::ArrayAccess(access) => self.consume_array_access(*access),
Expression::Slice(slice) => self.consume_slice(*slice),
Expression::AssociatedConstant(constant) => self.consume_associated_constant(constant),
Expression::AssociatedFunction(function) => self.consume_associated_function(function),
Expression::Async(async_) => self.consume_async(async_),
Expand Down Expand Up @@ -62,6 +63,8 @@ pub trait ExpressionConsumer {

fn consume_array(&mut self, _input: ArrayExpression) -> Self::Output;

fn consume_slice(&mut self, _input: Slice) -> Self::Output;

fn consume_binary(&mut self, _input: BinaryExpression) -> Self::Output;

fn consume_call(&mut self, _input: CallExpression) -> Self::Output;
Expand Down
64 changes: 43 additions & 21 deletions compiler/ast/src/passes/reconstructor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,29 +119,34 @@ pub trait AstReconstructor {
fn reconstruct_expression(
&mut self,
input: Expression,
additional: &Self::AdditionalInput,
_additional: &Self::AdditionalInput,
) -> (Expression, Self::AdditionalOutput) {
match input {
Expression::AssociatedConstant(constant) => self.reconstruct_associated_constant(constant, additional),
Expression::AssociatedFunction(function) => self.reconstruct_associated_function(function, additional),
Expression::Async(async_) => self.reconstruct_async(async_, additional),
Expression::Array(array) => self.reconstruct_array(array, additional),
Expression::ArrayAccess(access) => self.reconstruct_array_access(*access, additional),
Expression::Binary(binary) => self.reconstruct_binary(*binary, additional),
Expression::Call(call) => self.reconstruct_call(*call, additional),
Expression::Cast(cast) => self.reconstruct_cast(*cast, additional),
Expression::Struct(struct_) => self.reconstruct_struct_init(struct_, additional),
Expression::Err(err) => self.reconstruct_err(err, additional),
Expression::Path(path) => self.reconstruct_path(path, additional),
Expression::Literal(value) => self.reconstruct_literal(value, additional),
Expression::Locator(locator) => self.reconstruct_locator(locator, additional),
Expression::MemberAccess(access) => self.reconstruct_member_access(*access, additional),
Expression::Repeat(repeat) => self.reconstruct_repeat(*repeat, additional),
Expression::Ternary(ternary) => self.reconstruct_ternary(*ternary, additional),
Expression::Tuple(tuple) => self.reconstruct_tuple(tuple, additional),
Expression::TupleAccess(access) => self.reconstruct_tuple_access(*access, additional),
Expression::Unary(unary) => self.reconstruct_unary(*unary, additional),
Expression::Unit(unit) => self.reconstruct_unit(unit, additional),
Expression::AssociatedConstant(constant) => {
self.reconstruct_associated_constant(constant, &Default::default())
}
Expression::AssociatedFunction(function) => {
self.reconstruct_associated_function(function, &Default::default())
}
Expression::Async(async_) => self.reconstruct_async(async_, &Default::default()),
Expression::Slice(slice) => self.reconstruct_slice(*slice, &Default::default()),
Expression::Array(array) => self.reconstruct_array(array, &Default::default()),
Expression::ArrayAccess(access) => self.reconstruct_array_access(*access, &Default::default()),
Expression::Binary(binary) => self.reconstruct_binary(*binary, &Default::default()),
Expression::Call(call) => self.reconstruct_call(*call, &Default::default()),
Expression::Cast(cast) => self.reconstruct_cast(*cast, &Default::default()),
Expression::Struct(struct_) => self.reconstruct_struct_init(struct_, &Default::default()),
Expression::Err(err) => self.reconstruct_err(err, &Default::default()),
Expression::Path(path) => self.reconstruct_path(path, &Default::default()),
Expression::Literal(value) => self.reconstruct_literal(value, &Default::default()),
Expression::Locator(locator) => self.reconstruct_locator(locator, &Default::default()),
Expression::MemberAccess(access) => self.reconstruct_member_access(*access, &Default::default()),
Expression::Repeat(repeat) => self.reconstruct_repeat(*repeat, &Default::default()),
Expression::Ternary(ternary) => self.reconstruct_ternary(*ternary, &Default::default()),
Expression::Tuple(tuple) => self.reconstruct_tuple(tuple, &Default::default()),
Expression::TupleAccess(access) => self.reconstruct_tuple_access(*access, &Default::default()),
Expression::Unary(unary) => self.reconstruct_unary(*unary, &Default::default()),
Expression::Unit(unit) => self.reconstruct_unit(unit, &Default::default()),
}
}

Expand Down Expand Up @@ -253,6 +258,23 @@ pub trait AstReconstructor {
)
}

fn reconstruct_slice(
&mut self,
input: Slice,
_additional: &Self::AdditionalInput,
) -> (Expression, Self::AdditionalOutput) {
(
Slice {
source_array: self.reconstruct_expression(input.source_array, &Default::default()).0,
start: input.start.map(|expr| self.reconstruct_expression(expr, &Default::default()).0),
stop: input.stop.map(|expr| self.reconstruct_expression(expr, &Default::default()).0),
..input
}
.into(),
Default::default(),
)
}

fn reconstruct_binary(
&mut self,
input: BinaryExpression,
Expand Down
8 changes: 8 additions & 0 deletions compiler/ast/src/passes/visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ pub trait AstVisitor {
match input {
Expression::Array(array) => self.visit_array(array, additional),
Expression::ArrayAccess(access) => self.visit_array_access(access, additional),
Expression::Slice(slice) => self.visit_slice(slice, additional),
Expression::AssociatedConstant(constant) => self.visit_associated_constant(constant, additional),
Expression::AssociatedFunction(function) => self.visit_associated_function(function, additional),
Expression::Async(async_) => self.visit_async(async_, additional),
Expand Down Expand Up @@ -153,6 +154,13 @@ pub trait AstVisitor {
Default::default()
}

fn visit_slice(&mut self, input: &Slice, additional: &Self::AdditionalInput) -> Self::Output {
self.visit_expression(&input.source_array, additional);
input.start.as_ref().map(|start| self.visit_expression(start, additional));
input.stop.as_ref().map(|stop| self.visit_expression(stop, additional));
Default::default()
}

fn visit_associated_constant(
&mut self,
_input: &AssociatedConstantExpression,
Expand Down
22 changes: 22 additions & 0 deletions compiler/parser-lossless/src/grammar.lalrpop
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,28 @@ Expr1<S>: SyntaxNode<'a> = {
<x:Expr1<S>> <d:WithTrivia<Dot>> <i:WithTrivia<Integer>> => {
SyntaxNode::new(ExpressionKind::TupleAccess, [x, d, i])
},
// Array slice.
<x:Expr1<S>> <l:WithTrivia<LeftSquare>> <start:Expr?> <d:WithTrivia<DotDot>> <eq:WithTrivia<Assign>?> <stop:Expr?> <r:WithTrivia<RightSquare>> => {
let inclusive_range_without_an_end = eq.is_some() && stop.is_none();
let node = SyntaxNode::new(
ExpressionKind::Slice,
[x, l]
.into_iter()
.chain(start)
.chain(std::iter::once(d))
.chain(eq)
.chain(stop)
.chain(std::iter::once(r))
);

// Emit error for inclusive slices without an end
if inclusive_range_without_an_end {
handler.emit_err(ParserError::inclusive_range_with_no_end(node.span));
}

node
},
// todo: Maybe a seperate Range expression can be introduced here along with a new DotDotEq token, which can also be used with the loops.
// Array access.
<x:Expr1<S>> <l:WithTrivia<LeftSquare>> <index:Expr> <r:WithTrivia<RightSquare>> => {
SyntaxNode::new(ExpressionKind::ArrayAccess, [x, l, index, r])
Expand Down
1 change: 1 addition & 0 deletions compiler/parser-lossless/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ pub enum ExpressionKind {
AssociatedFunctionCall,
Async,
Array,
Slice,
Binary,
Call,
Cast,
Expand Down
26 changes: 26 additions & 0 deletions compiler/parser/src/conversions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,32 @@ pub fn to_expression(node: &SyntaxNode<'_>, builder: &NodeBuilder, handler: &Han
.collect::<Result<Vec<_>>>()?;
leo_ast::ArrayExpression { elements, span, id }.into()
}
ExpressionKind::Slice => {
let mut slice_iter = node.children.iter();

let mut next_token = || slice_iter.next().expect("Can't happen");

let array = to_expression(next_token(), builder, handler)?;
let _left = next_token();

let token = next_token();
let (start, _d) = if token.text != ".." {
(Some(to_expression(token, builder, handler)?), next_token())
} else {
(None, token)
};

let token = next_token();
let (clusivity, stop) = if token.text == "=" {
(true, Some(to_expression(next_token(), builder, handler)?))
} else if token.text != "]" {
(false, Some(to_expression(token, builder, handler)?))
} else {
(false, None)
};

leo_ast::Slice { source_array: array, start, stop, clusivity, span, id }.into()
}
ExpressionKind::Binary => {
let [lhs, op, rhs] = &node.children[..] else {
panic!("Can't happen");
Expand Down
14 changes: 14 additions & 0 deletions compiler/passes/src/code_generation/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ impl CodeGeneratingVisitor<'_> {
match input {
Expression::Array(expr) => self.visit_array(expr),
Expression::ArrayAccess(expr) => self.visit_array_access(expr),
Expression::Slice(_) => panic!("Slices should not appear in the AST at this point of compilation"),
Expression::AssociatedConstant(expr) => self.visit_associated_constant(expr),
Expression::AssociatedFunction(expr) => self.visit_associated_function(expr),
Expression::Async(expr) => self.visit_async(expr),
Expand Down Expand Up @@ -147,6 +148,19 @@ impl CodeGeneratingVisitor<'_> {
let (left_operand, left_instructions) = self.visit_expression(&input.left);
let (right_operand, right_instructions) = self.visit_expression(&input.right);

// Check that the types are not arrays.
// This is a sanity check.
// Below types may note be in the type table
// as common subexpression elimination can change the AST quite aggresively
// and also we are/should not be reconstructing the type table after it unless really need.
if let Some(left_type) = self.state.type_table.get(&input.left.id()) {
assert!(!matches!(left_type, Type::Array(_)));
}

if let Some(right_type) = self.state.type_table.get(&input.right.id()) {
assert!(!matches!(right_type, Type::Array(_)));
}

let opcode = match input.op {
BinaryOperation::Add => String::from("add"),
BinaryOperation::AddWrapped => String::from("add.w"),
Expand Down
1 change: 1 addition & 0 deletions compiler/passes/src/common/replacer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ where
Expression::Async(async_) => self.reconstruct_async(async_, &()),
Expression::Array(array) => self.reconstruct_array(array, &()),
Expression::ArrayAccess(access) => self.reconstruct_array_access(*access, &()),
Expression::Slice(slice) => self.reconstruct_slice(*slice, &()),
Expression::Binary(binary) => self.reconstruct_binary(*binary, &()),
Expression::Call(call) => self.reconstruct_call(*call, &()),
Expression::Cast(cast) => self.reconstruct_cast(*cast, &()),
Expand Down
Loading