Introduce length functor (#314)

This commit is contained in:
Mike Voronov 2022-09-08 16:58:04 +03:00 committed by GitHub
parent 626796b299
commit a4011ef038
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
56 changed files with 1411 additions and 683 deletions

View File

@ -1,3 +1,20 @@
## Version 0.28.0 (2022-09-07)
[PR 314](https://github.com/fluencelabs/aquavm/pull/314):
The length functor was introduced
[PR 307](https://github.com/fluencelabs/aquavm/pull/307):
bug with iteration over empty canonicalized stream was fixed
[PR 305](https://github.com/fluencelabs/aquavm/pull/305):
bug with absence of join behaviour for canonicalized streams in fold instructions was fixed
[PR 303](https://github.com/fluencelabs/aquavm/pull/303):
unsafe code for `LambdaAST` creation was removed
[PR 301](https://github.com/fluencelabs/aquavm/pull/301):
bug with adding just executed values to a restricted stream was fixed
## Version 0.27.0 (2022-08-23)
[PR 292](https://github.com/fluencelabs/aquavm/pull/292):

5
Cargo.lock generated
View File

@ -13,7 +13,7 @@ dependencies = [
[[package]]
name = "air"
version = "0.27.0"
version = "0.28.0"
dependencies = [
"air-execution-info-collector",
"air-interpreter-data",
@ -72,7 +72,7 @@ version = "0.1.0"
[[package]]
name = "air-interpreter"
version = "0.27.0"
version = "0.28.0"
dependencies = [
"air",
"air-interpreter-interface",
@ -112,6 +112,7 @@ dependencies = [
name = "air-lambda-ast"
version = "0.1.0"
dependencies = [
"itertools 0.10.3",
"non-empty-vec",
"serde",
"serde_json",

View File

@ -1,6 +1,6 @@
[package]
name = "air-interpreter"
version = "0.27.0"
version = "0.28.0"
description = "Crate-wrapper for air"
authors = ["Fluence Labs"]
edition = "2018"

View File

@ -1,6 +1,6 @@
[package]
name = "air"
version = "0.27.0"
version = "0.28.0"
description = "Interpreter of AIR scripts intended to coordinate request flow in the Fluence network"
authors = ["Fluence Labs"]
edition = "2018"

View File

@ -138,7 +138,11 @@ fn apply_canon_stream_with_lambda(
let canon_stream = exec_ctx.scalars.get_canon_stream(stream_name)?;
let (result, tetraplet) = JValuable::apply_lambda_with_tetraplets(&canon_stream, lambda, exec_ctx)?;
// TODO: refactor this code after boxed value
let value = ValueAggregate::new(Rc::new(result.clone()), Rc::new(tetraplet), canon_stream.position());
let value = ValueAggregate::new(
Rc::new(result.into_owned()),
Rc::new(tetraplet),
canon_stream.position(),
);
Ok(value)
}

View File

@ -15,6 +15,7 @@
*/
use super::*;
use crate::execution_step::boxed_value::populate_tetraplet_with_lambda;
use crate::execution_step::CatchableError;
use crate::execution_step::PEEK_ALLOWED_ON_NON_EMPTY;
use crate::JValue;
@ -23,6 +24,7 @@ use crate::SecurityTetraplet;
use air_parser::ast;
use std::borrow::Cow;
use std::ops::Deref;
use std::rc::Rc;
@ -130,11 +132,11 @@ fn create_scalar_lambda_iterable<'ctx>(
scalar_name: &str,
lambda: &LambdaAST<'_>,
) -> ExecutionResult<FoldIterableScalar> {
use crate::execution_step::lambda_applier::select_from_scalar;
use crate::execution_step::lambda_applier::select_by_lambda_from_scalar;
match exec_ctx.scalars.get_value(scalar_name)? {
ScalarRef::Value(variable) => {
let jvalues = select_from_scalar(&variable.result, lambda.iter(), exec_ctx)?;
let jvalues = select_by_lambda_from_scalar(&variable.result, lambda, exec_ctx)?;
let tetraplet = variable.tetraplet.deref().clone();
from_jvalue(jvalues, tetraplet, lambda)
}
@ -150,18 +152,17 @@ fn create_scalar_lambda_iterable<'ctx>(
/// Construct IterableValue from the result and given triplet.
fn from_jvalue(
jvalue: &JValue,
mut tetraplet: SecurityTetraplet,
jvalue: Cow<'_, JValue>,
tetraplet: SecurityTetraplet,
lambda: &LambdaAST<'_>,
) -> ExecutionResult<FoldIterableScalar> {
let formatted_lambda_ast = air_lambda_ast::format_ast(lambda);
tetraplet.add_lambda(&formatted_lambda_ast);
let tetraplet = populate_tetraplet_with_lambda(tetraplet, lambda);
let tetraplet = Rc::new(tetraplet);
let iterable = match jvalue {
let iterable = match jvalue.as_ref() {
JValue::Array(array) => array,
_ => {
return Err(CatchableError::FoldIteratesOverNonArray(jvalue.clone(), formatted_lambda_ast).into());
return Err(CatchableError::FoldIteratesOverNonArray(jvalue.into_owned(), lambda.to_string()).into());
}
};

View File

@ -38,14 +38,15 @@ use std::borrow::Cow;
/// Represent a value that could be transform to a JValue with or without tetraplets.
pub(crate) trait JValuable {
/// Applies lambda to the internal value, produces JValue.
fn apply_lambda<'i>(&self, lambda: &LambdaAST<'_>, exec_ctx: &ExecutionCtx<'i>) -> ExecutionResult<&JValue>;
fn apply_lambda<'i>(&self, lambda: &LambdaAST<'_>, exec_ctx: &ExecutionCtx<'i>)
-> ExecutionResult<Cow<'_, JValue>>;
/// Applies lambda to the internal value, produces JValue with tetraplet.
fn apply_lambda_with_tetraplets<'i>(
&self,
lambda: &LambdaAST<'_>,
exec_ctx: &ExecutionCtx<'i>,
) -> ExecutionResult<(&JValue, SecurityTetraplet)>;
) -> ExecutionResult<(Cow<'_, JValue>, SecurityTetraplet)>;
/// Return internal value as borrowed if it's possible, owned otherwise.
fn as_jvalue(&self) -> Cow<'_, JValue>;

View File

@ -14,9 +14,10 @@
* limitations under the License.
*/
use super::select_from_stream;
use super::select_by_lambda_from_stream;
use super::ExecutionResult;
use super::JValuable;
use crate::execution_step::boxed_value::populate_tetraplet_with_lambda;
use crate::execution_step::boxed_value::CanonStream;
use crate::execution_step::ExecutionCtx;
use crate::execution_step::RcSecurityTetraplets;
@ -24,15 +25,17 @@ use crate::JValue;
use crate::LambdaAST;
use crate::SecurityTetraplet;
use air_lambda_ast::format_ast;
use std::borrow::Cow;
use std::ops::Deref;
impl JValuable for &CanonStream {
fn apply_lambda<'i>(&self, lambda: &LambdaAST<'_>, exec_ctx: &ExecutionCtx<'i>) -> ExecutionResult<&JValue> {
fn apply_lambda<'i>(
&self,
lambda: &LambdaAST<'_>,
exec_ctx: &ExecutionCtx<'i>,
) -> ExecutionResult<Cow<'_, JValue>> {
let iter = self.iter().map(|v| v.result.deref());
let select_result = select_from_stream(iter, lambda, exec_ctx)?;
let select_result = select_by_lambda_from_stream(iter, lambda, exec_ctx)?;
Ok(select_result.result)
}
@ -41,14 +44,18 @@ impl JValuable for &CanonStream {
&self,
lambda: &LambdaAST<'_>,
exec_ctx: &ExecutionCtx<'i>,
) -> ExecutionResult<(&JValue, SecurityTetraplet)> {
) -> ExecutionResult<(Cow<'_, JValue>, SecurityTetraplet)> {
let iter = self.iter().map(|v| v.result.deref());
let select_result = select_from_stream(iter, lambda, exec_ctx)?;
let select_result = select_by_lambda_from_stream(iter, lambda, exec_ctx)?;
// unwrap is safe here because each value has a tetraplet and a lambda always returns a valid index
let resolved_call = self.nth(select_result.tetraplet_idx).unwrap();
let mut tetraplet = resolved_call.tetraplet.as_ref().clone();
tetraplet.add_lambda(&format_ast(lambda));
let tetraplet = match select_result.tetraplet_idx {
Some(idx) => {
let resolved_call = self.nth(idx).expect(crate::execution_step::TETRAPLET_IDX_CORRECT);
resolved_call.tetraplet.as_ref().clone()
}
None => SecurityTetraplet::new(exec_ctx.run_parameters.current_peer_id.to_string(), "", "", ""),
};
let tetraplet = populate_tetraplet_with_lambda(tetraplet, lambda);
Ok((select_result.result, tetraplet))
}

View File

@ -14,25 +14,28 @@
* limitations under the License.
*/
use super::select_from_stream;
use super::select_by_lambda_from_stream;
use super::ExecutionResult;
use super::JValuable;
use super::ValueAggregate;
use crate::execution_step::boxed_value::populate_tetraplet_with_lambda;
use crate::execution_step::ExecutionCtx;
use crate::execution_step::RcSecurityTetraplets;
use crate::JValue;
use crate::LambdaAST;
use crate::SecurityTetraplet;
use air_lambda_ast::format_ast;
use std::borrow::Cow;
use std::ops::Deref;
impl JValuable for std::cell::Ref<'_, Vec<ValueAggregate>> {
fn apply_lambda<'i>(&self, lambda: &LambdaAST<'_>, exec_ctx: &ExecutionCtx<'i>) -> ExecutionResult<&JValue> {
fn apply_lambda<'i>(
&self,
lambda: &LambdaAST<'_>,
exec_ctx: &ExecutionCtx<'i>,
) -> ExecutionResult<Cow<'_, JValue>> {
let stream_iter = self.iter().map(|r| r.result.deref());
let select_result = select_from_stream(stream_iter, lambda, exec_ctx)?;
let select_result = select_by_lambda_from_stream(stream_iter, lambda, exec_ctx)?;
Ok(select_result.result)
}
@ -40,13 +43,17 @@ impl JValuable for std::cell::Ref<'_, Vec<ValueAggregate>> {
&self,
lambda: &LambdaAST<'_>,
exec_ctx: &ExecutionCtx<'i>,
) -> ExecutionResult<(&JValue, SecurityTetraplet)> {
) -> ExecutionResult<(Cow<'_, JValue>, SecurityTetraplet)> {
let stream_iter = self.iter().map(|r| r.result.deref());
let select_result = select_from_stream(stream_iter, lambda, exec_ctx)?;
let select_result = select_by_lambda_from_stream(stream_iter, lambda, exec_ctx)?;
let tetraplet = &self[select_result.tetraplet_idx].tetraplet;
let mut tetraplet = tetraplet.as_ref().clone();
tetraplet.add_lambda(&format_ast(lambda));
let tetraplet = match select_result.tetraplet_idx {
Some(idx) => {
let tetraplet = &self[idx].tetraplet;
populate_tetraplet_with_lambda(tetraplet.as_ref().clone(), lambda)
}
None => SecurityTetraplet::new(exec_ctx.run_parameters.current_peer_id.to_string(), "", "", ""),
};
Ok((select_result.result, tetraplet))
}

View File

@ -27,7 +27,11 @@ use crate::SecurityTetraplet;
use std::borrow::Cow;
impl JValuable for () {
fn apply_lambda<'i>(&self, _lambda: &LambdaAST<'_>, _exec_ctx: &ExecutionCtx<'i>) -> ExecutionResult<&JValue> {
fn apply_lambda<'i>(
&self,
_lambda: &LambdaAST<'_>,
_exec_ctx: &ExecutionCtx<'i>,
) -> ExecutionResult<Cow<'_, JValue>> {
// applying lambda to an empty stream will produce a join behaviour
Err(LambdaApplierError(EmptyStream).into())
}
@ -36,7 +40,7 @@ impl JValuable for () {
&self,
_lambda: &LambdaAST<'_>,
_exec_ctx: &ExecutionCtx<'i>,
) -> ExecutionResult<(&JValue, SecurityTetraplet)> {
) -> ExecutionResult<(Cow<'_, JValue>, SecurityTetraplet)> {
// applying lambda to an empty stream will produce a join behaviour
Err(LambdaApplierError(EmptyStream).into())
}

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
use super::select_from_scalar;
use super::select_by_lambda_from_scalar;
use super::ExecutionResult;
use super::IterableItem;
use super::JValuable;
@ -24,12 +24,16 @@ use crate::execution_step::RcSecurityTetraplets;
use crate::JValue;
use crate::SecurityTetraplet;
use air_lambda_ast::format_ast;
use crate::execution_step::boxed_value::populate_tetraplet_with_lambda;
use std::borrow::Cow;
use std::ops::Deref;
impl<'ctx> JValuable for IterableItem<'ctx> {
fn apply_lambda<'i>(&self, lambda: &LambdaAST<'_>, exec_ctx: &ExecutionCtx<'i>) -> ExecutionResult<&JValue> {
fn apply_lambda<'i>(
&self,
lambda: &LambdaAST<'_>,
exec_ctx: &ExecutionCtx<'i>,
) -> ExecutionResult<Cow<'_, JValue>> {
use super::IterableItem::*;
let jvalue = match self {
@ -38,7 +42,7 @@ impl<'ctx> JValuable for IterableItem<'ctx> {
RcValue((jvalue, ..)) => jvalue.deref(),
};
let selected_value = select_from_scalar(jvalue, lambda.iter(), exec_ctx)?;
let selected_value = select_by_lambda_from_scalar(jvalue, lambda, exec_ctx)?;
Ok(selected_value)
}
@ -46,7 +50,7 @@ impl<'ctx> JValuable for IterableItem<'ctx> {
&self,
lambda: &LambdaAST<'_>,
exec_ctx: &ExecutionCtx<'i>,
) -> ExecutionResult<(&JValue, SecurityTetraplet)> {
) -> ExecutionResult<(Cow<'_, JValue>, SecurityTetraplet)> {
use super::IterableItem::*;
let (jvalue, tetraplet) = match self {
@ -55,9 +59,8 @@ impl<'ctx> JValuable for IterableItem<'ctx> {
RcValue((jvalue, tetraplet, _)) => (jvalue.deref(), tetraplet),
};
let selected_value = select_from_scalar(jvalue, lambda.iter(), exec_ctx)?;
let mut tetraplet = tetraplet.as_ref().clone();
tetraplet.add_lambda(&format_ast(lambda));
let selected_value = select_by_lambda_from_scalar(jvalue, lambda, exec_ctx)?;
let tetraplet = populate_tetraplet_with_lambda(tetraplet.as_ref().clone(), lambda);
Ok((selected_value, tetraplet))
}

View File

@ -14,24 +14,27 @@
* limitations under the License.
*/
use super::select_from_scalar;
use super::select_by_lambda_from_scalar;
use super::ExecutionResult;
use super::JValuable;
use super::LambdaAST;
use super::ValueAggregate;
use crate::execution_step::boxed_value::populate_tetraplet_with_lambda;
use crate::execution_step::ExecutionCtx;
use crate::execution_step::RcSecurityTetraplets;
use crate::JValue;
use crate::SecurityTetraplet;
use air_lambda_ast::format_ast;
use std::borrow::Cow;
use std::ops::Deref;
impl JValuable for ValueAggregate {
fn apply_lambda<'i>(&self, lambda: &LambdaAST<'_>, exec_ctx: &ExecutionCtx<'i>) -> ExecutionResult<&JValue> {
let selected_value = select_from_scalar(&self.result, lambda.iter(), exec_ctx)?;
fn apply_lambda<'i>(
&self,
lambda: &LambdaAST<'_>,
exec_ctx: &ExecutionCtx<'i>,
) -> ExecutionResult<Cow<'_, JValue>> {
let selected_value = select_by_lambda_from_scalar(&self.result, lambda, exec_ctx)?;
Ok(selected_value)
}
@ -39,10 +42,9 @@ impl JValuable for ValueAggregate {
&self,
lambda: &LambdaAST<'_>,
exec_ctx: &ExecutionCtx<'i>,
) -> ExecutionResult<(&JValue, SecurityTetraplet)> {
let selected_value = select_from_scalar(&self.result, lambda.iter(), exec_ctx)?;
let mut tetraplet = self.tetraplet.as_ref().clone();
tetraplet.add_lambda(&format_ast(lambda));
) -> ExecutionResult<(Cow<'_, JValue>, SecurityTetraplet)> {
let selected_value = select_by_lambda_from_scalar(&self.result, lambda, exec_ctx)?;
let tetraplet = populate_tetraplet_with_lambda(self.tetraplet.as_ref().clone(), lambda);
Ok((selected_value, tetraplet))
}

View File

@ -14,9 +14,10 @@
* limitations under the License.
*/
use super::select_from_stream;
use super::select_by_lambda_from_stream;
use super::ExecutionResult;
use super::JValuable;
use crate::execution_step::boxed_value::populate_tetraplet_with_lambda;
use crate::execution_step::boxed_value::Generation;
use crate::execution_step::boxed_value::Stream;
use crate::execution_step::ExecutionCtx;
@ -25,8 +26,6 @@ use crate::JValue;
use crate::LambdaAST;
use crate::SecurityTetraplet;
use air_lambda_ast::format_ast;
use std::borrow::Cow;
use std::ops::Deref;
@ -39,9 +38,13 @@ pub(crate) struct StreamJvaluableIngredients<'stream> {
// TODO: this will be deleted soon, because it would be impossible to use streams without
// canonicalization as an arg of a call
impl JValuable for StreamJvaluableIngredients<'_> {
fn apply_lambda<'i>(&self, lambda: &LambdaAST<'_>, exec_ctx: &ExecutionCtx<'i>) -> ExecutionResult<&JValue> {
fn apply_lambda<'i>(
&self,
lambda: &LambdaAST<'_>,
exec_ctx: &ExecutionCtx<'i>,
) -> ExecutionResult<Cow<'_, JValue>> {
let iter = self.iter()?.map(|v| v.result.deref());
let select_result = select_from_stream(iter, lambda, exec_ctx)?;
let select_result = select_by_lambda_from_stream(iter, lambda, exec_ctx)?;
Ok(select_result.result)
}
@ -50,14 +53,21 @@ impl JValuable for StreamJvaluableIngredients<'_> {
&self,
lambda: &LambdaAST<'_>,
exec_ctx: &ExecutionCtx<'i>,
) -> ExecutionResult<(&JValue, SecurityTetraplet)> {
) -> ExecutionResult<(Cow<'_, JValue>, SecurityTetraplet)> {
let iter = self.iter()?.map(|v| v.result.deref());
let select_result = select_from_stream(iter, lambda, exec_ctx)?;
let select_result = select_by_lambda_from_stream(iter, lambda, exec_ctx)?;
// unwrap is safe here because each value has a tetraplet and a lambda always returns a valid index
let resolved_call = self.iter()?.nth(select_result.tetraplet_idx).unwrap();
let mut tetraplet = resolved_call.tetraplet.as_ref().clone();
tetraplet.add_lambda(&format_ast(lambda));
let tetraplet = match select_result.tetraplet_idx {
Some(idx) => {
let resolved_call = self
.iter()?
.nth(idx)
.expect(crate::execution_step::TETRAPLET_IDX_CORRECT);
resolved_call.tetraplet.as_ref().clone()
}
None => SecurityTetraplet::new(exec_ctx.run_parameters.current_peer_id.to_string(), "", "", ""),
};
let tetraplet = populate_tetraplet_with_lambda(tetraplet, lambda);
Ok((select_result.result, tetraplet))
}

View File

@ -19,6 +19,7 @@ mod iterable;
mod jvaluable;
mod scalar;
mod stream;
mod utils;
mod variable;
pub(crate) use canon_stream::*;
@ -29,6 +30,7 @@ pub(crate) use scalar::ValueAggregate;
pub(crate) use stream::Generation;
pub(crate) use stream::Stream;
pub(crate) use stream::StreamIter;
pub(crate) use utils::populate_tetraplet_with_lambda;
pub(crate) use variable::Variable;
use super::ExecutionResult;

View File

@ -0,0 +1,31 @@
/*
* Copyright 2022 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use air_lambda_ast::LambdaAST;
use polyplets::SecurityTetraplet;
pub(crate) fn populate_tetraplet_with_lambda(
mut tetraplet: SecurityTetraplet,
lambda: &LambdaAST<'_>,
) -> SecurityTetraplet {
match lambda {
LambdaAST::ValuePath(_) => {
tetraplet.add_lambda(&lambda.to_string());
tetraplet
}
LambdaAST::Functor(_) => SecurityTetraplet::new("", "", "", &lambda.to_string()),
}
}

View File

@ -91,6 +91,10 @@ pub enum CatchableError {
/// Canon instruction can't canonicalize a stream since it's been found.
#[error("stream with name {0} wasn't defined, so canon instruction can't canonicalize it")]
StreamsForCanonNotFound(String),
/// This error type is occurred when the length functor applied to a value of non-array type.
#[error("the length functor could applied only to an array-like value, but it's applied to '{0}'")]
LengthFunctorAppliedToNotArray(JValue),
}
impl From<LambdaError> for Rc<CatchableError> {

View File

@ -16,23 +16,52 @@
use super::utils::*;
use super::LambdaError;
use crate::execution_step::CatchableError;
use crate::execution_step::ExecutionCtx;
use crate::execution_step::ExecutionResult;
use crate::lambda_to_execution_error;
use crate::ExecutionError;
use crate::JValue;
use crate::LambdaAST;
use air_lambda_ast::Functor;
use air_lambda_parser::ValueAccessor;
use non_empty_vec::NonEmpty;
use std::borrow::Cow;
use std::rc::Rc;
pub(crate) struct StreamSelectResult<'value> {
pub(crate) result: &'value JValue,
pub(crate) tetraplet_idx: usize,
pub(crate) result: Cow<'value, JValue>,
pub(crate) tetraplet_idx: Option<usize>,
}
pub(crate) fn select_from_stream<'value, 'i>(
pub(crate) fn select_by_lambda_from_stream<'value, 'i>(
stream: impl ExactSizeIterator<Item = &'value JValue> + 'value,
lambda: &LambdaAST<'_>,
exec_ctx: &ExecutionCtx<'i>,
) -> ExecutionResult<StreamSelectResult<'value>> {
match lambda {
LambdaAST::ValuePath(value_path) => select_by_path_from_stream(stream, value_path, exec_ctx),
LambdaAST::Functor(functor) => Ok(select_by_functor_from_stream(stream, functor)),
}
}
pub(crate) fn select_by_lambda_from_scalar<'value, 'i>(
value: &'value JValue,
lambda: &LambdaAST<'_>,
exec_ctx: &ExecutionCtx<'i>,
) -> ExecutionResult<Cow<'value, JValue>> {
match lambda {
LambdaAST::ValuePath(value_path) => select_by_path_from_scalar(value, value_path.iter(), exec_ctx),
LambdaAST::Functor(functor) => select_by_functor_from_scalar(value, functor).map(Cow::Owned),
}
}
fn select_by_path_from_stream<'value, 'i>(
stream: impl ExactSizeIterator<Item = &'value JValue> + 'value,
lambda: &NonEmpty<ValueAccessor<'_>>,
exec_ctx: &ExecutionCtx<'i>,
) -> ExecutionResult<StreamSelectResult<'value>> {
let (prefix, body) = lambda.split_first();
let idx = match prefix {
@ -55,16 +84,28 @@ pub(crate) fn select_from_stream<'value, 'i>(
.nth(idx as usize)
.ok_or(LambdaError::StreamNotHaveEnoughValues { stream_size, idx }))?;
let result = select_from_scalar(value, body.iter(), exec_ctx)?;
let select_result = StreamSelectResult::new(result, idx);
let result = select_by_path_from_scalar(value, body.iter(), exec_ctx)?;
let select_result = StreamSelectResult::from_cow(result, idx);
Ok(select_result)
}
pub(crate) fn select_from_scalar<'value, 'accessor, 'i>(
fn select_by_functor_from_stream<'value, 'i>(
stream: impl ExactSizeIterator<Item = &'value JValue> + 'value,
functor: &Functor,
) -> StreamSelectResult<'value> {
match functor {
Functor::Length => {
let result = serde_json::json!(stream.len());
StreamSelectResult::from_value(result)
}
}
}
fn select_by_path_from_scalar<'value, 'accessor, 'i>(
mut value: &'value JValue,
lambda: impl Iterator<Item = &'accessor ValueAccessor<'accessor>>,
exec_ctx: &ExecutionCtx<'i>,
) -> ExecutionResult<&'value JValue> {
) -> ExecutionResult<Cow<'value, JValue>> {
for accessor in lambda {
match accessor {
ValueAccessor::ArrayAccess { idx } => {
@ -81,14 +122,35 @@ pub(crate) fn select_from_scalar<'value, 'accessor, 'i>(
}
}
Ok(value)
Ok(Cow::Borrowed(value))
}
fn select_by_functor_from_scalar(value: &JValue, functor: &Functor) -> ExecutionResult<JValue> {
match functor {
Functor::Length => {
let length = value
.as_array()
.ok_or_else(|| {
ExecutionError::Catchable(Rc::new(CatchableError::LengthFunctorAppliedToNotArray(value.clone())))
})?
.len();
Ok(serde_json::json!(length))
}
}
}
impl<'value> StreamSelectResult<'value> {
pub(self) fn new(result: &'value JValue, tetraplet_idx: u32) -> Self {
pub(self) fn from_cow(result: Cow<'value, JValue>, tetraplet_idx: u32) -> Self {
Self {
result,
tetraplet_idx: tetraplet_idx as usize,
tetraplet_idx: Some(tetraplet_idx as usize),
}
}
pub(self) fn from_value(result: JValue) -> Self {
Self {
result: Cow::Owned(result),
tetraplet_idx: None,
}
}
}

View File

@ -22,8 +22,8 @@ pub use errors::LambdaError;
pub(crate) type LambdaResult<T> = std::result::Result<T, LambdaError>;
pub(crate) use applier::select_from_scalar;
pub(crate) use applier::select_from_stream;
pub(crate) use applier::select_by_lambda_from_scalar;
pub(crate) use applier::select_by_lambda_from_stream;
#[macro_export]
macro_rules! lambda_to_execution_error {

View File

@ -24,6 +24,8 @@ mod resolver;
const PEEK_ALLOWED_ON_NON_EMPTY: &str = "peek always return elements inside fold,\
this guaranteed by implementation of next and avoiding empty folds";
const TETRAPLET_IDX_CORRECT: &str = "selects always return a correct index inside stream";
pub use errors::CatchableError;
pub use errors::ExecutionError;
pub use errors::UncatchableError;

View File

@ -18,7 +18,7 @@ use super::RcSecurityTetraplets;
use crate::execution_step::boxed_value::JValuable;
use crate::execution_step::boxed_value::Variable;
use crate::execution_step::execution_context::ExecutionCtx;
use crate::execution_step::lambda_applier::select_from_scalar;
use crate::execution_step::lambda_applier::select_by_lambda_from_scalar;
use crate::execution_step::ExecutionResult;
use crate::JValue;
use crate::LambdaAST;
@ -71,8 +71,8 @@ pub(crate) fn prepare_last_error<'i>(
let LastError { error, tetraplet } = ctx.last_error();
let jvalue = match error_accessor {
Some(error_accessor) => select_from_scalar(error.as_ref(), error_accessor.iter(), ctx)?,
None => error.as_ref(),
Some(error_accessor) => select_by_lambda_from_scalar(error.as_ref(), error_accessor, ctx)?.into_owned(),
None => error.as_ref().clone(),
};
let tetraplets = match tetraplet {
@ -84,7 +84,7 @@ pub(crate) fn prepare_last_error<'i>(
}
};
Ok((jvalue.clone(), tetraplets))
Ok((jvalue, tetraplets))
}
#[tracing::instrument(level = "trace", skip(ctx))]
@ -157,5 +157,5 @@ pub(crate) fn apply_lambda<'i>(
let (jvalue, tetraplet) = resolved.apply_lambda_with_tetraplets(lambda, exec_ctx)?;
// it's known that apply_lambda_with_tetraplets returns vec of one value
Ok((jvalue.clone(), tetraplet))
Ok((jvalue.into_owned(), tetraplet))
}

View File

@ -0,0 +1,202 @@
/*
* Copyright 2022 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use air::CatchableError;
use air_test_framework::TestExecutor;
use air_test_utils::prelude::*;
use std::cell::RefCell;
#[test]
fn length_functor_for_array_scalar() {
let script = r#"
(seq
(call %init_peer_id% ("" "") [] variable) ; ok = [1,1,1]
(call %init_peer_id% ("" "") [variable.length]) ; behaviour = echo
)
"#;
let init_peer_id = "init_peer_id";
let executor = TestExecutor::simple(TestRunParameters::from_init_peer_id(init_peer_id), &script)
.expect("invalid test AIR script");
let result = executor.execute_one(init_peer_id).unwrap();
let actual_trace = trace_from_result(&result);
let expected_trace = vec![
executed_state::scalar(json!([1, 1, 1])),
executed_state::scalar_number(3),
];
assert_eq!(actual_trace, expected_trace);
}
#[test]
fn length_functor_for_non_array_scalar() {
let result_jvalue = "string_jvalue";
let script = f!(r#"
(seq
(call %init_peer_id% ("" "") [] variable) ; ok = "{result_jvalue}"
(call %init_peer_id% ("" "") [variable.length]) ; behaviour = echo
)
"#);
let init_peer_id = "init_peer_id";
let executor = TestExecutor::simple(TestRunParameters::from_init_peer_id(init_peer_id), &script)
.expect("invalid test AIR script");
let result = executor.execute_one(init_peer_id).unwrap();
check_error(
&result,
CatchableError::LengthFunctorAppliedToNotArray(json!(result_jvalue)),
);
}
#[test]
fn length_functor_for_stream() {
let script = r#"
(seq
(seq
(ap 1 $stream)
(ap 1 $stream))
(call %init_peer_id% ("" "") [$stream.length]) ; behaviour = echo
)
"#;
let init_peer_id = "init_peer_id";
let executor = TestExecutor::simple(TestRunParameters::from_init_peer_id(init_peer_id), &script)
.expect("invalid test AIR script");
let result = executor.execute_one(init_peer_id).unwrap();
let actual_trace = trace_from_result(&result);
let expected_trace = vec![
executed_state::ap(Some(0)),
executed_state::ap(Some(0)),
executed_state::scalar_number(2),
];
assert_eq!(actual_trace, expected_trace);
}
#[test]
fn length_functor_for_empty_stream() {
let script = r#"
(new $stream
(call %init_peer_id% ("" "") [$stream.length]) ; behaviour = echo
)
"#;
let init_peer_id = "init_peer_id";
let executor = TestExecutor::simple(TestRunParameters::from_init_peer_id(init_peer_id), &script)
.expect("invalid test AIR script");
let result = executor.execute_one(init_peer_id).unwrap();
let actual_trace = trace_from_result(&result);
let expected_trace = vec![executed_state::scalar_number(0)];
assert_eq!(actual_trace, expected_trace);
}
#[test]
fn length_functor_for_canon_stream() {
let script = r#"
(seq
(seq
(ap 1 $stream)
(ap 1 $stream))
(seq
(canon %init_peer_id% $stream #canon_stream)
(call %init_peer_id% ("" "") [#canon_stream.length]) ; behaviour = echo
)
)
"#;
let init_peer_id = "init_peer_id";
let executor = TestExecutor::simple(TestRunParameters::from_init_peer_id(init_peer_id), &script)
.expect("invalid test AIR script");
let result = executor.execute_one(init_peer_id).unwrap();
let actual_trace = trace_from_result(&result);
let expected_trace = vec![
executed_state::ap(Some(0)),
executed_state::ap(Some(0)),
executed_state::canon(vec![0.into(), 1.into()]),
executed_state::scalar_number(2),
];
assert_eq!(actual_trace, expected_trace);
}
#[test]
fn length_functor_for_empty_canon_stream() {
let script = r#"
(new $stream
(seq
(canon %init_peer_id% $stream #canon_stream)
(call %init_peer_id% ("" "") [#canon_stream.length]) ; behaviour = echo
)
)
"#;
let init_peer_id = "init_peer_id";
let executor = TestExecutor::simple(TestRunParameters::from_init_peer_id(init_peer_id), &script)
.expect("invalid test AIR script");
let result = executor.execute_one(init_peer_id).unwrap();
let actual_trace = trace_from_result(&result);
let expected_trace = vec![executed_state::canon(vec![]), executed_state::scalar_number(0)];
assert_eq!(actual_trace, expected_trace);
}
#[test]
fn functor_dont_influence_tetraplet() {
let set_variable_peer_id = "set_variable_peer_id";
let set_variable_peer_result = json!({"field": [1,2,3]});
let mut set_variable_vm = create_avm(
set_variable_call_service(set_variable_peer_result.clone()),
set_variable_peer_id,
);
let tetraplet_catcher_peer_id = "tetraplet_catcher_peer_id";
let (call_service, actual_tetraplet) = tetraplet_host_function(echo_call_service());
let mut tetraplet_catcher_vm = create_avm(call_service, tetraplet_catcher_peer_id);
let script = f!(r#"
(seq
(call "{set_variable_peer_id}" ("" "") [] scalar)
(seq
(ap scalar.$.field field)
(seq
(ap field.length length)
(call "{tetraplet_catcher_peer_id}" ("" "") [length])
)
)
)
"#);
let result = checked_call_vm!(set_variable_vm, <_>::default(), &script, "", "");
let result = checked_call_vm!(tetraplet_catcher_vm, <_>::default(), &script, "", result.data);
let actual_trace = trace_from_result(&result);
let expected_tetraplet = RefCell::new(vec![vec![SecurityTetraplet::new("", "", "", ".length")]]);
assert_eq!(actual_tetraplet.as_ref(), &expected_tetraplet);
let expected_trace = vec![
executed_state::scalar(set_variable_peer_result),
executed_state::scalar_number(3),
];
assert_eq!(actual_trace, expected_trace);
}

View File

@ -15,4 +15,5 @@
*/
mod flattening;
mod functors;
mod lambda;

View File

@ -134,7 +134,7 @@ fn fold_json_path() {
peer_pk: set_variable_vm_peer_id,
service_id,
function_name,
json_path: String::from(".args"),
json_path: String::from(".$.args"),
};
let second_arg_tetraplet = SecurityTetraplet {
@ -180,14 +180,14 @@ fn check_tetraplet_works_correctly() {
peer_pk: set_variable_vm_peer_id.clone(),
service_id: service_id.clone(),
function_name: function_name.clone(),
json_path: String::from(".args"),
json_path: String::from(".$.args"),
};
let second_arg_tetraplet = SecurityTetraplet {
peer_pk: set_variable_vm_peer_id.clone(),
service_id,
function_name,
json_path: String::from(".args.[0]"),
json_path: String::from(".$.args.[0]"),
};
let expected_tetraplets = vec![vec![first_arg_tetraplet], vec![second_arg_tetraplet]];

View File

@ -249,7 +249,7 @@ fn ap_canon_stream_with_lambda() {
vm_1_peer_id,
service_name,
function_name,
".[1]",
".$.[1]",
)]]);
assert_eq!(tetraplet_checker.as_ref(), &expected_tetraplet);
}

View File

@ -147,12 +147,9 @@ impl From<&Number> for serde_json::Value {
}
}
fn display_last_error(
f: &mut fmt::Formatter,
last_error_accessor: &Option<LambdaAST>,
) -> fmt::Result {
match last_error_accessor {
Some(accessor) => write!(f, "%last_error%.${}", air_lambda_ast::format_ast(accessor)),
fn display_last_error(f: &mut fmt::Formatter, lambda_ast: &Option<LambdaAST>) -> fmt::Result {
match lambda_ast {
Some(lambda_ast) => write!(f, "%last_error%{}", lambda_ast),
None => write!(f, "%last_error%"),
}
}

View File

@ -16,7 +16,6 @@
use super::*;
use air_lambda_ast::format_ast;
use std::fmt;
impl fmt::Display for Instruction<'_> {
@ -77,7 +76,7 @@ impl fmt::Display for Fail<'_> {
error_message,
} => write!(f, r#"fail {} "{}""#, ret_code, error_message),
Fail::CanonStream { name, lambda, .. } => {
write!(f, "fail {}.$.{}", name, format_ast(lambda))
write!(f, "fail {}.$.{}", name, lambda)
}
Fail::LastError => write!(f, "fail %last_error%"),
}

View File

@ -15,14 +15,17 @@
*/
use crate::ast::Value;
use air_lambda_ast::LambdaAST;
use air_lambda_ast::ValueAccessor;
use non_empty_vec::NonEmpty;
#[test]
// https://github.com/fluencelabs/aquavm/issues/263
fn issue_263() {
let val = Value::LastError(Some(NonEmpty::new(ValueAccessor::FieldAccessByName {
let val = Value::LastError(Some(
LambdaAST::try_from_accessors(vec![ValueAccessor::FieldAccessByName {
field_name: "message",
})));
}])
.unwrap(),
));
assert_eq!(val.to_string(), "%last_error%.$.message");
}

View File

@ -18,8 +18,6 @@ use super::*;
use air_lambda_parser::LambdaAST;
use air_lambda_parser::ValueAccessor;
use std::convert::TryFrom;
impl<'i> ScalarWithLambda<'i> {
pub fn new(name: &'i str, lambda: Option<LambdaAST<'i>>, position: usize) -> Self {
Self {
@ -29,12 +27,12 @@ impl<'i> ScalarWithLambda<'i> {
}
}
pub(crate) fn from_raw_lambda(
pub(crate) fn from_value_path(
name: &'i str,
lambda: Vec<ValueAccessor<'i>>,
accessors: Vec<ValueAccessor<'i>>,
position: usize,
) -> Self {
let lambda = LambdaAST::try_from(lambda).ok();
let lambda = LambdaAST::try_from_accessors(accessors).ok();
Self {
name,
lambda,
@ -53,12 +51,12 @@ impl<'i> StreamWithLambda<'i> {
}
#[allow(dead_code)]
pub(crate) fn from_raw_lambda(
pub(crate) fn from_value_path(
name: &'i str,
lambda: Vec<ValueAccessor<'i>>,
accessors: Vec<ValueAccessor<'i>>,
position: usize,
) -> Self {
let lambda = LambdaAST::try_from(lambda).ok();
let lambda = LambdaAST::try_from_accessors(accessors).ok();
Self {
name,
lambda,
@ -155,12 +153,12 @@ impl<'i> VariableWithLambda<'i> {
}
#[allow(dead_code)]
pub(crate) fn from_raw_lambda_scalar(
pub(crate) fn from_raw_value_path(
name: &'i str,
lambda: Vec<ValueAccessor<'i>>,
position: usize,
) -> Self {
let scalar = ScalarWithLambda::from_raw_lambda(name, lambda, position);
let scalar = ScalarWithLambda::from_value_path(name, lambda, position);
Self::Scalar(scalar)
}
@ -170,7 +168,7 @@ impl<'i> VariableWithLambda<'i> {
lambda: Vec<ValueAccessor<'i>>,
position: usize,
) -> Self {
let stream = StreamWithLambda::from_raw_lambda(name, lambda, position);
let stream = StreamWithLambda::from_value_path(name, lambda, position);
Self::Stream(stream)
}
}

View File

@ -15,7 +15,6 @@
*/
use super::*;
use air_lambda_ast::format_ast;
use std::fmt;
impl fmt::Display for Scalar<'_> {
@ -27,7 +26,7 @@ impl fmt::Display for Scalar<'_> {
impl fmt::Display for ScalarWithLambda<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &self.lambda {
Some(lambda) => write!(f, "{}.${}", self.name, format_ast(lambda)),
Some(lambda) => write!(f, "{}{}", self.name, lambda),
None => write!(f, "{}", self.name),
}
}
@ -48,7 +47,7 @@ impl fmt::Display for CanonStream<'_> {
impl fmt::Display for StreamWithLambda<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &self.lambda {
Some(lambda) => write!(f, "{}.${}", self.name, format_ast(lambda)),
Some(lambda) => write!(f, "{}{}", self.name, lambda),
None => write!(f, "{}", self.name),
}
}
@ -57,7 +56,7 @@ impl fmt::Display for StreamWithLambda<'_> {
impl fmt::Display for CanonStreamWithLambda<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &self.lambda {
Some(lambda) => write!(f, "{}.${}", self.name, format_ast(lambda)),
Some(lambda) => write!(f, "{}{}", self.name, lambda),
None => write!(f, "{}", self.name),
}
}

View File

@ -207,7 +207,6 @@ fn parse_last_error(input: &str, start_pos: usize) -> LexerResult<Token<'_>> {
return Ok(Token::LastError);
}
let last_error_size = last_error_size + 2;
if input.len() <= last_error_size {
return Err(LexerError::lambda_parser_error(
start_pos + last_error_size..start_pos + input.len(),

View File

@ -314,9 +314,8 @@ impl<'input> CallVariableParser<'input> {
}
fn try_to_variable_and_lambda(&self, lambda_start_pos: usize) -> LexerResult<Token<'input>> {
// +2 to ignore ".$" prefix
let lambda =
crate::parse_lambda(&self.string_to_parse[lambda_start_pos + 2..]).map_err(|e| {
crate::parse_lambda(&self.string_to_parse[lambda_start_pos..]).map_err(|e| {
LexerError::lambda_parser_error(
self.start_pos + lambda_start_pos..self.start_pos + self.string_to_parse.len(),
e.to_string(),

View File

@ -22,11 +22,10 @@ use super::Token;
use air_lambda_parser::LambdaAST;
use air_lambda_parser::ValueAccessor;
use air_lambda_ast::Functor;
use fstrings::f;
use fstrings::format_args_f;
use std::convert::TryFrom;
fn run_lexer(input: &str) -> Vec<Spanned<Token<'_>, usize, LexerError>> {
let lexer = AIRLexer::new(input);
lexer.collect()
@ -194,6 +193,80 @@ fn stream() {
);
}
#[test]
fn canon_stream() {
const CANON_STREAM: &str = "#stream____asdasd";
lexer_test(
CANON_STREAM,
Single(Ok((
0,
Token::CanonStream {
name: CANON_STREAM,
position: 0,
},
CANON_STREAM.len(),
))),
);
}
#[test]
fn stream_with_functor() {
let stream_name = "$stream";
let stream_with_functor: String = f!("{stream_name}.length");
lexer_test(
&stream_with_functor,
Single(Ok((
0,
Token::StreamWithLambda {
name: stream_name,
lambda: LambdaAST::Functor(Functor::Length),
position: 0,
},
stream_with_functor.len(),
))),
);
}
#[test]
fn canon_stream_with_functor() {
let canon_stream_name = "#canon_stream";
let canon_stream_with_functor: String = f!("{canon_stream_name}.length");
lexer_test(
&canon_stream_with_functor,
Single(Ok((
0,
Token::CanonStreamWithLambda {
name: canon_stream_name,
lambda: LambdaAST::Functor(Functor::Length),
position: 0,
},
canon_stream_with_functor.len(),
))),
);
}
#[test]
fn scalar_with_functor() {
let scalar_name = "scalar";
let scalar_with_functor: String = f!("{scalar_name}.length");
lexer_test(
&scalar_with_functor,
Single(Ok((
0,
Token::ScalarWithLambda {
name: scalar_name,
lambda: LambdaAST::Functor(Functor::Length),
position: 0,
},
scalar_with_functor.len(),
))),
);
}
#[test]
fn string_literal() {
const STRING_LITERAL: &str = r#""some_string""#;
@ -308,7 +381,7 @@ fn lambda() {
0,
Token::ScalarWithLambda {
name: "value",
lambda: LambdaAST::try_from(vec![
lambda: LambdaAST::try_from_accessors(vec![
ValueAccessor::FieldAccessByName {
field_name: "field",
},
@ -415,7 +488,7 @@ fn last_error_instruction() {
const LAST_ERROR: &str = r#"%last_error%.$.instruction"#;
let token = Token::LastErrorWithLambda(
LambdaAST::try_from(vec![ValueAccessor::FieldAccessByName {
LambdaAST::try_from_accessors(vec![ValueAccessor::FieldAccessByName {
field_name: "instruction",
}])
.unwrap(),
@ -429,7 +502,7 @@ fn last_error_message() {
const LAST_ERROR: &str = r#"%last_error%.$.message"#;
let token = Token::LastErrorWithLambda(
LambdaAST::try_from(vec![ValueAccessor::FieldAccessByName {
LambdaAST::try_from_accessors(vec![ValueAccessor::FieldAccessByName {
field_name: "message",
}])
.unwrap(),
@ -442,7 +515,7 @@ fn last_error_peer_id() {
const LAST_ERROR: &str = r#"%last_error%.$.peer_id"#;
let token = Token::LastErrorWithLambda(
LambdaAST::try_from(vec![ValueAccessor::FieldAccessByName {
LambdaAST::try_from_accessors(vec![ValueAccessor::FieldAccessByName {
field_name: "peer_id",
}])
.unwrap(),
@ -455,7 +528,7 @@ fn last_error_non_standard_field() {
const LAST_ERROR: &str = r#"%last_error%.$.asdasd"#;
let token = Token::LastErrorWithLambda(
LambdaAST::try_from(vec![ValueAccessor::FieldAccessByName {
LambdaAST::try_from_accessors(vec![ValueAccessor::FieldAccessByName {
field_name: "asdasd",
}])
.unwrap(),

View File

@ -22,8 +22,6 @@ use air_lambda_ast::{LambdaAST, ValueAccessor};
use fstrings::f;
use fstrings::format_args_f;
use std::convert::TryFrom;
#[test]
fn ap_with_literal() {
let source_code = r#"
@ -78,7 +76,7 @@ fn ap_with_last_error() {
let actual = parse(source_code);
let expected = ap(
ApArgument::LastError(Some(
LambdaAST::try_from(vec![ValueAccessor::FieldAccessByName {
LambdaAST::try_from_accessors(vec![ValueAccessor::FieldAccessByName {
field_name: "message",
}])
.unwrap(),
@ -178,7 +176,9 @@ fn ap_with_canon_stream_with_lambda() {
let expected = ap(
ApArgument::CanonStream(CanonStreamWithLambda::new(
canon_stream,
Some(LambdaAST::try_from(vec![ValueAccessor::ArrayAccess { idx: 0 }]).unwrap()),
Some(
LambdaAST::try_from_accessors(vec![ValueAccessor::ArrayAccess { idx: 0 }]).unwrap(),
),
13,
)),
ApResult::Scalar(Scalar::new(scalar, 33)),

View File

@ -24,7 +24,6 @@ use fstrings::f;
use fstrings::format_args_f;
use lalrpop_util::ParseError;
use std::convert::TryFrom;
use std::rc::Rc;
#[test]
@ -35,7 +34,7 @@ fn parse_json_path() {
let instruction = parse(source_code);
let expected = call(
CallInstrValue::Variable(VariableWithLambda::from_raw_lambda_scalar(
CallInstrValue::Variable(VariableWithLambda::from_raw_value_path(
"peer_id",
vec![ValueAccessor::FieldAccessByName { field_name: "a" }],
15,
@ -188,7 +187,7 @@ fn parse_lambda_complex() {
let instruction = parse(source_code);
let expected = seq(
call(
CallInstrValue::Variable(VariableWithLambda::from_raw_lambda_scalar(
CallInstrValue::Variable(VariableWithLambda::from_raw_value_path(
"m",
vec![ValueAccessor::ArrayAccess { idx: 1 }],
32,
@ -199,7 +198,7 @@ fn parse_lambda_complex() {
CallOutputValue::Scalar(Scalar::new("void", 75)),
),
call(
CallInstrValue::Variable(VariableWithLambda::from_raw_lambda_scalar(
CallInstrValue::Variable(VariableWithLambda::from_raw_value_path(
"m",
vec![
ValueAccessor::FieldAccessByName { field_name: "abc" },
@ -232,7 +231,7 @@ fn parse_lambda_with_scalars_complex() {
let instruction = parse(source_code);
let expected = seq(
call(
CallInstrValue::Variable(VariableWithLambda::from_raw_lambda_scalar(
CallInstrValue::Variable(VariableWithLambda::from_raw_value_path(
"m",
vec![
ValueAccessor::ArrayAccess { idx: 1 },
@ -251,7 +250,7 @@ fn parse_lambda_with_scalars_complex() {
CallOutputValue::Scalar(Scalar::new("void", 97)),
),
call(
CallInstrValue::Variable(VariableWithLambda::from_raw_lambda_scalar(
CallInstrValue::Variable(VariableWithLambda::from_raw_value_path(
"m",
vec![
ValueAccessor::FieldAccessByName { field_name: "abc" },
@ -286,7 +285,7 @@ fn json_path_square_braces() {
"#;
let instruction = parse(source_code);
let expected = call(
CallInstrValue::Variable(VariableWithLambda::from_raw_lambda_scalar(
CallInstrValue::Variable(VariableWithLambda::from_raw_value_path(
"u",
vec![ValueAccessor::FieldAccessByName {
field_name: "peer_id",
@ -296,7 +295,7 @@ fn json_path_square_braces() {
CallInstrValue::Literal("return"),
CallInstrValue::Literal(""),
Rc::new(vec![
Value::Variable(VariableWithLambda::from_raw_lambda_scalar(
Value::Variable(VariableWithLambda::from_raw_value_path(
"u",
vec![
ValueAccessor::ArrayAccess { idx: 1 },
@ -307,7 +306,7 @@ fn json_path_square_braces() {
],
43,
)),
Value::Variable(VariableWithLambda::from_raw_lambda_scalar(
Value::Variable(VariableWithLambda::from_raw_value_path(
"u",
vec![ValueAccessor::FieldAccessByName { field_name: "name" }],
64,
@ -472,7 +471,7 @@ fn canon_stream_with_lambda_in_triplet() {
let expected = call(
CallInstrValue::Variable(VariableWithLambda::canon_stream_wl(
canon_stream,
LambdaAST::try_from(vec![
LambdaAST::try_from_accessors(vec![
ValueAccessor::ArrayAccess { idx: 0 },
ValueAccessor::FieldAccessByName { field_name: "path" },
])

View File

@ -21,8 +21,6 @@ use crate::ast::ScalarWithLambda;
use air_lambda_ast::LambdaAST;
use air_lambda_ast::ValueAccessor;
use std::convert::TryFrom;
#[test]
fn parse_fail_last_error() {
let source_code = r#"
@ -62,7 +60,7 @@ fn parse_fail_scalar_with_lambda() {
let expected = fail_scalar(ScalarWithLambda::new(
"scalar",
Some(
LambdaAST::try_from(vec![ValueAccessor::FieldAccessByName {
LambdaAST::try_from_accessors(vec![ValueAccessor::FieldAccessByName {
field_name: "field_accessor",
}])
.unwrap(),

View File

@ -290,7 +290,7 @@ fn fold_json_path() {
let instruction = parse(source_code);
let expected = fold_scalar_variable(
ScalarWithLambda::from_raw_lambda(
ScalarWithLambda::from_value_path(
"members",
vec![ValueAccessor::ArrayAccess { idx: 123321 }],
33,
@ -358,7 +358,7 @@ fn comments() {
"#;
let instruction = parse(source_code);
let expected = fold_scalar_variable(
ScalarWithLambda::from_raw_lambda(
ScalarWithLambda::from_value_path(
"members",
vec![
ValueAccessor::FieldAccessByName {

View File

@ -22,8 +22,6 @@ use air_lambda_ast::{LambdaAST, ValueAccessor};
use fstrings::f;
use fstrings::format_args_f;
use std::convert::TryFrom;
#[test]
fn parse_match() {
let source_code = r#"
@ -54,7 +52,7 @@ fn parse_match_with_canon_stream() {
let expected = match_(
Value::Variable(VariableWithLambda::canon_stream_wl(
canon_stream,
LambdaAST::try_from(vec![ValueAccessor::ArrayAccess { idx: 0 }]).unwrap(),
LambdaAST::try_from_accessors(vec![ValueAccessor::ArrayAccess { idx: 0 }]).unwrap(),
16,
)),
Value::Variable(VariableWithLambda::scalar("v2", 36)),

View File

@ -207,7 +207,12 @@ impl<'i> VariableValidator<'i> {
}
fn met_lambda(&mut self, lambda: &LambdaAST<'i>, span: Span) {
for accessor in lambda.iter() {
let accessors = match lambda {
LambdaAST::ValuePath(accessors) => accessors,
LambdaAST::Functor(_) => return,
};
for accessor in accessors.iter() {
match accessor {
&ValueAccessor::FieldAccessByScalar { scalar_name } => {
self.met_variable_name(scalar_name, span)

View File

@ -15,6 +15,7 @@ path = "src/lib.rs"
[dependencies]
non-empty-vec = { version = "0.2.3", features = ["serde"] }
itertools = "0.10.0"
serde = { version = "1.0.144", features = ["rc", "derive"] }
serde_json = "1.0.85"

View File

@ -14,13 +14,23 @@
* limitations under the License.
*/
mod impls;
mod traits;
use non_empty_vec::NonEmpty;
use serde::Deserialize;
use serde::Serialize;
pub type LambdaAST<'input> = NonEmpty<ValueAccessor<'input>>;
// TODO: rename lambda to smth more appropriate
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
pub enum LambdaAST<'input> {
/// Various functors that could applied to a value.
Functor(Functor),
/// Each value in AIR could be represented as a tree and
/// this variant acts as a path in such trees.
#[serde(borrow)]
ValuePath(NonEmpty<ValueAccessor<'input>>),
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
pub enum ValueAccessor<'input> {
@ -37,3 +47,10 @@ pub enum ValueAccessor<'input> {
// on the very first one. Although, this variant is guaranteed not to be present in a lambda.
Error,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
pub enum Functor {
/// Returns a length of a value if this value has array type (json array or canon stream)
/// or a error if not.
Length,
}

View File

@ -0,0 +1,37 @@
/*
* Copyright 2022 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use crate::Functor;
use crate::LambdaAST;
use crate::ValueAccessor;
pub use non_empty_vec::EmptyError;
use non_empty_vec::NonEmpty;
use std::convert::TryFrom;
impl<'input> LambdaAST<'input> {
pub fn try_from_accessors(accessors: Vec<ValueAccessor<'input>>) -> Result<Self, EmptyError> {
let value_path = NonEmpty::try_from(accessors)?;
let lambda_ast = Self::ValuePath(value_path);
Ok(lambda_ast)
}
pub fn from_functor(functor: Functor) -> Self {
Self::Functor(functor)
}
}

View File

@ -15,18 +15,40 @@
*/
use super::*;
use itertools::Itertools;
use std::fmt;
impl fmt::Display for LambdaAST<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use LambdaAST::*;
match self {
Functor(functor) => write!(f, ".{}", functor),
ValuePath(value_path) => write!(f, ".$.{}", value_path.iter().join(".")),
}
}
}
impl fmt::Display for ValueAccessor<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use ValueAccessor::*;
match self {
ArrayAccess { idx } => write!(f, ".[{}]", idx),
FieldAccessByName { field_name } => write!(f, ".{}", field_name),
FieldAccessByScalar { scalar_name } => write!(f, ".[{}]", scalar_name),
ArrayAccess { idx } => write!(f, "[{}]", idx),
FieldAccessByName { field_name } => write!(f, "{}", field_name),
FieldAccessByScalar { scalar_name } => write!(f, "[{}]", scalar_name),
Error => write!(f, "a parser error occurred while parsing lambda expression"),
}
}
}
impl fmt::Display for Functor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use Functor::*;
match self {
Length => write!(f, "length"),
}
}
}

View File

@ -29,12 +29,3 @@
mod ast;
pub use ast::*;
pub fn format_ast(lambda_ast: &LambdaAST<'_>) -> String {
let mut formatted_ast = String::new();
for accessor in lambda_ast.iter() {
formatted_ast.push_str(&accessor.to_string());
}
formatted_ast
}

View File

@ -28,10 +28,10 @@
mod parser;
pub use parser::parse;
pub use parser::AccessorsLexer;
pub use parser::LambdaParser;
pub use parser::LambdaASTLexer;
pub use parser::LambdaParserError;
pub use parser::LexerError;
pub use air_lambda_ast::Functor;
pub use air_lambda_ast::LambdaAST;
pub use air_lambda_ast::ValueAccessor;

View File

@ -26,8 +26,8 @@ pub enum LambdaParserError<'input> {
#[error(transparent)]
LexerError(#[from] LexerError),
#[error("provided lambda expression doesn't contain any accessor")]
EmptyLambda,
#[error(transparent)]
LambdaError(#[from] IncorrectLambdaError),
#[error("{0:?}")]
ParseError(ParseError<usize, Token<'input>, LexerError>),
@ -36,6 +36,17 @@ pub enum LambdaParserError<'input> {
RecoveryErrors(Vec<ErrorRecovery<usize, Token<'input>, LexerError>>),
}
#[derive(ThisError, Debug, Clone, PartialEq, Eq)]
pub enum IncorrectLambdaError {
#[error("provided lambda expression doesn't contain any accessor")]
EmptyLambda,
#[error(
"normally, this error shouldn't occur, it's an internal error of a parser implementation"
)]
InternalError,
}
impl<'input> From<ParseError<usize, Token<'input>, LexerError>> for LambdaParserError<'input> {
fn from(e: ParseError<usize, Token<'input>, LexerError>) -> Self {
Self::ParseError(e)

View File

@ -14,36 +14,54 @@
* limitations under the License.
*/
use super::lexer::AccessorsLexer;
use super::va_lambda;
use super::LambdaParserError;
use super::lexer::LambdaASTLexer;
use super::LambdaParserResult;
use crate::parser::errors::IncorrectLambdaError;
use crate::parser::va_lambda::RawLambdaASTParser;
use crate::Functor;
use crate::LambdaAST;
use crate::ValueAccessor;
use va_lambda::LambdaParser;
use std::convert::TryFrom;
use std::convert::TryInto;
// Caching parser to cache internal regexes, which are expensive to instantiate
// See also https://github.com/lalrpop/lalrpop/issues/269
thread_local!(static PARSER: LambdaParser = LambdaParser::new());
thread_local!(static PARSER: RawLambdaASTParser = RawLambdaASTParser::new());
/// Parse AIR `source_code` to `Box<Instruction>`
/// Parse AIR lambda ast to `LambdaAST`
pub fn parse(lambda: &str) -> LambdaParserResult<'_, LambdaAST> {
PARSER.with(|parser| {
let mut errors = Vec::new();
let lexer = AccessorsLexer::new(lambda);
let lexer = LambdaASTLexer::new(lambda);
let result = parser.parse(lambda, &mut errors, lexer);
match result {
Ok(accessors) if errors.is_empty() => try_to_lambda(accessors),
Ok(lambda_ast) if errors.is_empty() => lambda_ast.try_into().map_err(Into::into),
Ok(_) => Err(errors.into()),
Err(e) => Err(e.into()),
}
})
}
fn try_to_lambda(accessors: Vec<ValueAccessor>) -> LambdaParserResult<'_, LambdaAST> {
LambdaAST::try_from(accessors).or(Err(LambdaParserError::EmptyLambda))
impl<'input> TryFrom<RawLambdaAST<'input>> for LambdaAST<'input> {
type Error = IncorrectLambdaError;
fn try_from(raw_lambda_ast: RawLambdaAST<'input>) -> Result<Self, Self::Error> {
match raw_lambda_ast {
RawLambdaAST::ValuePath(accessors) => {
LambdaAST::try_from_accessors(accessors).or(Err(IncorrectLambdaError::EmptyLambda))
}
RawLambdaAST::Functor(functor) => Ok(LambdaAST::from_functor(functor)),
RawLambdaAST::Error => Err(IncorrectLambdaError::InternalError),
}
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub(crate) enum RawLambdaAST<'input> {
Functor(Functor),
ValuePath(Vec<ValueAccessor<'input>>),
// needed to allow parser catch all errors from a lambda expression without stopping on the very first one.
Error,
}

View File

@ -16,21 +16,24 @@
use super::errors::LexerError;
use super::token::Token;
use crate::parser::lexer::is_air_alphanumeric;
use std::iter::Peekable;
use std::str::CharIndices;
const ARRAY_IDX_BASE: u32 = 10;
const LENGTH_FUNCTOR: &str = ".length";
const VALUE_PATH_STARTER: &str = ".$";
pub type Spanned<Token, Loc, Error> = Result<(Loc, Token, Loc), Error>;
pub struct AccessorsLexer<'input> {
pub struct LambdaASTLexer<'input> {
input: &'input str,
chars: Peekable<CharIndices<'input>>,
is_first_token: bool,
}
impl<'input> Iterator for AccessorsLexer<'input> {
impl<'input> Iterator for LambdaASTLexer<'input> {
type Item = Spanned<Token<'input>, usize, LexerError>;
fn next(&mut self) -> Option<Self::Item> {
@ -38,20 +41,30 @@ impl<'input> Iterator for AccessorsLexer<'input> {
}
}
impl<'input> AccessorsLexer<'input> {
impl<'input> LambdaASTLexer<'input> {
pub fn new(input: &'input str) -> Self {
Self {
input,
chars: input.char_indices().peekable(),
is_first_token: true,
}
}
pub fn next_token(&mut self) -> Option<Spanned<Token<'input>, usize, LexerError>> {
if self.input.is_empty() {
return None;
}
if self.is_first_token {
self.is_first_token = false;
return Some(self.try_parse_first_token());
}
self.chars.next().map(|(start_pos, ch)| match ch {
'[' => Ok((start_pos, Token::OpenSquareBracket, start_pos + 1)),
']' => Ok((start_pos, Token::CloseSquareBracket, start_pos + 1)),
'.' => Ok((start_pos, Token::Selector, start_pos + 1)),
'.' => Ok((start_pos, Token::ValuePathSelector, start_pos + 1)),
d if d.is_digit(ARRAY_IDX_BASE) => self.tokenize_arrays_idx(start_pos),
s if is_air_alphanumeric(s) => self.tokenize_field_name(start_pos),
@ -109,4 +122,24 @@ impl<'input> AccessorsLexer<'input> {
&self.input[start_pos..end_pos + 1]
}
fn try_parse_first_token(&mut self) -> Spanned<Token<'input>, usize, LexerError> {
let (token, token_size) = if self.input == LENGTH_FUNCTOR {
(Token::LengthFunctor, LENGTH_FUNCTOR.len())
} else if self.input.starts_with(VALUE_PATH_STARTER) {
(Token::ValuePathStarter, VALUE_PATH_STARTER.len())
} else {
return Err(LexerError::UnexpectedSymbol(0, self.input.len()));
};
self.advance_by(token_size);
Ok((0, token, token_size))
}
fn advance_by(&mut self, advance_size: usize) {
// advance_by is unstable
for _ in 0..advance_size {
self.chars.next();
}
}
}

View File

@ -14,16 +14,16 @@
* limitations under the License.
*/
mod accessors_lexer;
mod errors;
mod lambda_ast_lexer;
mod token;
mod utils;
#[cfg(test)]
mod tests;
pub use accessors_lexer::AccessorsLexer;
pub use errors::LexerError;
pub use lambda_ast_lexer::LambdaASTLexer;
pub use token::Token;
pub(self) use utils::is_air_alphanumeric;

View File

@ -14,26 +14,27 @@
* limitations under the License.
*/
use super::accessors_lexer::Spanned;
use super::AccessorsLexer;
use super::lambda_ast_lexer::Spanned;
use super::LambdaASTLexer;
use super::LexerError;
use super::Token;
fn run_lexer(input: &str) -> Vec<Spanned<Token<'_>, usize, LexerError>> {
let lexer = AccessorsLexer::new(input);
let lexer = LambdaASTLexer::new(input);
lexer.collect()
}
#[test]
fn array_access() {
let array_access: &str = ".[0]";
let array_access: &str = ".$.[0]";
let actual = run_lexer(array_access);
let expected = vec![
Spanned::Ok((0, Token::Selector, 1)),
Spanned::Ok((1, Token::OpenSquareBracket, 2)),
Spanned::Ok((2, Token::NumberAccessor(0), 3)),
Spanned::Ok((3, Token::CloseSquareBracket, 4)),
Spanned::Ok((0, Token::ValuePathStarter, 2)),
Spanned::Ok((2, Token::ValuePathSelector, 3)),
Spanned::Ok((3, Token::OpenSquareBracket, 4)),
Spanned::Ok((4, Token::NumberAccessor(0), 5)),
Spanned::Ok((5, Token::CloseSquareBracket, 6)),
];
assert_eq!(actual, expected);
}
@ -41,12 +42,13 @@ fn array_access() {
#[test]
fn field_access() {
let field_name = "some_field_name";
let field_access = format!(".{}", field_name);
let field_access = format!(".$.{}", field_name);
let actual = run_lexer(&field_access);
let expected = vec![
Spanned::Ok((0, Token::Selector, 1)),
Spanned::Ok((1, Token::StringAccessor(field_name), 1 + field_name.len())),
Spanned::Ok((0, Token::ValuePathStarter, 2)),
Spanned::Ok((2, Token::ValuePathSelector, 3)),
Spanned::Ok((3, Token::StringAccessor(field_name), 3 + field_name.len())),
];
assert_eq!(actual, expected);
}

View File

@ -19,8 +19,12 @@ use serde::Serialize;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Token<'input> {
LengthFunctor,
//.$
ValuePathStarter,
// .
Selector,
ValuePathSelector,
OpenSquareBracket,
CloseSquareBracket,

View File

@ -31,9 +31,8 @@ pub type LambdaParserResult<'input, T> = std::result::Result<T, LambdaParserErro
pub use errors::LambdaParserError;
pub use lambda_parser::parse;
pub use lexer::AccessorsLexer;
pub use lexer::LambdaASTLexer;
pub use lexer::LexerError;
pub use va_lambda::LambdaParser;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Span {

View File

@ -14,27 +14,45 @@
* limitations under the License.
*/
use crate::parser::LambdaParser;
use crate::parser::lambda_parser::RawLambdaAST;
use crate::parser::va_lambda::RawLambdaASTParser;
use crate::ValueAccessor;
use air_lambda_ast::Functor;
thread_local!(static TEST_PARSER: LambdaParser = LambdaParser::new());
thread_local!(static TEST_PARSER: RawLambdaASTParser = RawLambdaASTParser::new());
fn parse(source_code: &str) -> Vec<ValueAccessor<'_>> {
fn parse(source_code: &str) -> RawLambdaAST<'_> {
TEST_PARSER.with(|parser| {
let mut errors = Vec::new();
let lexer = crate::parser::AccessorsLexer::new(source_code);
let lexer = crate::parser::LambdaASTLexer::new(source_code);
parser
.parse(source_code, &mut errors, lexer)
.expect("parsing should be successful")
})
}
fn parse_to_accessors(source_code: &str) -> Vec<ValueAccessor<'_>> {
let lambda_ast = parse(source_code);
match lambda_ast {
RawLambdaAST::ValuePath(accessors) => accessors,
_ => panic!("it should be a value path"),
}
}
fn parse_to_functor(source_code: &str) -> Functor {
let lambda_ast = parse(source_code);
match lambda_ast {
RawLambdaAST::Functor(functor) => functor,
_ => panic!("it should be a functor"),
}
}
#[test]
fn field_access() {
let field_name = "some_field_name";
let lambda = format!(".{}", field_name);
let lambda = format!(".$.{}", field_name);
let actual = parse(&lambda);
let actual = parse_to_accessors(&lambda);
let expected = vec![ValueAccessor::FieldAccessByName { field_name }];
assert_eq!(actual, expected);
}
@ -42,9 +60,9 @@ fn field_access() {
#[test]
fn field_access_with_flattening() {
let field_name = "some_field_name";
let lambda = format!(".{}!", field_name);
let lambda = format!(".$.{}!", field_name);
let actual = parse(&lambda);
let actual = parse_to_accessors(&lambda);
let expected = vec![ValueAccessor::FieldAccessByName { field_name }];
assert_eq!(actual, expected);
}
@ -52,9 +70,9 @@ fn field_access_with_flattening() {
#[test]
fn array_access() {
let idx = 0;
let lambda = format!(".[{}]", idx);
let lambda = format!(".$.[{}]", idx);
let actual = parse(&lambda);
let actual = parse_to_accessors(&lambda);
let expected = vec![ValueAccessor::ArrayAccess { idx }];
assert_eq!(actual, expected);
}
@ -62,9 +80,9 @@ fn array_access() {
#[test]
fn array_access_with_flattening() {
let idx = 0;
let lambda = format!(".[{}]!", idx);
let lambda = format!(".$.[{}]!", idx);
let actual = parse(&lambda);
let actual = parse_to_accessors(&lambda);
let expected = vec![ValueAccessor::ArrayAccess { idx }];
assert_eq!(actual, expected);
}
@ -72,9 +90,9 @@ fn array_access_with_flattening() {
#[test]
fn scalar_access() {
let scalar_name = "some_field_name";
let lambda = format!(".[{}]", scalar_name);
let lambda = format!(".$.[{}]", scalar_name);
let actual = parse(&lambda);
let actual = parse_to_accessors(&lambda);
let expected = vec![ValueAccessor::FieldAccessByScalar { scalar_name }];
assert_eq!(actual, expected);
}
@ -82,9 +100,9 @@ fn scalar_access() {
#[test]
fn scalar_access_with_flattening() {
let scalar_name = "some_scalar_name";
let lambda = format!(".[{}]!", scalar_name);
let lambda = format!(".$.[{}]!", scalar_name);
let actual = parse(&lambda);
let actual = parse_to_accessors(&lambda);
let expected = vec![ValueAccessor::FieldAccessByScalar { scalar_name }];
assert_eq!(actual, expected);
}
@ -93,9 +111,9 @@ fn scalar_access_with_flattening() {
fn field_array_access() {
let field_name = "some_field_name";
let idx = 1;
let lambda = format!(".{}.[{}]", field_name, idx);
let lambda = format!(".$.{}.[{}]", field_name, idx);
let actual = parse(&lambda);
let actual = parse_to_accessors(&lambda);
let expected = vec![
ValueAccessor::FieldAccessByName { field_name },
ValueAccessor::ArrayAccess { idx },
@ -107,9 +125,9 @@ fn field_array_access() {
fn field_scalar_access() {
let field_name = "some_field_name";
let scalar_name = "some_scalar_name";
let lambda = format!(".{}.[{}]", field_name, scalar_name);
let lambda = format!(".$.{}.[{}]", field_name, scalar_name);
let actual = parse(&lambda);
let actual = parse_to_accessors(&lambda);
let expected = vec![
ValueAccessor::FieldAccessByName { field_name },
ValueAccessor::FieldAccessByScalar { scalar_name },
@ -121,9 +139,9 @@ fn field_scalar_access() {
fn scalar_array_access() {
let scalar_name = "some_scalar_name";
let idx = 1;
let lambda = format!(".[{}].[{}]", scalar_name, idx);
let lambda = format!(".$.[{}].[{}]", scalar_name, idx);
let actual = parse(&lambda);
let actual = parse_to_accessors(&lambda);
let expected = vec![
ValueAccessor::FieldAccessByScalar { scalar_name },
ValueAccessor::ArrayAccess { idx },
@ -135,9 +153,9 @@ fn scalar_array_access() {
fn field_array_access_without_dot() {
let field_name = "some_field_name";
let idx = 1;
let lambda = format!(".{}[{}]", field_name, idx);
let lambda = format!(".$.{}[{}]", field_name, idx);
let actual = parse(&lambda);
let actual = parse_to_accessors(&lambda);
let expected = vec![
ValueAccessor::FieldAccessByName { field_name },
ValueAccessor::ArrayAccess { idx },
@ -149,9 +167,9 @@ fn field_array_access_without_dot() {
fn array_field_access() {
let field_name = "some_field_name";
let idx = 1;
let lambda = format!(".[{}].{}", idx, field_name);
let lambda = format!(".$.[{}].{}", idx, field_name);
let actual = parse(&lambda);
let actual = parse_to_accessors(&lambda);
let expected = vec![
ValueAccessor::ArrayAccess { idx },
ValueAccessor::FieldAccessByName { field_name },
@ -163,9 +181,9 @@ fn array_field_access() {
fn array_scalar_access() {
let scalar_name = "some_scalar_name";
let idx = 1;
let lambda = format!(".[{}].[{}]", idx, scalar_name);
let lambda = format!(".$.[{}].[{}]", idx, scalar_name);
let actual = parse(&lambda);
let actual = parse_to_accessors(&lambda);
let expected = vec![
ValueAccessor::ArrayAccess { idx },
ValueAccessor::FieldAccessByScalar { scalar_name },
@ -179,9 +197,12 @@ fn many_array_field_access() {
let field_name_2 = "some_field_name_2";
let idx_1 = 1;
let idx_2 = u32::MAX;
let lambda = format!(".[{}].{}.[{}].{}", idx_1, field_name_1, idx_2, field_name_2);
let lambda = format!(
".$.[{}].{}.[{}].{}",
idx_1, field_name_1, idx_2, field_name_2
);
let actual = parse(&lambda);
let actual = parse_to_accessors(&lambda);
let expected = vec![
ValueAccessor::ArrayAccess { idx: idx_1 },
ValueAccessor::FieldAccessByName {
@ -204,11 +225,11 @@ fn many_array_field_scalar_access() {
let scalar_name_1 = "some_scalar_name_1";
let scalar_name_2 = "some_scalar_name_2";
let lambda = format!(
".[{}].[{}].{}.[{}].[{}].{}",
".$.[{}].[{}].{}.[{}].[{}].{}",
idx_1, scalar_name_1, field_name_1, idx_2, scalar_name_2, field_name_2
);
let actual = parse(&lambda);
let actual = parse_to_accessors(&lambda);
let expected = vec![
ValueAccessor::ArrayAccess { idx: idx_1 },
ValueAccessor::FieldAccessByScalar {
@ -227,3 +248,25 @@ fn many_array_field_scalar_access() {
];
assert_eq!(actual, expected);
}
#[test]
fn parse_length_functor() {
let lambda = ".length";
let actual = parse_to_functor(&lambda);
let expected = Functor::Length;
assert_eq!(actual, expected);
}
#[test]
fn parse_length_functor_with_following_accessors() {
let lambda = ".length.[0]";
let actual = TEST_PARSER.with(|parser| {
let mut errors = Vec::new();
let lexer = crate::parser::LambdaASTLexer::new(lambda);
parser.parse(lambda, &mut errors, lexer)
});
assert!(matches!(actual, Err(lalrpop_util::ParseError::User { .. })))
}

View File

@ -1,4 +1,6 @@
use crate::ValueAccessor;
use crate::parser::lambda_parser::RawLambdaAST;
use crate::Functor;
use crate::parser::lexer::LexerError;
use crate::parser::lexer::Token;
@ -7,7 +9,13 @@ use lalrpop_util::ErrorRecovery;
// the only thing why input matters here is just introducing lifetime for Token
grammar<'err, 'input>(input: &'input str, errors: &'err mut Vec<ErrorRecovery<usize, Token<'input>, LexerError>>);
pub Lambda: Vec<ValueAccessor<'input>> = <ValueAccessor*> => <>;
pub(crate) RawLambdaAST: RawLambdaAST<'input> = {
<value_path_starter: ".$"> <accessors: ValueAccessor*> => RawLambdaAST::ValuePath(accessors),
length_functor => RawLambdaAST::Functor(Functor::Length),
! => { errors.push(<>); RawLambdaAST::Error },
}
ValueAccessor: ValueAccessor<'input> = {
<maybe_dot_selector:"."?> "[" <idx: number_accessor> "]" <maybe_flatten_sign:"!"?> => {
@ -30,7 +38,8 @@ extern {
type Error = LexerError;
enum Token<'input> {
"." => Token::Selector,
".$" => Token::ValuePathStarter,
"." => Token::ValuePathSelector,
"[" => Token::OpenSquareBracket,
"]" => Token::CloseSquareBracket,
@ -39,5 +48,7 @@ extern {
string_accessor => Token::StringAccessor(<&'input str>),
"!" => Token::FlatteningSign,
length_functor => Token::LengthFunctor,
}
}

File diff suppressed because it is too large Load Diff

View File

@ -164,7 +164,7 @@ mod tests {
&avm_outcome[..],
);
let anomaly: AnomalyData =
let anomaly: AnomalyData<'_> =
serde_json::from_str(&json_data).expect("deserialize JSON anomaly data");
assert_eq!(

View File

@ -18,6 +18,7 @@ use super::{Call, Sexp};
use crate::{asserts::ServiceDefinition, ephemeral::PeerId};
use std::collections::{HashMap, HashSet};
use std::fmt::Write;
#[derive(Debug, Default)]
pub(crate) struct Transformer {
@ -57,7 +58,9 @@ impl Transformer {
self.results.insert(call_id, service.clone());
match &mut call.triplet.1 {
Sexp::String(ref mut value) => value.push_str(&format!("..{}", call_id)),
Sexp::String(ref mut value) => {
write!(value, "..{}", call_id).unwrap();
}
_ => panic!("Incorrect script: non-string service string not supported"),
}
}