Skip to content
Draft
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
19 changes: 17 additions & 2 deletions compiler/ast/src/expressions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ pub use intrinsic::*;
mod repeat;
pub use repeat::*;

mod slice;
pub use slice::*;

mod ternary;
pub use ternary::*;

Expand Down Expand Up @@ -106,6 +109,8 @@ pub enum Expression {
MemberAccess(Box<MemberAccess>),
/// An array expression constructed from one repeated element, e.g., `[1u32; 5]`.
Repeat(Box<RepeatExpression>),
/// An array expression constructed from a slice of another array, e.g., `arr[1..4]`.
Slice(Box<SliceExpression>),
/// A ternary conditional expression `cond ? if_expr : else_expr`.
Ternary(Box<TernaryExpression>),
/// A tuple expression e.g., `(foo, 42, true)`.
Expand Down Expand Up @@ -142,6 +147,7 @@ impl Node for Expression {
Locator(n) => n.span(),
MemberAccess(n) => n.span(),
Repeat(n) => n.span(),
Slice(n) => n.span(),
Ternary(n) => n.span(),
Tuple(n) => n.span(),
TupleAccess(n) => n.span(),
Expand All @@ -167,6 +173,7 @@ impl Node for Expression {
Locator(n) => n.set_span(span),
MemberAccess(n) => n.set_span(span),
Repeat(n) => n.set_span(span),
Slice(n) => n.set_span(span),
Ternary(n) => n.set_span(span),
Tuple(n) => n.set_span(span),
TupleAccess(n) => n.set_span(span),
Expand All @@ -190,6 +197,7 @@ impl Node for Expression {
Locator(n) => n.id(),
MemberAccess(n) => n.id(),
Repeat(n) => n.id(),
Slice(n) => n.id(),
Err(n) => n.id(),
Intrinsic(n) => n.id(),
Ternary(n) => n.id(),
Expand All @@ -215,6 +223,7 @@ impl Node for Expression {
Locator(n) => n.set_id(id),
MemberAccess(n) => n.set_id(id),
Repeat(n) => n.set_id(id),
Slice(n) => n.set_id(id),
Err(n) => n.set_id(id),
Intrinsic(n) => n.set_id(id),
Ternary(n) => n.set_id(id),
Expand Down Expand Up @@ -244,6 +253,7 @@ impl fmt::Display for Expression {
Locator(n) => n.fmt(f),
MemberAccess(n) => n.fmt(f),
Repeat(n) => n.fmt(f),
Slice(n) => n.fmt(f),
Ternary(n) => n.fmt(f),
Tuple(n) => n.fmt(f),
TupleAccess(n) => n.fmt(f),
Expand All @@ -268,8 +278,8 @@ impl Expression {
Cast(_) => 12,
Ternary(_) => 0,
Array(_) | ArrayAccess(_) | Async(_) | Call(_) | Composite(_) | Err(_) | Intrinsic(_) | Path(_)
| Literal(_) | Locator(_) | MemberAccess(_) | Repeat(_) | Tuple(_) | TupleAccess(_) | Unary(_)
| Unit(_) => 20,
| Literal(_) | Locator(_) | MemberAccess(_) | Repeat(_) | Slice(_) | Tuple(_) | TupleAccess(_)
| Unary(_) | Unit(_) => 20,
}
}

Expand Down Expand Up @@ -354,6 +364,11 @@ impl Expression {
Expression::ArrayAccess(expr) => expr.array.is_pure(get_type) && expr.index.is_pure(get_type),
Expression::MemberAccess(expr) => expr.inner.is_pure(get_type),
Expression::Repeat(expr) => expr.expr.is_pure(get_type) && expr.count.is_pure(get_type),
Expression::Slice(expr) => {
expr.array.is_pure(get_type)
&& expr.start.as_ref().is_none_or(|s| s.is_pure(get_type))
&& expr.end.as_ref().is_none_or(|(_, e)| e.is_pure(get_type))
}
Expression::TupleAccess(expr) => expr.tuple.is_pure(get_type),
Expression::Array(expr) => expr.elements.iter().all(|e| e.is_pure(get_type)),
Expression::Composite(expr) => {
Expand Down
60 changes: 60 additions & 0 deletions compiler/ast/src/expressions/slice.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// 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 super::*;

/// An array constructed from slicing another.
///
/// E.g., `arr[2..5]`, `arr[0..=3]`, `arr[1..]`, `arr[..4]`
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SliceExpression {
/// The array being sliced.
pub array: Expression,
/// The optional starting index.
pub start: Option<Expression>,
/// The optional ending index and whether it's inclusive.
pub end: Option<(bool, Expression)>,
/// The span.
pub span: Span,
/// The ID of the node.
pub id: NodeID,
}

impl fmt::Display for SliceExpression {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// Format the start expression.
let start = match &self.start {
Some(expr) => expr.to_string(),
None => "".to_string(),
};
// Format the end expression.
let end = match &self.end {
Some((true, expr)) => format!("={expr}"),
Some((false, expr)) => expr.to_string(),
None => "".to_string(),
};

write!(f, "{}[{start}..{end}]", self.array)
}
}

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

crate::simple_node_impl!(SliceExpression);
15 changes: 15 additions & 0 deletions compiler/ast/src/interpreter_value/evaluate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,21 @@ pub fn evaluate_binary(
_ => {}
}

// Handle array concatenation for Add operation
if matches!(op, BinaryOperation::Add)
&& let (
ValueVariants::Svm(SvmValueParam::Plaintext(Plaintext::Array(x, _))),
ValueVariants::Svm(SvmValueParam::Plaintext(Plaintext::Array(y, _))),
) = (&lhs.contents, &rhs.contents)
{
let mut elements = x.clone();
elements.extend(y.clone());
return Ok(Value::from(ValueVariants::Svm(SvmValueParam::Plaintext(Plaintext::Array(
elements,
Default::default(),
)))));
}

let ValueVariants::Svm(SvmValueParam::Plaintext(Plaintext::Literal(lhs, ..))) = &lhs.contents else {
halt2!(span, "Type error");
};
Expand Down
19 changes: 19 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 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 @@ -38,6 +38,7 @@ pub trait ExpressionConsumer {
Expression::Locator(locator) => self.consume_locator(locator),
Expression::MemberAccess(access) => self.consume_member_access(*access),
Expression::Repeat(repeat) => self.consume_repeat(*repeat),
Expression::Slice(slice) => self.consume_slice(*slice),
Expression::Ternary(ternary) => self.consume_ternary(*ternary),
Expression::Tuple(tuple) => self.consume_tuple(tuple),
Expression::TupleAccess(access) => self.consume_tuple_access(*access),
Expand Down Expand Up @@ -77,6 +78,8 @@ pub trait ExpressionConsumer {

fn consume_repeat(&mut self, _input: RepeatExpression) -> Self::Output;

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

fn consume_intrinsic(&mut self, _input: IntrinsicExpression) -> Self::Output;

fn consume_ternary(&mut self, _input: TernaryExpression) -> Self::Output;
Expand Down
20 changes: 20 additions & 0 deletions compiler/ast/src/passes/reconstructor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ pub trait AstReconstructor {
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::Slice(slice) => self.reconstruct_slice(*slice, 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),
Expand Down Expand Up @@ -195,6 +196,25 @@ pub trait AstReconstructor {
)
}

fn reconstruct_slice(
&mut self,
input: SliceExpression,
_additional: &Self::AdditionalInput,
) -> (Expression, Self::AdditionalOutput) {
(
SliceExpression {
array: self.reconstruct_expression(input.array, &Default::default()).0,
start: input.start.map(|start| self.reconstruct_expression(start, &Default::default()).0),
end: input
.end
.map(|(inclusive, end)| (inclusive, self.reconstruct_expression(end, &Default::default()).0)),
..input
}
.into(),
Default::default(),
)
}

fn reconstruct_intrinsic(
&mut self,
input: IntrinsicExpression,
Expand Down
12 changes: 12 additions & 0 deletions compiler/ast/src/passes/visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ pub trait AstVisitor {
Expression::Locator(locator) => self.visit_locator(locator, additional),
Expression::MemberAccess(access) => self.visit_member_access(access, additional),
Expression::Repeat(repeat) => self.visit_repeat(repeat, additional),
Expression::Slice(slice) => self.visit_slice(slice, additional),
Expression::Ternary(ternary) => self.visit_ternary(ternary, additional),
Expression::Tuple(tuple) => self.visit_tuple(tuple, additional),
Expression::TupleAccess(access) => self.visit_tuple_access(access, additional),
Expand Down Expand Up @@ -223,6 +224,17 @@ pub trait AstVisitor {
Default::default()
}

fn visit_slice(&mut self, input: &SliceExpression, _additional: &Self::AdditionalInput) -> Self::Output {
self.visit_expression(&input.array, &Default::default());
if let Some(start) = input.start.as_ref() {
self.visit_expression(start, &Default::default());
}
if let Some((_, end)) = input.end.as_ref() {
self.visit_expression(end, &Default::default());
}
Default::default()
}

fn visit_ternary(&mut self, input: &TernaryExpression, _additional: &Self::AdditionalInput) -> Self::Output {
self.visit_expression(&input.condition, &Default::default());
self.visit_expression(&input.if_true, &Default::default());
Expand Down
23 changes: 23 additions & 0 deletions compiler/parser-lossless/src/grammar.lalrpop
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,28 @@ Expr1<S>: SyntaxNode<'a> = {
<x:Expr1<S>> <l:WithTrivia<LeftSquare>> <index:Expr> <r:WithTrivia<RightSquare>> => {
SyntaxNode::new(ExpressionKind::ArrayAccess, [x, l, index, r])
},
// Slice with both bounds: arr[start..end] or arr[start..=end]
<x:Expr1<S>> <l:WithTrivia<LeftSquare>> <start:Expr> <op:WithTrivia<DotDot>> <end:Expr> <r:WithTrivia<RightSquare>> => {
SyntaxNode::new(ExpressionKind::SliceBoth, [x, l, start, op, end, r])
},
<x:Expr1<S>> <l:WithTrivia<LeftSquare>> <start:Expr> <op:WithTrivia<DotDotEq>> <end:Expr> <r:WithTrivia<RightSquare>> => {
SyntaxNode::new(ExpressionKind::SliceBoth, [x, l, start, op, end, r])
},
// Slice with only start: arr[start..]
<x:Expr1<S>> <l:WithTrivia<LeftSquare>> <start:Expr> <op:WithTrivia<DotDot>> <r:WithTrivia<RightSquare>> => {
SyntaxNode::new(ExpressionKind::SliceFirst, [x, l, start, op, r])
},
// Slice with only end: arr[..end] or arr[..=end]
<x:Expr1<S>> <l:WithTrivia<LeftSquare>> <op:WithTrivia<DotDot>> <end:Expr> <r:WithTrivia<RightSquare>> => {
SyntaxNode::new(ExpressionKind::SliceLast, [x, l, op, end, r])
},
<x:Expr1<S>> <l:WithTrivia<LeftSquare>> <op:WithTrivia<DotDotEq>> <end:Expr> <r:WithTrivia<RightSquare>> => {
SyntaxNode::new(ExpressionKind::SliceLast, [x, l, op, end, r])
},
// Slice with neither: arr[..] (slice entire array)
<x:Expr1<S>> <l:WithTrivia<LeftSquare>> <op:WithTrivia<DotDot>> <r:WithTrivia<RightSquare>> => {
SyntaxNode::new(ExpressionKind::SliceNone, [x, l, op, r])
},
// Member access.
<x:Expr1<S>> <d:WithTrivia<Dot>> <i:WithTrivia<Identifier>> => {
SyntaxNode::new(ExpressionKind::MemberAccess, [x, d, i])
Expand Down Expand Up @@ -906,6 +928,7 @@ extern {
Comma => LalrToken { token: Token::Comma, .. },
Dot => LalrToken { token: Token::Dot, .. },
DotDot => LalrToken { token: Token::DotDot, .. },
DotDotEq => LalrToken { token: Token::DotDotEq, .. },
Semicolon => LalrToken { token: Token::Semicolon, .. },
Colon => LalrToken { token: Token::Colon, .. },
DoubleColon => LalrToken{ token: Token::DoubleColon, .. },
Expand Down
4 changes: 4 additions & 0 deletions compiler/parser-lossless/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,10 @@ pub enum ExpressionKind {
MethodCall,
Parenthesized,
Repeat,
SliceBoth,
SliceFirst,
SliceLast,
SliceNone,
Intrinsic,
SpecialAccess, // TODO: fold into Intrinsic
Struct,
Expand Down
3 changes: 3 additions & 0 deletions compiler/parser-lossless/src/tokens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,8 @@ pub enum Token {
Dot,
#[token("..")]
DotDot,
#[token("..=")]
DotDotEq,
#[token(";")]
Semicolon,
#[token(":")]
Expand Down Expand Up @@ -414,6 +416,7 @@ impl Token {
"Comma" => "','",
"Dot" => "'.'",
"DotDot" => "'..'",
"DotDotEq" => "'..='",
"Semicolon" => "';'",
"Colon" => "':'",
"DoubleColon" => "'::'",
Expand Down
54 changes: 54 additions & 0 deletions compiler/parser/src/conversions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,60 @@ impl<'a> ConversionContext<'a> {
leo_ast::ArrayAccess { array, index, span, id }.into()
}

ExpressionKind::SliceBoth => {
// arr[start..end] or arr[start..=end]
let [array, _left, start, op, end, _right] = &node.children[..] else {
panic!("Can't happen");
};

let array = self.to_expression(array)?;
let start = Some(self.to_expression(start)?);
let inclusive = op.text == "..=";
let end_expr = self.to_expression(end)?;
let end = Some((inclusive, end_expr));

leo_ast::SliceExpression { array, start, end, span, id }.into()
}

ExpressionKind::SliceFirst => {
// arr[start..]
let [array, _left, start, _op, _right] = &node.children[..] else {
panic!("Can't happen");
};

let array = self.to_expression(array)?;
let start = Some(self.to_expression(start)?);
let end = None;

leo_ast::SliceExpression { array, start, end, span, id }.into()
}

ExpressionKind::SliceLast => {
// arr[..end] or arr[..=end]
let [array, _left, op, end, _right] = &node.children[..] else {
panic!("Can't happen");
};

let array = self.to_expression(array)?;
let start = None;
let inclusive = op.text == "..=";
let end_expr = self.to_expression(end)?;
let end = Some((inclusive, end_expr));

leo_ast::SliceExpression { array, start, end, span, id }.into()
}

ExpressionKind::SliceNone => {
// arr[..]
let [array, _left, _op, _right] = &node.children[..] else {
panic!("Can't happen");
};

let array = self.to_expression(array)?;

leo_ast::SliceExpression { array, start: None, end: None, span, id }.into()
}

ExpressionKind::Intrinsic => {
let name = Symbol::intern(node.children[0].text);

Expand Down
Loading