Introduce restriction operator for streams (#172)

This commit is contained in:
Mike Voronov 2021-11-24 17:57:14 +03:00 committed by GitHub
parent 9f47eb9b83
commit 5cd45385b4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
61 changed files with 2484 additions and 1224 deletions

View File

@ -1,3 +1,17 @@
## Version 0.17.0 (2021-11-24)
[PR 172](https://github.com/fluencelabs/aquavm/pull/172):
A new instruction intended to restrict a scope of variables was introduced to AquaVM.
[PR 168](https://github.com/fluencelabs/aquavm/pull/168):
AIR parser and AST was highly refactored to be more suitable to the scalar/stream restriction scheme used in AIR instructions.
[PR 164](https://github.com/fluencelabs/aquavm/pull/164):
SecurityTetraplet was decoupled with marine-rs-sdk to have the only one definition in AquaVM that then exported by marine-rs-sdk.
[PR 162](https://github.com/fluencelabs/aquavm/pull/162):
The scalar scoping scheme was improved in order to support more than two scope levels.
## Version 0.16.0 (2021-10-18)
[PR 154](https://github.com/fluencelabs/aquavm/pull/154)

6
Cargo.lock generated
View File

@ -13,7 +13,7 @@ dependencies = [
[[package]]
name = "air"
version = "0.16.0"
version = "0.17.0"
dependencies = [
"air-execution-info-collector",
"air-interpreter-data",
@ -49,7 +49,7 @@ version = "0.1.0"
[[package]]
name = "air-interpreter"
version = "0.16.0"
version = "0.17.0"
dependencies = [
"air",
"air-log-targets",
@ -62,7 +62,7 @@ dependencies = [
[[package]]
name = "air-interpreter-data"
version = "0.2.0"
version = "0.2.2"
dependencies = [
"once_cell",
"semver 1.0.4",

View File

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

View File

@ -1,6 +1,6 @@
[package]
name = "air"
version = "0.16.0"
version = "0.17.0"
description = "Interpreter of AIR scripts intended to coordinate request flow in the Fluence network"
authors = ["Fluence Labs"]
edition = "2018"
@ -30,6 +30,7 @@ serde = { version = "1.0.118", features = [ "derive", "rc" ] }
serde_json = "1.0.61"
boolinator = "2.4.0"
maplit = "1.0.2"
log = "0.4.11"
thiserror = "1.0.23"
strum = "0.21"
@ -46,7 +47,6 @@ criterion = "0.3.3"
csv = "1.1.5"
once_cell = "1.4.1"
env_logger = "0.7.1"
maplit = "1.0.2"
pretty_assertions = "0.6.1"
serde_json = "1.0.61"

View File

@ -17,7 +17,6 @@
mod apply_to_arguments;
mod utils;
use super::call::call_result_setter::set_stream_result;
use super::ExecutionCtx;
use super::ExecutionResult;
use super::TraceHandler;
@ -75,7 +74,10 @@ fn save_result<'ctx>(
Scalar(scalar) => exec_ctx.scalars.set_value(scalar.name, result).map(|_| ()),
Stream(stream) => {
let generation = ap_result_to_generation(merger_ap_result);
set_stream_result(result, generation, stream.name.to_string(), exec_ctx).map(|_| ())
exec_ctx
.streams
.add_stream_value(result, generation, stream.name, stream.position)
.map(|_| ())
}
}
}

View File

@ -69,7 +69,7 @@ fn apply_scalar(
) -> ExecutionResult<ValueAggregate> {
// TODO: refactor this code after boxed value
match &scalar.lambda {
Some(lambda) => apply_scalar_wl_impl(scalar.name, lambda, exec_ctx, trace_ctx),
Some(lambda) => apply_scalar_wl_impl(scalar.name, scalar.position, lambda, exec_ctx, trace_ctx),
None => apply_scalar_impl(scalar.name, exec_ctx, trace_ctx, should_touch_trace),
}
}
@ -104,11 +104,12 @@ fn apply_scalar_impl(
fn apply_scalar_wl_impl(
scalar_name: &str,
position: usize,
lambda: &LambdaAST<'_>,
exec_ctx: &ExecutionCtx<'_>,
trace_ctx: &TraceHandler,
) -> ExecutionResult<ValueAggregate> {
let variable = Variable::scalar(scalar_name);
let variable = Variable::scalar(scalar_name, position);
let (jvalue, mut tetraplets) = apply_lambda(variable, lambda, exec_ctx)?;
let tetraplet = tetraplets

View File

@ -80,7 +80,7 @@ fn variable_to_generations(variable: &ast::Variable<'_>, exec_ctx: &ExecutionCtx
Stream(stream) => {
// unwrap here is safe because this function will be called only
// when this stream's been created
let stream = exec_ctx.streams.get(stream.name).unwrap();
let stream = exec_ctx.streams.get(stream.name, stream.position).unwrap();
let generation = match stream.borrow().generations_count() {
0 => 0,
n => n - 1,

View File

@ -17,7 +17,6 @@
use super::*;
use crate::execution_step::execution_context::*;
use crate::execution_step::Generation;
use crate::execution_step::Stream;
use crate::execution_step::ValueAggregate;
use air_interpreter_data::CallResult;
@ -26,9 +25,6 @@ use air_parser::ast::CallOutputValue;
use air_parser::ast::Variable;
use air_trace_handler::TraceHandler;
use std::cell::RefCell;
use std::collections::hash_map::Entry::{Occupied, Vacant};
/// Writes result of a local `Call` instruction to `ExecutionCtx` at `output`.
/// Returns call result.
pub(crate) fn set_local_result<'i>(
@ -44,12 +40,21 @@ pub(crate) fn set_local_result<'i>(
}
CallOutputValue::Variable(Variable::Stream(stream)) => {
// TODO: refactor this generation handling
let generation = match exec_ctx.streams.get(stream.name) {
Some(stream) => Generation::Nth(stream.borrow().generations_count() as u32 - 1),
let generation = match exec_ctx.streams.get(stream.name, stream.position) {
Some(stream) => {
let generation = match stream.borrow().generations_count() {
0 => 0,
n => n - 1,
};
Generation::Nth(generation as u32)
}
None => Generation::Last,
};
let generation = set_stream_result(executed_result, generation, stream.name.to_string(), exec_ctx)?;
let generation =
exec_ctx
.streams
.add_stream_value(executed_result, generation, stream.name, stream.position)?;
Ok(CallResult::executed_stream(result_value, generation))
}
CallOutputValue::None => Ok(CallResult::executed_scalar(result_value)),
@ -71,7 +76,9 @@ pub(crate) fn set_result_from_value<'i>(
(CallOutputValue::Variable(Variable::Stream(stream)), Value::Stream { value, generation }) => {
let result = ValueAggregate::new(value, tetraplet, trace_pos);
let generation = Generation::Nth(generation);
let _ = set_stream_result(result, generation, stream.name.to_string(), exec_ctx)?;
let _ = exec_ctx
.streams
.add_stream_value(result, generation, stream.name, stream.position)?;
}
// it isn't needed to check there that output and value matches because
// it's been already checked in trace handler
@ -81,28 +88,6 @@ pub(crate) fn set_result_from_value<'i>(
Ok(())
}
// TODO: decouple this function to a separate module
pub(crate) fn set_stream_result(
executed_result: ValueAggregate,
generation: Generation,
stream_name: String,
exec_ctx: &mut ExecutionCtx<'_>,
) -> ExecutionResult<u32> {
let generation = match exec_ctx.streams.entry(stream_name) {
Occupied(mut entry) => {
// if result is an array, insert result to the end of the array
entry.get_mut().borrow_mut().add_value(executed_result, generation)?
}
Vacant(entry) => {
let stream = Stream::from_value(executed_result);
entry.insert(RefCell::new(stream));
0
}
};
Ok(generation)
}
/// Writes an executed state of a particle being sent to remote node.
pub(crate) fn set_remote_call_result<'i>(
peer_pk: String,

View File

@ -51,10 +51,10 @@ pub(crate) fn construct_scalar_iterable_value<'ctx>(
/// Constructs iterable value for given stream iterable.
pub(crate) fn construct_stream_iterable_value<'ctx>(
stream_name: &'ctx str,
stream: &ast::Stream<'_>,
exec_ctx: &ExecutionCtx<'ctx>,
) -> ExecutionResult<FoldIterableStream> {
match exec_ctx.streams.get(stream_name) {
match exec_ctx.streams.get(stream.name, stream.position) {
Some(stream) => {
let stream = stream.borrow();
if stream.is_empty() {

View File

@ -29,7 +29,7 @@ impl<'i> ExecutableInstruction<'i> for FoldScalar<'i> {
fn execute(&self, exec_ctx: &mut ExecutionCtx<'i>, trace_ctx: &mut TraceHandler) -> ExecutionResult<()> {
log_instruction!(fold, exec_ctx, trace_ctx);
exec_ctx.scalars.meet_fold_begin();
exec_ctx.scalars.meet_fold_start();
let fold_result = match construct_scalar_iterable_value(&self.iterable, exec_ctx)? {
FoldIterableScalar::Empty => Ok(()),

View File

@ -29,14 +29,14 @@ impl<'i> ExecutableInstruction<'i> for FoldStream<'i> {
fn execute(&self, exec_ctx: &mut ExecutionCtx<'i>, trace_ctx: &mut TraceHandler) -> ExecutionResult<()> {
log_instruction!(fold, exec_ctx, trace_ctx);
let iterables = match construct_stream_iterable_value(self.iterable.name, exec_ctx)? {
let iterables = match construct_stream_iterable_value(&self.iterable, exec_ctx)? {
FoldIterableStream::Empty => return Ok(()),
FoldIterableStream::Stream(iterables) => iterables,
};
let fold_id = exec_ctx.tracker.fold.seen_stream_count;
trace_to_exec_err!(trace_ctx.meet_fold_start(fold_id))?;
exec_ctx.scalars.meet_fold_begin();
exec_ctx.scalars.meet_fold_start();
for iterable in iterables {
let value = match iterable.peek() {

View File

@ -22,6 +22,7 @@ mod fold_scalar;
mod fold_stream;
mod match_;
mod mismatch;
mod new;
mod next;
mod null;
mod par;
@ -126,6 +127,7 @@ impl<'i> ExecutableInstruction<'i> for Instruction<'i> {
Instruction::Ap(ap) => execute!(self, ap, exec_ctx, trace_ctx),
Instruction::FoldScalar(fold) => execute!(self, fold, exec_ctx, trace_ctx),
Instruction::FoldStream(fold) => execute_fold!(self, fold, exec_ctx, trace_ctx),
Instruction::New(new) => execute!(self, new, exec_ctx, trace_ctx),
Instruction::Next(next) => execute!(self, next, exec_ctx, trace_ctx),
Instruction::Null(null) => execute!(self, null, exec_ctx, trace_ctx),
Instruction::Par(par) => execute!(self, par, exec_ctx, trace_ctx),
@ -147,16 +149,10 @@ macro_rules! log_instruction {
log::debug!(target: air_log_targets::INSTRUCTION, "> {}", stringify!($instr_name));
let mut variables = String::from(" scalars:");
variables.push_str(&format!("\n {}", $exec_ctx.scalars));
variables.push_str(" streams:");
if $exec_ctx.streams.is_empty() {
variables.push_str(" empty");
}
for (key, value) in $exec_ctx.streams.iter() {
variables.push_str(&format!("\n {} => {}", key, value.borrow()));
}
variables.push_str(&format!("\n {}", $exec_ctx.streams));
log::trace!(target: air_log_targets::DATA_CACHE, "{}", variables);
log::trace!(

View File

@ -0,0 +1,64 @@
/*
* Copyright 2021 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 super::ExecutionCtx;
use super::ExecutionResult;
use super::TraceHandler;
use crate::log_instruction;
use air_parser::ast::New;
use air_parser::ast::Variable;
impl<'i> super::ExecutableInstruction<'i> for New<'i> {
fn execute(&self, exec_ctx: &mut ExecutionCtx<'i>, trace_ctx: &mut TraceHandler) -> ExecutionResult<()> {
log_instruction!(new, exec_ctx, trace_ctx);
prolog(self, exec_ctx);
// it should be a lazy error evaluating after execution of epilog block, since it's
// necessary to return a restricted variable to it's previous state in case of
// any error. It's highly important to distinguish between global and restricted streams
// at the end of execution to make a correct data.
let instruction_result = self.instruction.execute(exec_ctx, trace_ctx);
epilog(self, exec_ctx);
instruction_result
}
}
fn prolog<'i>(new: &New<'i>, exec_ctx: &mut ExecutionCtx<'i>) {
let position = new.span.left;
match &new.variable {
Variable::Stream(stream) => {
let iteration = exec_ctx.tracker.new_tracker.get_iteration(position);
exec_ctx.streams.meet_scope_start(stream.name, new.span, iteration);
}
// noop
Variable::Scalar(_) => {}
}
exec_ctx.tracker.meet_new(position);
}
fn epilog<'i>(new: &New<'i>, exec_ctx: &mut ExecutionCtx<'i>) {
let position = new.span.left;
match &new.variable {
Variable::Stream(stream) => exec_ctx
.streams
.meet_scope_end(stream.name.to_string(), position as u32),
// noop
Variable::Scalar(_) => {}
}
}

View File

@ -19,32 +19,28 @@ use air_parser::ast;
#[derive(Clone, Copy, Debug)]
pub(crate) enum Variable<'i> {
Scalar(&'i str),
Stream { name: &'i str, generation: Generation },
#[allow(dead_code)]
// position will be needed to implement new for operators
Scalar { name: &'i str, position: usize },
Stream {
name: &'i str,
generation: Generation,
position: usize,
},
}
impl<'i> Variable<'i> {
pub(crate) fn scalar(name: &'i str) -> Self {
Self::Scalar(name)
pub(crate) fn scalar(name: &'i str, position: usize) -> Self {
Self::Scalar { name, position }
}
#[allow(dead_code)]
pub(crate) fn from_ast_with_generation(ast_variable: &ast::Variable<'i>, generation: Generation) -> Self {
use ast::Variable::*;
match ast_variable {
Scalar(scalar) => Variable::Scalar(scalar.name),
Stream(stream) => Variable::Stream {
name: stream.name,
pub(crate) fn stream(name: &'i str, generation: Generation, position: usize) -> Self {
Self::Stream {
name,
generation,
},
position,
}
}
#[allow(dead_code)]
pub(crate) fn from_stream(name: &'i str, generation: Generation) -> Self {
Self::Stream { name, generation }
}
}
impl<'i> From<&ast::Variable<'i>> for Variable<'i> {
@ -52,11 +48,8 @@ impl<'i> From<&ast::Variable<'i>> for Variable<'i> {
use ast::Variable::*;
match ast_variable {
Scalar(scalar) => Self::Scalar(scalar.name),
Stream(stream) => Self::Stream {
name: stream.name,
generation: Generation::Last,
},
Scalar(scalar) => Self::scalar(scalar.name, scalar.position),
Stream(stream) => Self::stream(stream.name, Generation::Last, stream.position),
}
}
}
@ -66,11 +59,8 @@ impl<'i> From<&ast::VariableWithLambda<'i>> for Variable<'i> {
use ast::VariableWithLambda::*;
match ast_variable {
Scalar(scalar) => Self::Scalar(scalar.name),
Stream(stream) => Self::Stream {
name: stream.name,
generation: Generation::Last,
},
Scalar(scalar) => Self::scalar(scalar.name, scalar.position),
Stream(stream) => Self::stream(stream.name, Generation::Last, stream.position),
}
}
}

View File

@ -42,8 +42,8 @@ pub(crate) enum ExecutionError {
#[error("Local service error, ret_code is {0}, error message is '{1}'")]
LocalServiceError(i32, Rc<String>),
/// Value for such name isn't presence in data.
#[error("variable with name '{0}' isn't present in data")]
/// Variable with such a name wasn't defined during AIR script execution.
#[error("variable with name '{0}' wasn't defined during script execution")]
VariableNotFound(String),
/// Multiple values for such name found.

View File

@ -17,13 +17,11 @@
use super::LastErrorDescriptor;
use super::LastErrorWithTetraplet;
use super::Scalars;
use crate::execution_step::boxed_value::Stream;
use super::Streams;
use air_execution_info_collector::InstructionTracker;
use air_interpreter_interface::*;
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
/// Contains all necessary state needed to execute AIR script.
@ -33,8 +31,7 @@ pub(crate) struct ExecutionCtx<'i> {
pub(crate) scalars: Scalars<'i>,
/// Contains all streams.
// TODO: use shared string (Rc<String>) to avoid copying.
pub(crate) streams: HashMap<String, RefCell<Stream>>,
pub(crate) streams: Streams,
/// Set of peer public keys that should receive resulted data.
pub(crate) next_peer_pks: Vec<String>,
@ -116,9 +113,7 @@ impl<'i> Display for ExecutionCtx<'i> {
writeln!(f, " {}", self.scalars)?;
writeln!(f, "streams:")?;
for (name, stream) in self.streams.iter() {
writeln!(f, " {} => {}", name, stream.borrow())?;
}
writeln!(f, " {}", self.streams)?;
writeln!(f, "current peer id: {}", self.current_peer_id)?;
writeln!(f, "subtree complete: {}", self.subtree_complete)?;

View File

@ -17,9 +17,11 @@
mod context;
mod error_descriptor;
mod scalar_variables;
mod streams_variables;
pub(crate) use context::*;
pub use error_descriptor::LastError;
pub(crate) use error_descriptor::LastErrorDescriptor;
pub(crate) use error_descriptor::LastErrorWithTetraplet;
pub(crate) use scalar_variables::*;
pub(crate) use streams_variables::*;

View File

@ -70,7 +70,6 @@ pub(crate) struct Scalars<'i> {
pub fold_block_id: usize,
}
#[allow(dead_code)]
impl<'i> Scalars<'i> {
/// Returns true if there was a previous value for the provided key on the same
/// fold block.
@ -122,10 +121,6 @@ impl<'i> Scalars<'i> {
}
}
pub(crate) fn remove_value(&mut self, name: &str) {
self.values.remove(name);
}
pub(crate) fn remove_iterable_value(&mut self, name: &str) {
self.iterable_values.remove(name);
}
@ -143,26 +138,6 @@ impl<'i> Scalars<'i> {
.ok_or_else(|| Rc::new(ExecutionError::VariableNotFound(name.to_string())))
}
pub(crate) fn get_value_mut(&'i mut self, name: &str) -> ExecutionResult<&'i mut ValueAggregate> {
let fold_block_id = self.fold_block_id;
self.values
.get_mut(name)
.and_then(|scalars| {
scalars
.iter_mut()
.take(fold_block_id)
.rev()
.find_map(|scalar| scalar.as_mut())
})
.ok_or_else(|| Rc::new(ExecutionError::VariableNotFound(name.to_string())))
}
pub(crate) fn get_iterable(&self, name: &str) -> ExecutionResult<&FoldState<'i>> {
self.iterable_values
.get(name)
.ok_or_else(|| Rc::new(ExecutionError::FoldStateNotFound(name.to_string())))
}
pub(crate) fn get_iterable_mut(&mut self, name: &str) -> ExecutionResult<&mut FoldState<'i>> {
self.iterable_values
.get_mut(name)
@ -181,7 +156,7 @@ impl<'i> Scalars<'i> {
}
}
pub(crate) fn meet_fold_begin(&mut self) {
pub(crate) fn meet_fold_start(&mut self) {
self.fold_block_id += 1;
}

View File

@ -0,0 +1,39 @@
/*
* Copyright 2021 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 super::Scalars;
use std::fmt;
impl<'i> fmt::Display for Scalars<'i> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "fold_block_id: {}", self.fold_blocks_sequence.len())?;
for (name, _) in self.values.iter() {
let descriptor = self.values.get(name);
if let Some(descriptor) = descriptor {
writeln!(f, "{} => {:?}", name, descriptor.values.last())?;
}
}
for (name, _) in self.iterable_values.iter() {
// it's impossible to print an iterable value for now
writeln!(f, "{} => iterable", name)?;
}
Ok(())
}
}

View File

@ -0,0 +1,224 @@
/*
* Copyright 2021 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::execution_step::ExecutionResult;
use crate::execution_step::Generation;
use crate::execution_step::Stream;
use crate::execution_step::ValueAggregate;
use air_interpreter_data::GlobalStreamGens;
use air_interpreter_data::RestrictedStreamGens;
use std::cell::RefCell;
use std::collections::hash_map::Entry::{Occupied, Vacant};
use std::collections::HashMap;
#[derive(Default)]
pub(crate) struct Streams {
// this one is optimized for speed (not for memory), because it's unexpected
// that a script could have a lot of new.
// TODO: use shared string (Rc<String>) to avoid copying.
streams: HashMap<String, Vec<StreamDescriptor>>,
/// Contains stream generation that private stream should have at the scope start.
data_restr_stream_generations: RestrictedStreamGens,
/// Contains stream generations that each private stream had at the scope end.
/// Then it's placed into data
collected_restricted_stream_gens: RestrictedStreamGens,
}
struct StreamDescriptor {
pub(self) span: Span,
// TODO: get rid of RefCell in a separate PR
pub(self) stream: RefCell<Stream>,
}
impl Streams {
pub(crate) fn get(&self, name: &str, position: usize) -> Option<&RefCell<Stream>> {
self.streams
.get(name)
.map(|descriptors| find_closest(descriptors.iter(), position))
.flatten()
}
pub(crate) fn add_stream_value(
&mut self,
value: ValueAggregate,
generation: Generation,
stream_name: &str,
position: usize,
) -> ExecutionResult<u32> {
match self.get(stream_name, position) {
Some(stream) => stream.borrow_mut().add_value(value, generation),
None => {
// streams could be created in three ways:
// - after met new instruction with stream name that isn't present in streams
// (it's the only way to create restricted streams)
// - by calling add_global_stream with generation that come from data
// for global streams
// - and by this function, and if there is no such a streams in streams,
// it means that a new global one should be created.
let stream = Stream::from_value(value);
self.add_global_stream(stream_name.to_string(), stream);
let generation = 0;
Ok(generation)
}
}
}
pub(crate) fn add_global_stream(&mut self, name: String, stream: Stream) {
let descriptor = StreamDescriptor::global(RefCell::new(stream));
self.streams.insert(name, vec![descriptor]);
}
pub(crate) fn meet_scope_start(&mut self, name: impl Into<String>, span: Span, iteration: u32) {
let name = name.into();
let generations_count = self
.stream_generation_from_data(&name, span.left as u32, iteration as usize)
.unwrap_or_default();
let new_stream = RefCell::new(Stream::from_generations_count(generations_count as usize));
let new_descriptor = StreamDescriptor::restricted(new_stream, span);
match self.streams.entry(name) {
Occupied(mut entry) => {
entry.get_mut().push(new_descriptor);
}
Vacant(entry) => {
entry.insert(vec![new_descriptor]);
}
}
}
pub(crate) fn meet_scope_end(&mut self, name: String, position: u32) {
// unwraps are safe here because met_scope_end must be called after met_scope_start
let stream_descriptors = self.streams.get_mut(&name).unwrap();
// delete a stream after exit from a scope
let last_descriptor = stream_descriptors.pop().unwrap();
if stream_descriptors.is_empty() {
// streams should contain only non-empty stream embodiments
self.streams.remove(&name);
}
self.collect_stream_generation(
name,
position,
last_descriptor.stream.borrow().generations_count() as u32,
);
}
/// This method must be called at the end of execution, because it contains logic to collect
/// all global streams depending on their presence in a streams field.
pub(crate) fn into_streams_data(self) -> (GlobalStreamGens, RestrictedStreamGens) {
// since it's called at the end of execution, streams contains only global ones,
// because all private's been deleted after exiting a scope
let global_streams = self
.streams
.into_iter()
.map(|(name, mut descriptors)| {
// unwrap is safe here because of invariant that streams contains non-empty vectors,
// moreover it must contain only one value, because this method is called at the end
// of the execution
let generation = descriptors.pop().unwrap().stream.borrow().generations_count();
(name, generation as u32)
})
.collect::<GlobalStreamGens>();
(global_streams, self.collected_restricted_stream_gens)
}
fn stream_generation_from_data(&self, name: &str, position: u32, iteration: usize) -> Option<u32> {
self.data_restr_stream_generations
.get(name)
.and_then(|scopes| {
scopes
.get(&position)
.map(|iterations| iterations.get(iteration))
.flatten()
})
.copied()
}
fn collect_stream_generation(&mut self, name: String, position: u32, generation: u32) {
match self.collected_restricted_stream_gens.entry(name) {
Occupied(mut streams) => match streams.get_mut().entry(position) {
Occupied(mut iterations) => iterations.get_mut().push(generation),
Vacant(entry) => {
entry.insert(vec![generation]);
}
},
Vacant(entry) => {
let iterations = maplit::hashmap! {
position => vec![generation],
};
entry.insert(iterations);
}
}
}
}
impl StreamDescriptor {
pub(self) fn global(stream: RefCell<Stream>) -> Self {
Self {
span: Span::new(0, usize::MAX),
stream,
}
}
pub(self) fn restricted(stream: RefCell<Stream>, span: Span) -> Self {
Self { span, stream }
}
}
fn find_closest<'d>(
descriptors: impl DoubleEndedIterator<Item = &'d StreamDescriptor>,
position: usize,
) -> Option<&'d RefCell<Stream>> {
// descriptors are placed in a order of decreasing scopes, so it's enough to get the latest suitable
for descriptor in descriptors.rev() {
if descriptor.span.contains(position) {
return Some(&descriptor.stream);
}
}
None
}
use air_parser::ast::Span;
use std::fmt;
impl fmt::Display for Streams {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (name, descriptors) in self.streams.iter() {
if let Some(last_descriptor) = descriptors.last() {
writeln!(f, "{} => {}", name, last_descriptor)?;
}
}
Ok(())
}
}
impl fmt::Display for StreamDescriptor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
" <{}> - <{}>: {}",
self.span.left,
self.span.right,
self.stream.borrow()
)
}
}

View File

@ -87,9 +87,13 @@ pub(crate) fn resolve_variable<'ctx, 'i>(
use crate::execution_step::boxed_value::StreamJvaluableIngredients;
match variable {
Variable::Scalar(name) => Ok(ctx.scalars.get(name)?.into_jvaluable()),
Variable::Stream { name, generation } => {
match ctx.streams.get(name) {
Variable::Scalar { name, .. } => Ok(ctx.scalars.get(name)?.into_jvaluable()),
Variable::Stream {
name,
generation,
position,
} => {
match ctx.streams.get(name, position) {
Some(stream) => {
let ingredients = StreamJvaluableIngredients::new(stream.borrow(), generation);
Ok(Box::new(ingredients))

View File

@ -88,13 +88,10 @@ fn make_exec_ctx(
}
fn create_streams(ctx: &mut ExecutionCtx<'_>, prev_data: &InterpreterData) {
use std::cell::RefCell;
for (stream_name, generation_count) in prev_data.streams.iter() {
for (stream_name, generation_count) in prev_data.global_streams.iter() {
let new_stream = Stream::from_generations_count(*generation_count as usize);
let new_stream = RefCell::new(new_stream);
// it's impossible to have duplicates of streams in data because of HashMap in data
ctx.streams.insert(stream_name.to_string(), new_stream);
ctx.streams.add_global_stream(stream_name.to_string(), new_stream);
}
}

View File

@ -16,28 +16,25 @@
use crate::execution_step::ExecutionCtx;
use crate::execution_step::ExecutionError;
use crate::execution_step::Stream;
use crate::execution_step::TraceHandler;
use crate::preparation_step::PreparationError;
use crate::InterpreterOutcome;
use crate::INTERPRETER_SUCCESS;
use air_interpreter_data::InterpreterData;
use air_interpreter_data::StreamGenerations;
use air_interpreter_interface::CallRequests;
use std::cell::RefCell;
use std::collections::HashMap;
use std::hash::Hash;
use std::rc::Rc;
/// Create InterpreterOutcome from supplied execution context and trace handler,
/// set ret_code to INTERPRETER_SUCCESS.
pub(crate) fn from_success_result(exec_ctx: ExecutionCtx<'_>, trace_handler: TraceHandler) -> InterpreterOutcome {
let streams = extract_stream_generations(exec_ctx.streams);
let (global_streams, restricted_streams) = exec_ctx.streams.into_streams_data();
let data = InterpreterData::from_execution_result(
trace_handler.into_result_trace(),
streams,
global_streams,
restricted_streams,
exec_ctx.last_call_request_id,
);
let data = serde_json::to_vec(&data).expect("default serializer shouldn't fail");
@ -94,10 +91,11 @@ pub(crate) fn from_execution_error(
) -> InterpreterOutcome {
let ret_code = err.to_error_code() as i32;
let streams = extract_stream_generations(exec_ctx.streams);
let (global_streams, restricted_streams) = exec_ctx.streams.into_streams_data();
let data = InterpreterData::from_execution_result(
trace_handler.into_result_trace(),
streams,
global_streams,
restricted_streams,
exec_ctx.last_call_request_id,
);
let data = serde_json::to_vec(&data).expect("default serializer shouldn't fail");
@ -120,10 +118,3 @@ fn dedup<T: Eq + Hash>(mut vec: Vec<T>) -> Vec<T> {
let set: HashSet<_> = vec.drain(..).collect();
set.into_iter().collect()
}
fn extract_stream_generations(streams: HashMap<String, RefCell<Stream>>) -> StreamGenerations {
streams
.into_iter()
.map(|(name, stream)| (name, stream.borrow().generations_count() as u32))
.collect::<_>()
}

View File

@ -19,6 +19,7 @@ mod call;
mod fold;
mod match_;
mod mismatch;
mod new;
mod par;
mod seq;
mod xor;

View File

@ -0,0 +1,289 @@
/*
* Copyright 2020 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_test_utils::prelude::*;
#[test]
fn new_with_global_streams_seq() {
let set_variable_peer_id = "set_variable_peer_id";
let local_vm_peer_id_1 = "local_vm_peer_id_1";
let local_vm_peer_id_2 = "local_vm_peer_id_2";
let mut local_vm_1 = create_avm(echo_call_service(), local_vm_peer_id_1);
let mut local_vm_2 = create_avm(echo_call_service(), local_vm_peer_id_2);
let variables_mapping = maplit::hashmap! {
"1".to_string() => json!(1),
"2".to_string() => json!(2),
};
let mut set_variable_vm = create_avm(set_variables_call_service(variables_mapping), set_variable_peer_id);
let script = format!(
r#"
(seq
(seq
(call "{0}" ("" "") ["1"] $stream)
(call "{0}" ("" "") ["2"] $stream)
)
(fold $stream i
(seq
(new $stream
(seq
(seq
(call "{1}" ("" "") [i] $stream)
(next i)
)
(call "{1}" ("" "") [$stream])
)
)
(call "{2}" ("" "") [$stream])
)
)
)"#,
set_variable_peer_id, local_vm_peer_id_1, local_vm_peer_id_2
);
let result = checked_call_vm!(set_variable_vm, "", &script, "", "");
let vm_1_result = checked_call_vm!(local_vm_1, "", &script, "", result.data);
let vm_2_result = checked_call_vm!(local_vm_2, "", &script, "", vm_1_result.data.clone());
let vm_1_result = checked_call_vm!(local_vm_1, "", &script, vm_1_result.data, vm_2_result.data.clone());
let vm_2_result = checked_call_vm!(local_vm_2, "", script, vm_2_result.data, vm_1_result.data);
let actual_trace = trace_from_result(&vm_2_result);
let expected_trace = vec![
executed_state::stream_number(1, 0),
executed_state::stream_number(2, 0),
executed_state::fold(vec![
executed_state::subtrace_lore(0, SubTraceDesc::new(3, 1), SubTraceDesc::new(7, 2)),
executed_state::subtrace_lore(1, SubTraceDesc::new(4, 1), SubTraceDesc::new(5, 2)),
]),
executed_state::stream_number(1, 0),
executed_state::stream_number(2, 0),
executed_state::scalar(json!([2])),
executed_state::scalar(json!([1, 2])),
executed_state::scalar(json!([1])),
executed_state::scalar(json!([1, 2])),
];
assert_eq!(actual_trace, expected_trace);
let data = data_from_result(&vm_2_result);
let actual_restricted_streams = data.restricted_streams;
let expected_restricted_streams = maplit::hashmap! {
"$stream".to_string() => maplit::hashmap! {
282 => vec![1,1]
}
};
assert_eq!(actual_restricted_streams, expected_restricted_streams);
}
#[test]
fn several_restrictions() {
let vm_peer_id = "vm_peer_id";
let mut vm = create_avm(echo_call_service(), vm_peer_id);
let script = format!(
r#"
(new $stream
(seq
(new $stream
(call "{0}" ("" "") ["test"] $stream)
)
(call "{0}" ("" "") [$stream])
)
)"#,
vm_peer_id
);
let result = checked_call_vm!(vm, "", script, "", "");
let actual_trace = trace_from_result(&result);
let expected_trace = vec![
executed_state::stream_string("test", 0),
executed_state::scalar(json!([])),
];
assert_eq!(actual_trace, expected_trace);
}
#[test]
fn check_influence_to_not_restricted() {
let vm_peer_id = "vm_peer_id";
let mut vm = create_avm(echo_call_service(), vm_peer_id);
let script = format!(
r#"
(seq
(new $a
(seq
(seq
(seq
(ap "push more" $a0)
(ap "push more" $a1)
)
(ap "more" $a)
)
(call "{0}" ("op" "identity") [$a] a-fix)
)
)
(seq
(seq
(call "{0}" ("callbackSrv" "response") [$a0]) ;; should be non-empty
(call "{0}" ("callbackSrv" "response") [$a1]) ;; should be non-empty
)
(seq
(call "{0}" ("callbackSrv" "response") [$a]) ;; should be empty
(call "{0}" ("callbackSrv" "response") [a-fix]) ;; should be empty
)
)
)
"#,
vm_peer_id
);
let result = checked_call_vm!(vm, "", script, "", "");
print_trace(&result, "initial");
let actual_trace = trace_from_result(&result);
let expected_trace = vec![
executed_state::ap(Some(0)),
executed_state::ap(Some(0)),
executed_state::ap(Some(0)),
executed_state::scalar(json!(["more"])),
executed_state::scalar(json!(["push more"])),
executed_state::scalar(json!(["push more"])),
executed_state::scalar(json!([])),
executed_state::scalar(json!(["more"])),
];
assert_eq!(actual_trace, expected_trace);
}
#[test]
fn new_in_fold_with_ap() {
let set_variable_peer_id = "set_variable_peer_id";
let vm_peer_id = "vm_peer_id";
let mut set_variable_vm = create_avm(set_variable_call_service(json!([1, 2, 3, 4, 5])), set_variable_peer_id);
let mut vm = create_avm(echo_call_service(), vm_peer_id);
let script = format!(
r#"
(seq
(call "{0}" ("" "") [] iterable)
(fold iterable x
(seq
(new $s1
(seq
(ap "none" $s1)
(call "{1}" ("" "") [$s1] s-fix1) ;; should contains only "none" on each iteration
)
)
(next x)
)
)
)
"#,
set_variable_peer_id, vm_peer_id
);
let result = checked_call_vm!(set_variable_vm, "", &script, "", "");
let result = checked_call_vm!(vm, "", script, "", result.data);
print_trace(&result, "initial");
let actual_trace = trace_from_result(&result);
let expected_trace = vec![
executed_state::scalar(json!([1, 2, 3, 4, 5])),
executed_state::ap(Some(0)),
executed_state::scalar_string_array(vec!["none"]),
executed_state::ap(Some(0)),
executed_state::scalar_string_array(vec!["none"]),
executed_state::ap(Some(0)),
executed_state::scalar_string_array(vec!["none"]),
executed_state::ap(Some(0)),
executed_state::scalar_string_array(vec!["none"]),
executed_state::ap(Some(0)),
executed_state::scalar_string_array(vec!["none"]),
];
assert_eq!(actual_trace, expected_trace);
let data = data_from_result(&result);
let actual_restricted_streams = data.restricted_streams;
let expected_restricted_streams = maplit::hashmap! {
"$s1".to_string() => maplit::hashmap! {
146 => vec![1,1,1,1,1]
}
};
assert_eq!(actual_restricted_streams, expected_restricted_streams);
}
#[test]
fn new_with_errors() {
let faillible_peer_id = "failible_peer_id";
let mut faillible_vm = create_avm(fallible_call_service("service_id_1"), faillible_peer_id);
let local_peer_id = "local_peer_id";
let mut vm = create_avm(echo_call_service(), local_peer_id);
let script = format!(
r#"
(seq
(call "{0}" ("" "") [1] $global_stream) ;; this stream should precense in a data
(new $restricted_stream_1
(seq
(new $restricted_stream_2
(seq
(call "{0}" ("" "") [2] $restricted_stream_2) ;; should have generation 1 in a data
(call "{1}" ("service_id_1" "local_fn_name") [] result)
)
)
(call "{0}" ("" "") [2] restricted_stream_1) ;; should have generation 0 in a data
)
)
)"#,
local_peer_id, faillible_peer_id
);
let result = checked_call_vm!(vm, "", &script, "", "");
let result = call_vm!(faillible_vm, "", script, "", result.data);
let actual_trace = trace_from_result(&result);
let expected_trace = vec![
executed_state::stream_number(1, 0),
executed_state::stream_number(2, 0),
executed_state::service_failed(1, r#""error""#),
];
assert_eq!(actual_trace, expected_trace);
let data = data_from_result(&result);
let actual_restricted_streams = data.restricted_streams;
let expected_restricted_streams = maplit::hashmap! {
"$restricted_stream_2".to_string() => maplit::hashmap! {
216 => vec![1]
},
"$restricted_stream_1".to_string() => maplit::hashmap! {
141 => vec![0]
}
};
assert_eq!(actual_restricted_streams, expected_restricted_streams);
let actual_global_streams = data.global_streams;
let expected_global_streams = maplit::hashmap! {
"$global_stream".to_string() => 1,
};
assert_eq!(actual_global_streams, expected_global_streams);
}

View File

@ -262,13 +262,13 @@ fn fold_merge() {
let data = InterpreterData::try_from_slice(&result_7.data).expect("data should be well-formed");
let stream_1_generations = data
.streams
.global_streams
.get("$stream_1")
.expect("$stream_1 should presence in data");
.expect("$stream_1 should be present in data");
let stream_2_generations = data
.streams
.global_streams
.get("$stream_2")
.expect("$stream_2 should presence in data");
.expect("$stream_2 should be present in data");
assert_eq!(*stream_1_generations, 4);
assert_eq!(*stream_2_generations, 3);

View File

@ -74,7 +74,7 @@ fn join_chat() {
let remote_actual_trace = trace_from_result(&remote_result);
let remote_expected_trace = vec![
executed_state::stream_string("test", 0),
executed_state::stream_jvalue(json!([["A", "Relay1"], ["B", "Relay2"]]), 0),
executed_state::stream(json!([["A", "Relay1"], ["B", "Relay2"]]), 0),
executed_state::scalar(json!([["A", "Relay1"], ["B", "Relay2"]])),
executed_state::par(1, 2),
executed_state::request_sent_by("Remote"),
@ -97,7 +97,7 @@ fn join_chat() {
let relay_1_expected_trace = vec![
executed_state::stream_string("test", 0),
executed_state::stream_jvalue(json!([["A", "Relay1"], ["B", "Relay2"]]), 0),
executed_state::stream(json!([["A", "Relay1"], ["B", "Relay2"]]), 0),
executed_state::scalar(json!([["A", "Relay1"], ["B", "Relay2"]])),
executed_state::par(2, 2),
executed_state::stream_string("test", 0),
@ -115,7 +115,7 @@ fn join_chat() {
let client_1_expected_trace = vec![
executed_state::stream_string("test", 0),
executed_state::stream_jvalue(json!([["A", "Relay1"], ["B", "Relay2"]]), 0),
executed_state::stream(json!([["A", "Relay1"], ["B", "Relay2"]]), 0),
executed_state::scalar(json!([["A", "Relay1"], ["B", "Relay2"]])),
executed_state::par(2, 2),
executed_state::stream_string("test", 0),
@ -133,7 +133,7 @@ fn join_chat() {
let relay_2_expected_trace = vec![
executed_state::stream_string("test", 0),
executed_state::stream_jvalue(json!([["A", "Relay1"], ["B", "Relay2"]]), 0),
executed_state::stream(json!([["A", "Relay1"], ["B", "Relay2"]]), 0),
executed_state::scalar(json!([["A", "Relay1"], ["B", "Relay2"]])),
executed_state::par(1, 3),
executed_state::request_sent_by("Remote"),
@ -151,7 +151,7 @@ fn join_chat() {
let client_2_expected_trace = vec![
executed_state::stream_string("test", 0),
executed_state::stream_jvalue(json!([["A", "Relay1"], ["B", "Relay2"]]), 0),
executed_state::stream(json!([["A", "Relay1"], ["B", "Relay2"]]), 0),
executed_state::scalar(json!([["A", "Relay1"], ["B", "Relay2"]])),
executed_state::par(1, 3),
executed_state::request_sent_by("Remote"),

View File

@ -24,7 +24,6 @@ use std::rc::Rc;
#[allow(clippy::large_enum_variant)] // for Null and Error variants
#[derive(Serialize, Debug, PartialEq)]
pub enum Instruction<'i> {
Null(Null),
Call(Call<'i>),
Ap(Ap<'i>),
Seq(Seq<'i>),
@ -34,7 +33,9 @@ pub enum Instruction<'i> {
MisMatch(MisMatch<'i>),
FoldScalar(FoldScalar<'i>),
FoldStream(FoldStream<'i>),
New(New<'i>),
Next(Next<'i>),
Null(Null),
Error,
}
@ -88,6 +89,7 @@ pub struct FoldScalar<'i> {
#[serde(borrow)]
pub iterator: Scalar<'i>,
pub instruction: Rc<Instruction<'i>>,
pub span: Span,
}
/// (fold stream_iterable iterator instruction)
@ -97,6 +99,7 @@ pub struct FoldStream<'i> {
#[serde(borrow)]
pub iterator: Scalar<'i>,
pub instruction: Rc<Instruction<'i>>,
pub span: Span,
}
/// (fold stream_iterable iterator instruction)
@ -105,6 +108,14 @@ pub struct Next<'i> {
pub iterator: Scalar<'i>,
}
/// (new variable instruction)
#[derive(Serialize, Debug, PartialEq)]
pub struct New<'i> {
pub variable: Variable<'i>,
pub instruction: Box<Instruction<'i>>,
pub span: Span,
}
/// (null)
#[derive(Serialize, Debug, PartialEq)]
pub struct Null;

View File

@ -21,3 +21,121 @@ impl<'i> Ap<'i> {
Self { argument, result }
}
}
impl<'i> Call<'i> {
pub fn new(
triplet: Triplet<'i>,
args: Rc<Vec<Value<'i>>>,
output: CallOutputValue<'i>,
) -> Self {
Self {
triplet,
args,
output,
}
}
}
impl<'i> Seq<'i> {
pub fn new(
left_instruction: Box<Instruction<'i>>,
right_instruction: Box<Instruction<'i>>,
) -> Self {
Self(left_instruction, right_instruction)
}
}
impl<'i> Par<'i> {
pub fn new(
left_instruction: Box<Instruction<'i>>,
right_instruction: Box<Instruction<'i>>,
) -> Self {
Self(left_instruction, right_instruction)
}
}
impl<'i> Xor<'i> {
pub fn new(
left_instruction: Box<Instruction<'i>>,
right_instruction: Box<Instruction<'i>>,
) -> Self {
Self(left_instruction, right_instruction)
}
}
impl<'i> Match<'i> {
pub fn new(
left_value: Value<'i>,
right_value: Value<'i>,
instruction: Box<Instruction<'i>>,
) -> Self {
Self {
left_value,
right_value,
instruction,
}
}
}
impl<'i> MisMatch<'i> {
pub fn new(
left_value: Value<'i>,
right_value: Value<'i>,
instruction: Box<Instruction<'i>>,
) -> Self {
Self {
left_value,
right_value,
instruction,
}
}
}
impl<'i> FoldScalar<'i> {
pub fn new(
iterable: ScalarWithLambda<'i>,
iterator: Scalar<'i>,
instruction: Instruction<'i>,
span: Span,
) -> Self {
Self {
iterable,
iterator,
instruction: Rc::new(instruction),
span,
}
}
}
impl<'i> FoldStream<'i> {
pub fn new(
iterable: Stream<'i>,
iterator: Scalar<'i>,
instruction: Instruction<'i>,
span: Span,
) -> Self {
Self {
iterable,
iterator,
instruction: Rc::new(instruction),
span,
}
}
}
impl<'i> Next<'i> {
pub fn new(iterator: Scalar<'i>) -> Self {
Self { iterator }
}
}
impl<'i> New<'i> {
#[allow(clippy::self_named_constructors)]
pub fn new(variable: Variable<'i>, instruction: Box<Instruction<'i>>, span: Span) -> Self {
Self {
variable,
instruction,
span,
}
}
}

View File

@ -23,7 +23,6 @@ impl fmt::Display for Instruction<'_> {
use Instruction::*;
match self {
Null(null) => write!(f, "{}", null),
Call(call) => write!(f, "{}", call),
Ap(ap) => write!(f, "{}", ap),
Seq(seq) => write!(f, "{}", seq),
@ -34,6 +33,8 @@ impl fmt::Display for Instruction<'_> {
FoldScalar(fold) => write!(f, "{}", fold),
FoldStream(fold) => write!(f, "{}", fold),
Next(next) => write!(f, "{}", next),
New(new) => write!(f, "{}", new),
Null(null) => write!(f, "{}", null),
Error => Ok(()),
}
}
@ -107,3 +108,9 @@ impl fmt::Display for Next<'_> {
write!(f, "next")
}
}
impl fmt::Display for New<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "new {}", self.variable)
}
}

View File

@ -24,3 +24,4 @@ pub use values::*;
pub use crate::parser::lexer::LastErrorPath;
pub use crate::parser::lexer::Number;
pub use crate::parser::Span;

View File

@ -26,6 +26,7 @@ use serde::Serialize;
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct Scalar<'i> {
pub name: &'i str,
pub position: usize,
}
/// A scalar value with possible lambda expression.
@ -34,12 +35,14 @@ pub struct ScalarWithLambda<'i> {
pub name: &'i str,
#[serde(borrow)]
pub lambda: Option<LambdaAST<'i>>,
pub position: usize,
}
/// A stream without lambda.
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct Stream<'i> {
pub name: &'i str,
pub position: usize,
}
/// A stream with possible lambda expression.
@ -48,6 +51,7 @@ pub struct StreamWithLambda<'i> {
pub name: &'i str,
#[serde(borrow)]
pub lambda: Option<LambdaAST<'i>>,
pub position: usize,
}
/// A variable that could be either scalar or stream without lambda.

View File

@ -19,55 +19,73 @@ use air_lambda_parser::LambdaAST;
use air_lambda_parser::ValueAccessor;
impl<'i> ScalarWithLambda<'i> {
pub fn new(name: &'i str, lambda: Option<LambdaAST<'i>>) -> Self {
Self { name, lambda }
pub fn new(name: &'i str, lambda: Option<LambdaAST<'i>>, position: usize) -> Self {
Self {
name,
lambda,
position,
}
}
// it's unsafe method that should be used only for tests
pub(crate) fn from_raw_lambda(name: &'i str, lambda: Vec<ValueAccessor<'i>>) -> Self {
pub(crate) fn from_raw_lambda(
name: &'i str,
lambda: Vec<ValueAccessor<'i>>,
position: usize,
) -> Self {
let lambda = unsafe { LambdaAST::new_unchecked(lambda) };
Self {
name,
lambda: Some(lambda),
position,
}
}
}
impl<'i> StreamWithLambda<'i> {
pub fn new(name: &'i str, lambda: Option<LambdaAST<'i>>) -> Self {
Self { name, lambda }
pub fn new(name: &'i str, lambda: Option<LambdaAST<'i>>, position: usize) -> Self {
Self {
name,
lambda,
position,
}
}
// it's unsafe method that should be used only for tests
#[allow(dead_code)]
pub(crate) fn from_raw_lambda(name: &'i str, lambda: Vec<ValueAccessor<'i>>) -> Self {
pub(crate) fn from_raw_lambda(
name: &'i str,
lambda: Vec<ValueAccessor<'i>>,
position: usize,
) -> Self {
let lambda = unsafe { LambdaAST::new_unchecked(lambda) };
Self {
name,
lambda: Some(lambda),
position,
}
}
}
impl<'i> Scalar<'i> {
pub fn new(name: &'i str) -> Self {
Self { name }
pub fn new(name: &'i str, position: usize) -> Self {
Self { name, position }
}
}
impl<'i> Stream<'i> {
pub fn new(name: &'i str) -> Self {
Self { name }
pub fn new(name: &'i str, position: usize) -> Self {
Self { name, position }
}
}
impl<'i> Variable<'i> {
pub fn scalar(name: &'i str) -> Self {
Self::Scalar(Scalar { name })
pub fn scalar(name: &'i str, position: usize) -> Self {
Self::Scalar(Scalar::new(name, position))
}
pub fn stream(name: &'i str) -> Self {
Self::Stream(Stream { name })
pub fn stream(name: &'i str, position: usize) -> Self {
Self::Stream(Stream::new(name, position))
}
pub fn name(&self) -> &str {
@ -79,26 +97,20 @@ impl<'i> Variable<'i> {
}
impl<'i> VariableWithLambda<'i> {
pub fn scalar(name: &'i str) -> Self {
Self::Scalar(ScalarWithLambda { name, lambda: None })
pub fn scalar(name: &'i str, position: usize) -> Self {
Self::Scalar(ScalarWithLambda::new(name, None, position))
}
pub fn scalar_wl(name: &'i str, lambda: LambdaAST<'i>) -> Self {
Self::Scalar(ScalarWithLambda {
name,
lambda: Some(lambda),
})
pub fn scalar_wl(name: &'i str, lambda: LambdaAST<'i>, position: usize) -> Self {
Self::Scalar(ScalarWithLambda::new(name, Some(lambda), position))
}
pub fn stream(name: &'i str) -> Self {
Self::Stream(StreamWithLambda { name, lambda: None })
pub fn stream(name: &'i str, position: usize) -> Self {
Self::Stream(StreamWithLambda::new(name, None, position))
}
pub fn stream_wl(name: &'i str, lambda: LambdaAST<'i>) -> Self {
Self::Stream(StreamWithLambda {
name,
lambda: Some(lambda),
})
pub fn stream_wl(name: &'i str, lambda: LambdaAST<'i>, position: usize) -> Self {
Self::Stream(StreamWithLambda::new(name, Some(lambda), position))
}
pub fn name(&self) -> &str {
@ -117,15 +129,23 @@ impl<'i> VariableWithLambda<'i> {
// This function is unsafe and lambda must be non-empty, although it's used only for tests
#[allow(dead_code)]
pub(crate) fn from_raw_lambda_scalar(name: &'i str, lambda: Vec<ValueAccessor<'i>>) -> Self {
let scalar = ScalarWithLambda::from_raw_lambda(name, lambda);
pub(crate) fn from_raw_lambda_scalar(
name: &'i str,
lambda: Vec<ValueAccessor<'i>>,
position: usize,
) -> Self {
let scalar = ScalarWithLambda::from_raw_lambda(name, lambda, position);
Self::Scalar(scalar)
}
// This function is unsafe and lambda must be non-empty, although it's used only for tests
#[allow(dead_code)]
pub(crate) fn from_raw_lambda_stream(name: &'i str, lambda: Vec<ValueAccessor<'i>>) -> Self {
let stream = StreamWithLambda::from_raw_lambda(name, lambda);
pub(crate) fn from_raw_lambda_stream(
name: &'i str,
lambda: Vec<ValueAccessor<'i>>,
position: usize,
) -> Self {
let stream = StreamWithLambda::from_raw_lambda(name, lambda, position);
Self::Stream(stream)
}
}

View File

@ -28,10 +28,11 @@ Instr: Box<Instruction<'input>> = {
}
};
let output = output.map(CallOutputValue::Variable).unwrap_or(CallOutputValue::None);
let args = Rc::new(args);
let call = Call { triplet, args, output};
let span = Span { left, right };
let output = output.map(CallOutputValue::Variable).unwrap_or(CallOutputValue::None);
let call = Call::new(triplet, args, output);
let span = Span::new(left, right);
validator.met_call(&call, span);
Box::new(Instruction::Call(call))
@ -39,42 +40,51 @@ Instr: Box<Instruction<'input>> = {
<left: @L> "(" ap <arg:ApArgument> <result:ApResult> ")" <right: @R> => {
let apply = Ap::new(arg, result);
let span = Span { left, right };
let span = Span::new(left, right);
validator.met_ap(&apply, span);
Box::new(Instruction::Ap(apply))
},
"(" seq <l:Instr> <r:Instr> ")" => Box::new(Instruction::Seq(Seq(l, r))),
"(" par <l:Instr> <r:Instr> ")" => Box::new(Instruction::Par(Par(l, r))),
"(" seq <l:Instr> <r:Instr> ")" => Box::new(Instruction::Seq(Seq::new(l, r))),
"(" par <l:Instr> <r:Instr> ")" => Box::new(Instruction::Par(Par::new(l, r))),
"(" null ")" => Box::new(Instruction::Null(Null)),
<left: @L> "(" fold <iterable:FoldScalarIterable> <iterator_name:Scalar> <i:Instr> ")" <right: @R> => {
let iterator = Scalar { name: iterator_name };
let instruction = Rc::new(*i);
let fold = FoldScalar { iterable, iterator, instruction };
let span = Span { left, right };
<left: @L> "(" new <variable: ScriptVariable> <instruction:Instr> ")" <right: @R> => {
let span = Span::new(left, right);
let new = New::new(variable, instruction, span);
validator.met_new(&new, span);
Box::new(Instruction::New(new))
},
<left: @L> "(" fold <iterable:FoldScalarIterable> <iterator:Scalar> <i:Instr> ")" <right: @R> => {
let iterator = Scalar::new(iterator.0, iterator.1);
let span = Span::new(left, right);
let fold = FoldScalar::new(iterable, iterator, *i, span);
validator.met_fold_scalar(&fold, span);
Box::new(Instruction::FoldScalar(fold))
},
<left: @L> "(" fold <stream_name:Stream> <iterator_name:Scalar> <i:Instr> ")" <right: @R> => {
let iterable = Stream { name: stream_name };
let iterator = Scalar { name: iterator_name };
let instruction = Rc::new(*i);
let fold = FoldStream { iterable, iterator, instruction };
<left: @L> "(" fold <stream:Stream> <iterator:Scalar> <i:Instr> ")" <right: @R> => {
let iterable = Stream::new(stream.0, stream.1);
let iterator = Scalar::new(iterator.0, iterator.1);
let span = Span::new(left, right);
let fold = FoldStream::new(iterable, iterator, *i, span);
let span = Span { left, right };
validator.meet_fold_stream(&fold, span);
Box::new(Instruction::FoldStream(fold))
},
<left: @L> "(" next <iterator_name:Scalar> ")" <right: @R> => {
let iterator = Scalar { name: iterator_name };
let next = Next { iterator };
let span = Span { left, right };
<left: @L> "(" next <iterator:Scalar> ")" <right: @R> => {
let iterator = Scalar::new(iterator.0, iterator.1);
let next = Next::new(iterator);
let span = Span::new(left, right);
validator.met_next(&next, span);
Box::new(Instruction::Next(next))
@ -83,16 +93,16 @@ Instr: Box<Instruction<'input>> = {
"(" xor <l:Instr> <r:Instr> ")" => Box::new(Instruction::Xor(Xor(l, r))),
<left: @L> "(" match_ <l:Value> <r:Value> <i:Instr> ")" <right: @R> => {
let match_ = Match { left_value: l, right_value: r, instruction: i};
let span = Span { left, right };
let match_ = Match::new(l, r, i);
let span = Span::new(left, right);
validator.met_match(&match_, span);
Box::new(Instruction::Match(match_))
},
<left: @L> "(" mismatch <l:Value> <r:Value> <i:Instr> ")" <right: @R> => {
let mismatch = MisMatch { left_value: l, right_value: r, instruction: i};
let span = Span { left, right };
let mismatch = MisMatch::new(l, r, i);
let span = Span::new(left, right);
validator.met_mismatch(&mismatch, span);
Box::new(Instruction::MisMatch(mismatch))
@ -119,13 +129,13 @@ ApResult = ScriptVariable;
CallOutput = ScriptVariable;
ScriptVariable: Variable<'input> = {
<scalar:Scalar> => Variable::scalar(scalar),
<stream:Stream> => Variable::stream(stream),
<scalar:Scalar> => Variable::scalar(scalar.0, scalar.1),
<stream:Stream> => Variable::stream(stream.0, stream.1),
};
FoldScalarIterable: ScalarWithLambda<'input> = {
<scalar:Scalar> => ScalarWithLambda::new(scalar, None),
<scalar:ScalarWithLambda> => ScalarWithLambda::new(scalar.0, Some(scalar.1)),
<scalar:Scalar> => ScalarWithLambda::new(scalar.0, None, scalar.1),
<scalar:ScalarWithLambda> => ScalarWithLambda::new(scalar.0, Some(scalar.1), scalar.2),
};
Function = CallInstrValue;
@ -135,10 +145,10 @@ ServiceId = CallInstrValue;
CallInstrValue: CallInstrValue<'input> = {
InitPeerId => CallInstrValue::InitPeerId,
<l:Literal> => CallInstrValue::Literal(l),
<scalar:Scalar> => CallInstrValue::Variable(VariableWithLambda::scalar(scalar)),
<scalar:ScalarWithLambda> => CallInstrValue::Variable(VariableWithLambda::scalar_wl(scalar.0, scalar.1)),
<stream:Stream> => CallInstrValue::Variable(VariableWithLambda::stream(stream)),
<stream:StreamWithLambda> => CallInstrValue::Variable(VariableWithLambda::stream_wl(stream.0, stream.1)),
<scalar:Scalar> => CallInstrValue::Variable(VariableWithLambda::scalar(scalar.0, scalar.1)),
<scalar:ScalarWithLambda> => CallInstrValue::Variable(VariableWithLambda::scalar_wl(scalar.0, scalar.1, scalar.2)),
<stream:Stream> => CallInstrValue::Variable(VariableWithLambda::stream(stream.0, stream.1)),
<stream:StreamWithLambda> => CallInstrValue::Variable(VariableWithLambda::stream_wl(stream.0, stream.1, stream.2)),
}
Arg = Value;
@ -150,10 +160,10 @@ Value: Value<'input> = {
<n:Number> => Value::Number(n),
<b:Boolean> => Value::Boolean(b),
EmptyArray => Value::EmptyArray,
<scalar_name:Scalar> => Value::Variable(VariableWithLambda::scalar(scalar_name)),
<scalar:ScalarWithLambda> => Value::Variable(VariableWithLambda::scalar_wl(scalar.0, scalar.1)),
<stream_name:Stream> => Value::Variable(VariableWithLambda::stream(stream_name)),
<stream:StreamWithLambda> => Value::Variable(VariableWithLambda::stream_wl(stream.0, stream.1)),
<scalar:Scalar> => Value::Variable(VariableWithLambda::scalar(scalar.0, scalar.1)),
<scalar:ScalarWithLambda> => Value::Variable(VariableWithLambda::scalar_wl(scalar.0, scalar.1, scalar.2)),
<stream:Stream> => Value::Variable(VariableWithLambda::stream(stream.0, stream.1)),
<stream:StreamWithLambda> => Value::Variable(VariableWithLambda::stream_wl(stream.0, stream.1, stream.2)),
}
ApArgument: ApArgument<'input> = {
@ -163,8 +173,8 @@ ApArgument: ApArgument<'input> = {
<n:Number> => ApArgument::Number(n),
<b:Boolean> => ApArgument::Boolean(b),
EmptyArray => ApArgument::EmptyArray,
<scalar:Scalar> => ApArgument::Scalar(ScalarWithLambda { name: scalar, lambda: None }),
<scalar:ScalarWithLambda> => ApArgument::Scalar(ScalarWithLambda { name: scalar.0, lambda: Some(scalar.1) }),
<scalar:Scalar> => ApArgument::Scalar(ScalarWithLambda::new(scalar.0, None, scalar.1)),
<scalar:ScalarWithLambda> => ApArgument::Scalar(ScalarWithLambda::new(scalar.0, Some(scalar.1), scalar.2)),
}
extern {
@ -179,10 +189,10 @@ extern {
EmptyArray => Token::SquareBrackets,
Literal => Token::StringLiteral(<&'input str>),
Scalar => Token::Scalar { name:<&'input str> },
ScalarWithLambda => Token::ScalarWithLambda { name: <&'input str>, lambda: <LambdaAST<'input>>},
Stream => Token::Stream { name: <&'input str> },
StreamWithLambda => Token::StreamWithLambda {name: <&'input str>, lambda:<LambdaAST<'input>>},
Scalar => Token::Scalar { name:<&'input str>, position: <usize> },
ScalarWithLambda => Token::ScalarWithLambda { name: <&'input str>, lambda: <LambdaAST<'input>>, position: <usize> },
Stream => Token::Stream { name: <&'input str>, position: <usize> },
StreamWithLambda => Token::StreamWithLambda {name: <&'input str>, lambda:<LambdaAST<'input>>, position: <usize>},
Number => Token::Number(<Number>),
Boolean => Token::Boolean(<bool>),
@ -193,10 +203,11 @@ extern {
ap => Token::Ap,
seq => Token::Seq,
par => Token::Par,
null => Token::Null,
fold => Token::Fold,
xor => Token::Xor,
new => Token::New,
next => Token::Next,
null => Token::Null,
match_ => Token::Match,
mismatch => Token::MisMatch,
}

File diff suppressed because it is too large Load Diff

View File

@ -136,6 +136,9 @@ fn parser_error_to_label(file_id: usize, error: ParserError) -> Label<usize> {
InvalidCallTriplet(start, end) => {
Label::primary(file_id, start..end).with_message(error.to_string())
}
IteratorRestrictionNotAllowed(start, end, _) => {
Label::primary(file_id, start..end).with_message(error.to_string())
}
}
}

View File

@ -34,6 +34,9 @@ pub enum ParserError {
/// Semantic errors in a call instructions.
#[error("call should have service id specified by peer part or function part")]
InvalidCallTriplet(usize, usize),
#[error("new can't be applied to a '{2}' because it's an iterator")]
IteratorRestrictionNotAllowed(usize, usize, String),
}
impl From<std::convert::Infallible> for ParserError {

View File

@ -192,10 +192,11 @@ fn string_to_token(input: &str, start_pos: usize) -> LexerResult<Token> {
AP_INSTR => Ok(Token::Ap),
SEQ_INSTR => Ok(Token::Seq),
PAR_INSTR => Ok(Token::Par),
NULL_INSTR => Ok(Token::Null),
FOLD_INSTR => Ok(Token::Fold),
XOR_INSTR => Ok(Token::Xor),
NEW_INSTR => Ok(Token::New),
NEXT_INSTR => Ok(Token::Next),
NULL_INSTR => Ok(Token::Null),
MATCH_INSTR => Ok(Token::Match),
MISMATCH_INSTR => Ok(Token::MisMatch),
@ -237,10 +238,11 @@ const CALL_INSTR: &str = "call";
const AP_INSTR: &str = "ap";
const SEQ_INSTR: &str = "seq";
const PAR_INSTR: &str = "par";
const NULL_INSTR: &str = "null";
const FOLD_INSTR: &str = "fold";
const XOR_INSTR: &str = "xor";
const NEW_INSTR: &str = "new";
const NEXT_INSTR: &str = "next";
const NULL_INSTR: &str = "null";
const MATCH_INSTR: &str = "match";
const MISMATCH_INSTR: &str = "mismatch";

View File

@ -274,17 +274,31 @@ impl<'input> CallVariableParser<'input> {
fn to_variable_token<'v>(&self, name: &'v str) -> Token<'v> {
if self.state.is_first_stream_tag {
Token::Stream { name }
Token::Stream {
name,
position: self.start_pos,
}
} else {
Token::Scalar { name }
Token::Scalar {
name,
position: self.start_pos,
}
}
}
fn to_variable_token_with_lambda<'v>(&self, name: &'v str, lambda: LambdaAST<'v>) -> Token<'v> {
if self.state.is_first_stream_tag {
Token::StreamWithLambda { name, lambda }
Token::StreamWithLambda {
name,
lambda,
position: self.start_pos,
}
} else {
Token::ScalarWithLambda { name, lambda }
Token::ScalarWithLambda {
name,
lambda,
position: self.start_pos,
}
}
}

View File

@ -160,7 +160,14 @@ fn stream() {
lexer_test(
STREAM,
Single(Ok((0, Token::Stream { name: STREAM }, STREAM.len()))),
Single(Ok((
0,
Token::Stream {
name: STREAM,
position: 0,
},
STREAM.len(),
))),
);
}
@ -284,6 +291,7 @@ fn lambda() {
ValueAccessor::ArrayAccess { idx: 1 },
])
},
position: 0,
},
LAMBDA.len(),
))),
@ -453,6 +461,7 @@ fn booleans() {
0,
Token::Scalar {
name: NON_BOOL_CONST,
position: 0,
},
NON_BOOL_CONST.len(),
))),

View File

@ -34,17 +34,21 @@ pub enum Token<'input> {
StringLiteral(&'input str),
Scalar {
name: &'input str,
position: usize,
},
ScalarWithLambda {
name: &'input str,
lambda: LambdaAST<'input>,
position: usize,
},
Stream {
name: &'input str,
position: usize,
},
StreamWithLambda {
name: &'input str,
lambda: LambdaAST<'input>,
position: usize,
},
Number(Number),
Boolean(bool),
@ -56,10 +60,11 @@ pub enum Token<'input> {
Ap,
Seq,
Par,
Null,
Fold,
Xor,
New,
Next,
Null,
Match,
MisMatch,
}

View File

@ -17,6 +17,7 @@
pub mod air_parser;
mod air_utils;
pub(crate) mod lexer;
mod span;
// air is auto-generated, so exclude it from `cargo fmt -- --check` and `cargo clippy`
#[rustfmt::skip]
@ -32,12 +33,7 @@ pub mod tests;
pub use self::air_parser::parse;
pub use air::AIRParser;
pub use lexer::AIRLexer;
pub use span::Span;
pub use validator::VariableValidator;
use errors::ParserError;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Span {
pub left: usize,
pub right: usize,
}

View File

@ -0,0 +1,34 @@
/*
* Copyright 2021 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 serde::Deserialize;
use serde::Serialize;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct Span {
pub left: usize,
pub right: usize,
}
impl Span {
pub fn new(left: usize, right: usize) -> Self {
Self { left, right }
}
pub fn contains(&self, position: usize) -> bool {
self.left < position && position < self.right
}
}

View File

@ -27,7 +27,7 @@ fn ap_with_literal() {
let actual = parse(source_code);
let expected = ap(
ApArgument::Literal("some_string"),
Variable::stream("$stream"),
Variable::stream("$stream", 27),
);
assert_eq!(actual, expected);
@ -42,7 +42,7 @@ fn ap_with_number() {
let actual = parse(source_code);
let expected = ap(
ApArgument::Number(Number::Int(-100)),
Variable::stream("$stream"),
Variable::stream("$stream", 18),
);
assert_eq!(actual, expected);
@ -55,7 +55,7 @@ fn ap_with_bool() {
"#;
let actual = parse(source_code);
let expected = ap(ApArgument::Boolean(true), Variable::stream("$stream"));
let expected = ap(ApArgument::Boolean(true), Variable::stream("$stream", 18));
assert_eq!(actual, expected);
}
@ -69,7 +69,7 @@ fn ap_with_last_error() {
let actual = parse(source_code);
let expected = ap(
ApArgument::LastError(LastErrorPath::Message),
Variable::stream("$stream"),
Variable::stream("$stream", 33),
);
assert_eq!(actual, expected);

View File

@ -35,14 +35,15 @@ fn parse_json_path() {
CallInstrValue::Variable(VariableWithLambda::from_raw_lambda_scalar(
"peer_id",
vec![ValueAccessor::FieldAccess { field_name: "a" }],
15,
)),
CallInstrValue::Literal("service_id"),
CallInstrValue::Literal("function_name"),
Rc::new(vec![
Value::Literal("hello"),
Value::Variable(VariableWithLambda::scalar("name")),
Value::Variable(VariableWithLambda::scalar("name", 68)),
]),
CallOutputValue::Variable(Variable::stream("$void")),
CallOutputValue::Variable(Variable::stream("$void", 74)),
);
assert_eq!(instruction, expected);
}
@ -55,13 +56,13 @@ fn parse_empty_array() {
let actual = parse(source_code);
let expected = call(
CallInstrValue::Variable(VariableWithLambda::scalar("peer_id")),
CallInstrValue::Variable(VariableWithLambda::scalar("service_id")),
CallInstrValue::Variable(VariableWithLambda::scalar("peer_id", 15)),
CallInstrValue::Variable(VariableWithLambda::scalar("service_id", 24)),
CallInstrValue::Literal("function_name"),
Rc::new(vec![
Value::Literal(""),
Value::EmptyArray,
Value::Variable(VariableWithLambda::scalar("arg")),
Value::Variable(VariableWithLambda::scalar("arg", 59)),
]),
CallOutputValue::None,
);
@ -77,11 +78,11 @@ fn parse_empty_array_2() {
let actual = parse(source_code);
let expected = call(
CallInstrValue::Variable(VariableWithLambda::scalar("peer_id")),
CallInstrValue::Variable(VariableWithLambda::scalar("peer_id", 15)),
CallInstrValue::Literal("service_id"),
CallInstrValue::Literal("function_name"),
Rc::new(vec![
Value::Variable(VariableWithLambda::scalar("k")),
Value::Variable(VariableWithLambda::scalar("k", 55)),
Value::EmptyArray,
Value::EmptyArray,
]),
@ -206,11 +207,12 @@ fn parse_json_path_complex() {
CallInstrValue::Variable(VariableWithLambda::from_raw_lambda_scalar(
"m",
vec![ValueAccessor::ArrayAccess { idx: 1 }],
32,
)),
CallInstrValue::Literal("service_id"),
CallInstrValue::Literal("function_name"),
Rc::new(vec![]),
CallOutputValue::Variable(Variable::scalar("void")),
CallOutputValue::Variable(Variable::scalar("void", 75)),
),
call(
CallInstrValue::Variable(VariableWithLambda::from_raw_lambda_scalar(
@ -224,11 +226,12 @@ fn parse_json_path_complex() {
ValueAccessor::FieldAccess { field_name: "cde" },
ValueAccessor::ArrayAccess { idx: 1 },
],
99,
)),
CallInstrValue::Literal("service_id"),
CallInstrValue::Literal("function_name"),
Rc::new(vec![]),
CallOutputValue::Variable(Variable::scalar("void")),
CallOutputValue::Variable(Variable::scalar("void", 162)),
),
);
assert_eq!(instruction, expected);
@ -246,6 +249,7 @@ fn json_path_square_braces() {
vec![ValueAccessor::FieldAccess {
field_name: "peer_id",
}],
15,
)),
CallInstrValue::Literal("return"),
CallInstrValue::Literal(""),
@ -259,13 +263,15 @@ fn json_path_square_braces() {
ValueAccessor::ArrayAccess { idx: 0 },
ValueAccessor::FieldAccess { field_name: "abc" },
],
43,
)),
Value::Variable(VariableWithLambda::from_raw_lambda_scalar(
"u",
vec![ValueAccessor::FieldAccess { field_name: "name" }],
64,
)),
]),
CallOutputValue::Variable(Variable::stream("$void")),
CallOutputValue::Variable(Variable::stream("$void", 74)),
);
assert_eq!(instruction, expected);
@ -352,14 +358,14 @@ fn seq_par_call() {
CallInstrValue::Literal("local_service_id"),
CallInstrValue::Literal("local_fn_name"),
Rc::new(vec![]),
CallOutputValue::Variable(Variable::scalar("result_1")),
CallOutputValue::Variable(Variable::scalar("result_1", 108)),
),
call(
CallInstrValue::Literal(peer_id),
CallInstrValue::Literal("service_id"),
CallInstrValue::Literal("fn_name"),
Rc::new(vec![]),
CallOutputValue::Variable(Variable::scalar("g")),
CallOutputValue::Variable(Variable::scalar("g", 183)),
),
),
call(
@ -367,7 +373,7 @@ fn seq_par_call() {
CallInstrValue::Literal("local_service_id"),
CallInstrValue::Literal("local_fn_name"),
Rc::new(vec![]),
CallOutputValue::Variable(Variable::scalar("result_2")),
CallOutputValue::Variable(Variable::scalar("result_2", 273)),
),
);
@ -407,14 +413,14 @@ fn seq_with_empty_and_dash() {
CallInstrValue::Literal(""),
CallInstrValue::Literal(""),
Rc::new(vec![Value::Literal("module-bytes")]),
CallOutputValue::Variable(Variable::scalar("module-bytes")),
CallOutputValue::Variable(Variable::scalar("module-bytes", 119)),
),
call(
CallInstrValue::Literal("set_variables"),
CallInstrValue::Literal(""),
CallInstrValue::Literal(""),
Rc::new(vec![Value::Literal("module_config")]),
CallOutputValue::Variable(Variable::scalar("module_config")),
CallOutputValue::Variable(Variable::scalar("module_config", 201)),
),
),
call(
@ -422,7 +428,7 @@ fn seq_with_empty_and_dash() {
CallInstrValue::Literal(""),
CallInstrValue::Literal(""),
Rc::new(vec![Value::Literal("blueprint")]),
CallOutputValue::Variable(Variable::scalar("blueprint")),
CallOutputValue::Variable(Variable::scalar("blueprint", 294)),
),
),
seq(
@ -431,10 +437,10 @@ fn seq_with_empty_and_dash() {
CallInstrValue::Literal("add_module"),
CallInstrValue::Literal(""),
Rc::new(vec![
Value::Variable(VariableWithLambda::scalar("module-bytes")),
Value::Variable(VariableWithLambda::scalar("module_config")),
Value::Variable(VariableWithLambda::scalar("module-bytes", 381)),
Value::Variable(VariableWithLambda::scalar("module_config", 394)),
]),
CallOutputValue::Variable(Variable::scalar("module")),
CallOutputValue::Variable(Variable::scalar("module", 409)),
),
seq(
Instruction::Call(Call {
@ -445,8 +451,9 @@ fn seq_with_empty_and_dash() {
},
args: Rc::new(vec![Value::Variable(VariableWithLambda::scalar(
"blueprint",
490,
))]),
output: CallOutputValue::Variable(Variable::scalar("blueprint_id")),
output: CallOutputValue::Variable(Variable::scalar("blueprint_id", 501)),
}),
seq(
call(
@ -455,8 +462,9 @@ fn seq_with_empty_and_dash() {
CallInstrValue::Literal(""),
Rc::new(vec![Value::Variable(VariableWithLambda::scalar(
"blueprint_id",
589,
))]),
CallOutputValue::Variable(Variable::scalar("service_id")),
CallOutputValue::Variable(Variable::scalar("service_id", 603)),
),
call(
CallInstrValue::Literal("remote_peer_id"),
@ -464,8 +472,9 @@ fn seq_with_empty_and_dash() {
CallInstrValue::Literal(""),
Rc::new(vec![Value::Variable(VariableWithLambda::scalar(
"service_id",
671,
))]),
CallOutputValue::Variable(Variable::scalar("client_result")),
CallOutputValue::Variable(Variable::scalar("client_result", 683)),
),
),
),
@ -484,9 +493,9 @@ fn no_output() {
let actual = parse(source_code);
let expected = call(
CallInstrValue::Variable(VariableWithLambda::scalar("peer")),
CallInstrValue::Variable(VariableWithLambda::scalar("service")),
CallInstrValue::Variable(VariableWithLambda::scalar("fname")),
CallInstrValue::Variable(VariableWithLambda::scalar("peer", 15)),
CallInstrValue::Variable(VariableWithLambda::scalar("service", 21)),
CallInstrValue::Variable(VariableWithLambda::scalar("fname", 29)),
Rc::new(vec![]),
CallOutputValue::None,
);

View File

@ -53,31 +53,47 @@ pub(super) fn seqnn() -> Instruction<'static> {
seq(null(), null())
}
pub(super) fn new<'i>(
variable: Variable<'i>,
instruction: Instruction<'i>,
span: Span,
) -> Instruction<'i> {
Instruction::New(New {
variable,
instruction: Box::new(instruction),
span,
})
}
pub(super) fn null() -> Instruction<'static> {
Instruction::Null(Null)
}
pub(super) fn fold_scalar<'a>(
iterable: ScalarWithLambda<'a>,
iterator: &'a str,
iterator: Scalar<'a>,
instruction: Instruction<'a>,
span: Span,
) -> Instruction<'a> {
Instruction::FoldScalar(FoldScalar {
iterable,
iterator: Scalar::new(iterator),
iterator,
instruction: std::rc::Rc::new(instruction),
span,
})
}
pub(super) fn fold_stream<'a>(
stream_name: &'a str,
iterator: &'a str,
iterable: Stream<'a>,
iterator: Scalar<'a>,
instruction: Instruction<'a>,
span: Span,
) -> Instruction<'a> {
Instruction::FoldStream(FoldStream {
iterable: Stream::new(stream_name),
iterator: Scalar::new(iterator),
iterable,
iterator,
instruction: std::rc::Rc::new(instruction),
span,
})
}

View File

@ -67,7 +67,12 @@ fn parse_fold() {
)
"#;
let instruction = parse(&source_code);
let expected = fold_scalar(ScalarWithLambda::new("iterable", None), "i", null());
let expected = fold_scalar(
ScalarWithLambda::new("iterable", None, 15),
Scalar::new("i", 24),
null(),
Span::new(9, 54),
);
assert_eq!(instruction, expected);
}
@ -84,9 +89,11 @@ fn fold_json_path() {
ScalarWithLambda::from_raw_lambda(
"members",
vec![ValueAccessor::ArrayAccess { idx: 123321 }],
33,
),
"m",
Scalar::new("m", 52),
null(),
Span::new(27, 61),
);
assert_eq!(instruction, expected);
}
@ -98,7 +105,12 @@ fn fold_on_stream() {
"#;
let instruction = parse(source_code);
let expected = fold_stream("$stream", "iterator", null());
let expected = fold_stream(
Stream::new("$stream", 15),
Scalar::new("iterator", 23),
null(),
Span::new(9, 39),
);
assert_eq!(instruction, expected);
}
@ -119,9 +131,11 @@ fn comments() {
},
ValueAccessor::ArrayAccess { idx: 1 },
],
33,
),
"m",
Scalar::new("m", 52),
null(),
Span::new(27, 61),
);
assert_eq!(instruction, expected);
}
@ -138,9 +152,10 @@ fn parse_fold_with_xor_par_seq() {
let instruction = parse(&source_code);
let instr = binary_instruction(*name);
let expected = fold_scalar(
ScalarWithLambda::new("iterable", None),
"i",
ScalarWithLambda::new("iterable", None, 6),
Scalar::new("i", 15),
instr(null(), null()),
Span::new(0, 58),
);
assert_eq!(instruction, expected);
}

View File

@ -27,8 +27,8 @@ fn parse_match() {
"#;
let instruction = parse(&source_code);
let expected = match_(
Value::Variable(VariableWithLambda::scalar("v1")),
Value::Variable(VariableWithLambda::scalar("v2")),
Value::Variable(VariableWithLambda::scalar("v1", 16)),
Value::Variable(VariableWithLambda::scalar("v2", 19)),
null(),
);
assert_eq!(instruction, expected);
@ -43,7 +43,7 @@ fn parse_match_with_init_peer_id() {
"#;
let instruction = parse(&source_code);
let expected = match_(
Value::Variable(VariableWithLambda::scalar("v1")),
Value::Variable(VariableWithLambda::scalar("v1", 16)),
Value::InitPeerId,
null(),
);
@ -59,8 +59,8 @@ fn parse_mismatch() {
"#;
let instruction = parse(&source_code);
let expected = mismatch(
Value::Variable(VariableWithLambda::scalar("v1")),
Value::Variable(VariableWithLambda::scalar("v2")),
Value::Variable(VariableWithLambda::scalar("v1", 19)),
Value::Variable(VariableWithLambda::scalar("v2", 22)),
null(),
);
assert_eq!(instruction, expected);
@ -74,7 +74,7 @@ fn match_with_bool() {
)
"#;
let left_value = Value::Variable(VariableWithLambda::scalar("isOnline"));
let left_value = Value::Variable(VariableWithLambda::scalar("isOnline", 17));
let right_value = Value::Boolean(true);
let null = null();
let expected = match_(left_value, right_value, null);
@ -92,7 +92,7 @@ fn mismatch_with_bool() {
"#;
let left_value = Value::Boolean(true);
let right_value = Value::Variable(VariableWithLambda::scalar("isOnline"));
let right_value = Value::Variable(VariableWithLambda::scalar("isOnline", 25));
let null = null();
let expected = mismatch(left_value, right_value, null);

View File

@ -19,6 +19,7 @@ mod call;
mod dsl;
mod fold;
mod match_;
mod new;
mod null;
mod par;
mod seq;

View File

@ -0,0 +1,84 @@
/*
* Copyright 2021 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 super::dsl::*;
use super::parse;
use crate::ast::*;
use crate::parser::ParserError;
use lalrpop_util::ParseError;
#[test]
fn parse_new_with_scalar() {
let source_code = r#"(new scalar
(null)
)
"#;
let instruction = parse(source_code);
let expected = new(Variable::scalar("scalar", 5), null(), Span::new(0, 40));
assert_eq!(instruction, expected);
}
#[test]
fn parse_new_with_stream() {
let source_code = r#"(new $stream
(null)
)
"#;
let instruction = parse(source_code);
let expected = new(Variable::stream("$stream", 5), null(), Span::new(0, 41));
assert_eq!(instruction, expected);
}
#[test]
fn iterators_cant_be_restricted() {
let source_code = r#"
(seq
(call "" ("" "") [] iterable)
(fold iterable iterator
(new iterator
(call "" ("" "") [iterator])
)
)
)
"#;
let lexer = crate::AIRLexer::new(source_code);
let parser = crate::AIRParser::new();
let mut errors = Vec::new();
let mut validator = crate::parser::VariableValidator::new();
parser
.parse(source_code, &mut errors, &mut validator, lexer)
.expect("parser shouldn't fail");
let errors = validator.finalize();
assert_eq!(errors.len(), 1);
println!("{:?}", errors);
let error = &errors[0].error;
let parser_error = match error {
ParseError::User { error } => error,
_ => panic!("unexpected error type"),
};
assert!(matches!(
parser_error,
ParserError::IteratorRestrictionNotAllowed(..)
));
}

View File

@ -32,11 +32,11 @@ fn parse_seq() {
let instruction = parse(source_code);
let expected = seq(
call(
CallInstrValue::Variable(VariableWithLambda::scalar("peer_id")),
CallInstrValue::Variable(VariableWithLambda::scalar("service_id")),
CallInstrValue::Variable(VariableWithLambda::scalar("function_name")),
CallInstrValue::Variable(VariableWithLambda::scalar("peer_id", 32)),
CallInstrValue::Variable(VariableWithLambda::scalar("service_id", 41)),
CallInstrValue::Variable(VariableWithLambda::scalar("function_name", 52)),
Rc::new(vec![Value::EmptyArray, Value::EmptyArray]),
CallOutputValue::Variable(Variable::scalar("output")),
CallOutputValue::Variable(Variable::scalar("output", 75)),
),
call(
CallInstrValue::Literal("peer_id"),
@ -45,7 +45,7 @@ fn parse_seq() {
Rc::new(vec![
Value::Literal("hello"),
Value::EmptyArray,
Value::Variable(VariableWithLambda::scalar("name")),
Value::Variable(VariableWithLambda::scalar("name", 154)),
]),
CallOutputValue::None,
),
@ -68,16 +68,16 @@ fn parse_seq_seq() {
let expected = seq(
seq(
call(
CallInstrValue::Variable(VariableWithLambda::scalar("peer_id")),
CallInstrValue::Variable(VariableWithLambda::scalar("service_id")),
CallInstrValue::Variable(VariableWithLambda::scalar("function_name")),
CallInstrValue::Variable(VariableWithLambda::scalar("peer_id", 53)),
CallInstrValue::Variable(VariableWithLambda::scalar("service_id", 62)),
CallInstrValue::Variable(VariableWithLambda::scalar("function_name", 073)),
Rc::new(vec![]),
CallOutputValue::None,
),
call(
CallInstrValue::Variable(VariableWithLambda::scalar("peer_id")),
CallInstrValue::Variable(VariableWithLambda::scalar("peer_id", 115)),
CallInstrValue::Literal("service_B"),
CallInstrValue::Variable(VariableWithLambda::scalar("function_name")),
CallInstrValue::Variable(VariableWithLambda::scalar("function_name", 147)),
Rc::new(vec![]),
CallOutputValue::None,
),
@ -88,9 +88,9 @@ fn parse_seq_seq() {
CallInstrValue::Literal("function_name"),
Rc::new(vec![
Value::Literal("hello"),
Value::Variable(VariableWithLambda::scalar("name")),
Value::Variable(VariableWithLambda::scalar("name", 248)),
]),
CallOutputValue::Variable(Variable::stream("$output")),
CallOutputValue::Variable(Variable::stream("$output", 254)),
),
);
assert_eq!(instruction, expected);

View File

@ -47,6 +47,9 @@ pub struct VariableValidator<'i> {
/// Contains all met iterable in call and next, they will be resolved after the whole parsing
/// due to the way how lalrpop work.
unresolved_iterables: MultiMap<&'i str, Span>,
/// Contains all names that should be checked that they are not iterators.
check_for_non_iterators: Vec<(&'i str, Span)>,
}
impl<'i> VariableValidator<'i> {
@ -87,6 +90,13 @@ impl<'i> VariableValidator<'i> {
self.met_iterator_definition(&fold.iterator, span);
}
pub(super) fn met_new(&mut self, new: &New<'i>, span: Span) {
self.check_for_non_iterators
.push((variable_name(&new.variable), span));
// new defines a new variable
self.met_variable_definition(&new.variable, span);
}
pub(super) fn met_next(&mut self, next: &Next<'i>, span: Span) {
let iterable_name = next.iterator.name;
// due to the right to left convolution in lalrpop, a next instruction will be met earlier
@ -124,6 +134,12 @@ impl<'i> VariableValidator<'i> {
}
}
for (name, span) in self.check_for_non_iterators.iter() {
if self.contains_iterable(name, *span) {
add_to_errors(*name, &mut errors, *span, Token::New);
}
}
errors
}
@ -254,6 +270,9 @@ fn add_to_errors<'err, 'i>(
let variable_name = variable_name.into();
let error = match token {
Token::Next => ParserError::UndefinedIterable(span.left, span.right, variable_name),
Token::New => {
ParserError::IteratorRestrictionNotAllowed(span.left, span.right, variable_name)
}
_ => ParserError::UndefinedVariable(span.left, span.right, variable_name),
};
let error = ParseError::User { error };

View File

@ -14,6 +14,8 @@
* limitations under the License.
*/
use std::collections::HashMap;
/// Intended to track a number of executed instruction of each type. For instructions that
/// have a corresponding state in data, it tracks number of executed instructions on
/// current peer (executed) and overall number (seen) of met instructions of such type.
@ -24,6 +26,7 @@ pub struct InstructionTracker {
pub fold: FoldTracker,
pub match_count: u32,
pub mismatch_count: u32,
pub new_tracker: NewTracker,
pub next_count: u32,
pub null_count: u32,
pub par: ParTracker,
@ -55,6 +58,14 @@ pub struct ParTracker {
pub executed_count: u32,
}
#[derive(Default, Debug, PartialEq, Eq)]
pub struct NewTracker {
/// Mapping from a new instruction position in a script
/// to a number of their execution. This is needed to
/// support private stream generation mappings.
pub executed_count: HashMap<usize, u32>,
}
impl InstructionTracker {
pub fn meet_ap(&mut self) {
self.ap.seen_count += 1;
@ -111,4 +122,24 @@ impl InstructionTracker {
pub fn meet_xor(&mut self) {
self.xor_count += 1;
}
pub fn meet_new(&mut self, position: usize) {
use std::collections::hash_map::Entry::{Occupied, Vacant};
match self.new_tracker.executed_count.entry(position) {
Occupied(mut entry) => *entry.get_mut() += 1,
Vacant(entry) => {
entry.insert(1);
}
};
}
}
impl NewTracker {
pub fn get_iteration(&self, position: usize) -> u32 {
self.executed_count
.get(&position)
.copied()
.unwrap_or_default()
}
}

View File

@ -1,3 +1,8 @@
## Version 0.2.2
[PR 169](https://github.com/fluencelabs/aquavm/pull/169):
- added a new field for tracking generations of private streams
## Version 0.2.1
[PR 130](https://github.com/fluencelabs/aquavm/pull/130):

View File

@ -1,7 +1,7 @@
[package]
name = "air-interpreter-data"
description = "Data format of the AIR interpreter"
version = "0.2.0"
version = "0.2.2"
authors = ["Fluence Labs"]
edition = "2018"
license = "Apache-2.0"

View File

@ -15,13 +15,13 @@
*/
use super::ExecutedState;
use super::GlobalStreamGens;
use super::RestrictedStreamGens;
use super::DATA_FORMAT_VERSION;
use serde::Deserialize;
use serde::Serialize;
use std::collections::HashMap;
use std::ops::Deref;
pub type StreamGenerations = HashMap<String, u32>;
pub type ExecutionTrace = Vec<ExecutedState>;
/// The AIR interpreter could be considered as a function
@ -33,9 +33,11 @@ pub struct InterpreterData {
/// Trace of AIR execution, which contains executed call, par and fold states.
pub trace: ExecutionTrace,
/// Contains maximum generation for each stream. This info will be used while merging
/// values in streams.
pub streams: StreamGenerations,
/// Contains maximum generation for each global stream. This info will be used while merging
/// values in streams. This field is also needed for backward compatibility with
/// <= 0.2.1 versions.
#[serde(rename = "streams")] // for compatibility with versions <= 0.2.1
pub global_streams: GlobalStreamGens,
/// Version of this data format.
pub version: semver::Version,
@ -44,28 +46,37 @@ pub struct InterpreterData {
#[serde(default)]
#[serde(rename = "lcid")]
pub last_call_request_id: u32,
/// Contains maximum generation for each private stream. This info will be used while merging
/// values in streams.
#[serde(default)]
#[serde(rename = "r_streams")]
pub restricted_streams: RestrictedStreamGens,
}
impl InterpreterData {
pub fn new() -> Self {
Self {
trace: <_>::default(),
streams: <_>::default(),
global_streams: <_>::default(),
version: DATA_FORMAT_VERSION.deref().clone(),
last_call_request_id: 0,
restricted_streams: <_>::default(),
}
}
pub fn from_execution_result(
trace: ExecutionTrace,
streams: StreamGenerations,
streams: GlobalStreamGens,
restricted_streams: RestrictedStreamGens,
last_call_request_id: u32,
) -> Self {
Self {
trace,
streams,
global_streams: streams,
version: DATA_FORMAT_VERSION.deref().clone(),
last_call_request_id,
restricted_streams,
}
}
@ -98,14 +109,14 @@ mod tests {
#[derive(Serialize, Deserialize)]
struct InterpreterData0_2_0 {
pub trace: ExecutionTrace,
pub streams: StreamGenerations,
pub streams: GlobalStreamGens,
pub version: semver::Version,
}
// test 0.2.0 to 0.2.1 conversion
// test 0.2.0 to 0.2.2 conversion
let data_0_2_0 = InterpreterData0_2_0 {
trace: ExecutionTrace::default(),
streams: StreamGenerations::default(),
streams: GlobalStreamGens::default(),
version: semver::Version::new(0, 2, 0),
};
@ -113,10 +124,41 @@ mod tests {
let data_0_2_1 = serde_json::from_slice::<InterpreterData>(&data_0_2_0_se);
assert!(data_0_2_1.is_ok());
// test 0.2.1 to 0.2.1 conversion
let data_0_2_1 = InterpreterData::default();
// test 0.2.2 to 0.2.0 conversion
let data_0_2_2 = InterpreterData::default();
let data_0_2_2_se = serde_json::to_vec(&data_0_2_2).unwrap();
let data_0_2_0 = serde_json::from_slice::<InterpreterData0_2_0>(&data_0_2_2_se);
assert!(data_0_2_0.is_ok());
}
#[test]
fn compatible_with_0_2_1_version() {
#[derive(Serialize, Deserialize)]
struct InterpreterData0_2_1 {
pub trace: ExecutionTrace,
pub streams: GlobalStreamGens,
pub version: semver::Version,
#[serde(default)]
#[serde(rename = "lcid")]
pub last_call_request_id: u32,
}
// test 0.2.1 to 0.2.2 conversion
let data_0_2_1 = InterpreterData0_2_1 {
trace: ExecutionTrace::default(),
streams: GlobalStreamGens::default(),
version: semver::Version::new(0, 2, 1),
last_call_request_id: 1,
};
let data_0_2_1_se = serde_json::to_vec(&data_0_2_1).unwrap();
let data_0_2_0 = serde_json::from_slice::<InterpreterData0_2_0>(&data_0_2_1_se);
let data_0_2_2 = serde_json::from_slice::<InterpreterData>(&data_0_2_1_se);
assert!(data_0_2_2.is_ok());
// test 0.2.2 to 0.2.1 conversion
let data_0_2_2 = InterpreterData::default();
let data_0_2_2_se = serde_json::to_vec(&data_0_2_2).unwrap();
let data_0_2_0 = serde_json::from_slice::<InterpreterData0_2_1>(&data_0_2_2_se);
assert!(data_0_2_0.is_ok());
}
}

View File

@ -14,15 +14,28 @@
* limitations under the License.
*/
#![warn(rust_2018_idioms)]
#![deny(
dead_code,
nonstandard_style,
unused_imports,
unused_mut,
unused_variables,
unused_unsafe,
unreachable_patterns
)]
mod executed_state;
mod interpreter_data;
mod stream_generations;
pub use executed_state::*;
pub use interpreter_data::*;
pub use stream_generations::*;
use once_cell::sync::Lazy;
use std::str::FromStr;
pub static DATA_FORMAT_VERSION: Lazy<semver::Version> = Lazy::new(|| {
semver::Version::from_str("0.2.1").expect("invalid data format version specified")
semver::Version::from_str("0.2.2").expect("invalid data format version specified")
});

View File

@ -0,0 +1,31 @@
/*
* Copyright 2021 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 std::collections::HashMap;
/// Mapping from a stream name to it's generation count.
/// Similar to pi-calculus non-restricted names/channels.
pub type GlobalStreamGens = HashMap<String, u32>;
/// Mapping from a stream name to
/// position of a new instruction in a script that creates a scope for a stream
/// to vector where each position represents a corresponding iteration.
///
/// Vec<u32> is needed because a new instruction could be placed into a fold instruction,
/// so it could be met several times during script execution. This field anchors iteration
/// where it was met.
/// Similar to pi-calculus restricted names/channels.
pub type RestrictedStreamGens = HashMap<String, HashMap<u32, Vec<u32>>>;

View File

@ -33,7 +33,14 @@ pub fn scalar(result: JValue) -> ExecutedState {
ExecutedState::Call(CallResult::Executed(value))
}
pub fn stream_jvalue(result: JValue, generation: u32) -> ExecutedState {
pub fn scalar_number(result: impl Into<serde_json::Number>) -> ExecutedState {
let result = JValue::Number(result.into());
let value = Rc::new(result);
ExecutedState::Call(CallResult::executed_scalar(value))
}
pub fn stream(result: JValue, generation: u32) -> ExecutedState {
let call_result = CallResult::executed_stream(Rc::new(result), generation);
ExecutedState::Call(call_result)
}

View File

@ -82,7 +82,7 @@ pub fn data_from_result(result: &RawAVMOutcome) -> InterpreterData {
}
pub fn raw_data_from_trace(trace: ExecutionTrace) -> Vec<u8> {
let data = InterpreterData::from_execution_result(trace, <_>::default(), 0);
let data = InterpreterData::from_execution_result(trace, <_>::default(), <_>::default(), 0);
serde_json::to_vec(&data).expect("default serializer shouldn't fail")
}

View File

@ -19,8 +19,8 @@ use super::KeeperError;
use super::KeeperResult;
use super::TraceSlider;
use air_interpreter_data::GlobalStreamGens;
use air_interpreter_data::InterpreterData;
use air_interpreter_data::StreamGenerations;
use std::collections::HashMap;
@ -28,7 +28,7 @@ use std::collections::HashMap;
#[derive(Debug, Default, PartialEq)]
pub struct MergeCtx {
pub slider: TraceSlider,
pub streams: StreamGenerations,
pub streams: GlobalStreamGens,
}
impl MergeCtx {
@ -47,7 +47,7 @@ impl MergeCtx {
Self {
slider,
streams: data.streams,
streams: data.global_streams,
}
}