Refactor AIR parser (#168)

This commit is contained in:
Mike Voronov
2021-11-12 14:12:50 +03:00
committed by GitHub
parent a19eba6bec
commit 9f47eb9b83
62 changed files with 4077 additions and 3922 deletions

1
Cargo.lock generated
View File

@ -114,6 +114,7 @@ version = "0.1.0"
name = "air-parser"
version = "0.7.1"
dependencies = [
"air-lambda-ast",
"air-lambda-parser",
"codespan",
"codespan-reporting",

View File

@ -30,10 +30,8 @@ use crate::SecurityTetraplet;
use apply_to_arguments::*;
use utils::*;
use air_parser::ast::ApArgument;
use air_parser::ast::AstVariable;
use air_parser::ast::VariableWithLambda;
use air_parser::ast::{Ap, LastErrorPath};
use air_parser::ast;
use air_parser::ast::Ap;
use air_trace_handler::MergerApResult;
use std::cell::RefCell;
@ -45,7 +43,7 @@ impl<'i> super::ExecutableInstruction<'i> for Ap<'i> {
let merger_ap_result = if should_touch_trace {
let merger_ap_result = trace_to_exec_err!(trace_ctx.meet_ap_start())?;
try_match_result_to_instr(&merger_ap_result, self)?;
try_match_trace_to_instr(&merger_ap_result, self)?;
merger_ap_result
} else {
MergerApResult::Empty
@ -66,27 +64,24 @@ impl<'i> super::ExecutableInstruction<'i> for Ap<'i> {
}
fn save_result<'ctx>(
ap_result_type: &AstVariable<'ctx>,
ap_result_type: &ast::Variable<'ctx>,
merger_ap_result: &MergerApResult,
result: ValueAggregate,
exec_ctx: &mut ExecutionCtx<'ctx>,
) -> ExecutionResult<()> {
use ast::Variable::*;
match ap_result_type {
AstVariable::Scalar(name) => exec_ctx.scalars.set_value(*name, result).map(|_| ()),
AstVariable::Stream(name) => {
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, name.to_string(), exec_ctx).map(|_| ())
set_stream_result(result, generation, stream.name.to_string(), exec_ctx).map(|_| ())
}
}
}
/// This function is intended to check whether a Ap instruction should produce
/// a new state in data.
fn should_touch_trace(ap: &Ap<'_>) -> bool {
match (&ap.argument, &ap.result) {
(_, AstVariable::Stream(_)) => true,
(ApArgument::VariableWithLambda(vl), _) => match &vl.variable {
AstVariable::Scalar(_) => false,
AstVariable::Stream(_) => true,
},
_ => false,
}
matches!(ap.result, ast::Variable::Stream(_))
}

View File

@ -15,27 +15,66 @@
*/
use super::*;
use air_lambda_parser::LambdaAST;
use air_parser::ast;
pub(super) fn apply_to_arg(
argument: &ApArgument<'_>,
argument: &ast::ApArgument<'_>,
exec_ctx: &ExecutionCtx<'_>,
trace_ctx: &TraceHandler,
should_touch_trace: bool,
) -> ExecutionResult<ValueAggregate> {
use ast::ApArgument::*;
let result = match argument {
ApArgument::ScalarVariable(scalar_name) => apply_scalar(scalar_name, exec_ctx, trace_ctx, should_touch_trace)?,
ApArgument::VariableWithLambda(vl) => apply_json_argument(vl, exec_ctx, trace_ctx)?,
ApArgument::LastError(error_path) => apply_last_error(error_path, exec_ctx, trace_ctx)?,
ApArgument::Literal(value) => apply_const(value.to_string(), exec_ctx, trace_ctx),
ApArgument::Number(value) => apply_const(value, exec_ctx, trace_ctx),
ApArgument::Boolean(value) => apply_const(*value, exec_ctx, trace_ctx),
ApArgument::EmptyArray => apply_const(serde_json::json!([]), exec_ctx, trace_ctx),
InitPeerId => apply_const(exec_ctx.init_peer_id.clone(), exec_ctx, trace_ctx),
LastError(error_path) => apply_last_error(error_path, exec_ctx, trace_ctx)?,
Literal(value) => apply_const(*value, exec_ctx, trace_ctx),
Number(value) => apply_const(value, exec_ctx, trace_ctx),
Boolean(value) => apply_const(*value, exec_ctx, trace_ctx),
EmptyArray => apply_const(serde_json::json!([]), exec_ctx, trace_ctx),
Scalar(scalar) => apply_scalar(scalar, exec_ctx, trace_ctx, should_touch_trace)?,
};
Ok(result)
}
fn apply_const(value: impl Into<JValue>, exec_ctx: &ExecutionCtx<'_>, trace_ctx: &TraceHandler) -> ValueAggregate {
let value = Rc::new(value.into());
let tetraplet = SecurityTetraplet::literal_tetraplet(exec_ctx.init_peer_id.clone());
let tetraplet = Rc::new(RefCell::new(tetraplet));
ValueAggregate::new(value, tetraplet, trace_ctx.trace_pos())
}
fn apply_last_error(
error_path: &ast::LastErrorPath,
exec_ctx: &ExecutionCtx<'_>,
trace_ctx: &TraceHandler,
) -> ExecutionResult<ValueAggregate> {
let (value, mut tetraplets) = crate::execution_step::utils::prepare_last_error(error_path, exec_ctx)?;
let value = Rc::new(value);
// removing is safe because prepare_last_error always returns a vec with one element.
let tetraplet = tetraplets.remove(0);
let result = ValueAggregate::new(value, tetraplet, trace_ctx.trace_pos());
Ok(result)
}
fn apply_scalar(
scalar: &ast::ScalarWithLambda<'_>,
exec_ctx: &ExecutionCtx<'_>,
trace_ctx: &TraceHandler,
should_touch_trace: bool,
) -> 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),
None => apply_scalar_impl(scalar.name, exec_ctx, trace_ctx, should_touch_trace),
}
}
fn apply_scalar_impl(
scalar_name: &str,
exec_ctx: &ExecutionCtx<'_>,
trace_ctx: &TraceHandler,
@ -63,34 +102,14 @@ fn apply_scalar(
Ok(result)
}
fn apply_const(value: impl Into<JValue>, exec_ctx: &ExecutionCtx<'_>, trace_ctx: &TraceHandler) -> ValueAggregate {
let value = Rc::new(value.into());
let tetraplet = SecurityTetraplet::literal_tetraplet(exec_ctx.init_peer_id.clone());
let tetraplet = Rc::new(RefCell::new(tetraplet));
ValueAggregate::new(value, tetraplet, trace_ctx.trace_pos())
}
fn apply_last_error(
error_path: &LastErrorPath,
fn apply_scalar_wl_impl(
scalar_name: &str,
lambda: &LambdaAST<'_>,
exec_ctx: &ExecutionCtx<'_>,
trace_ctx: &TraceHandler,
) -> ExecutionResult<ValueAggregate> {
let (value, mut tetraplets) = crate::execution_step::utils::prepare_last_error(error_path, exec_ctx)?;
let value = Rc::new(value);
let tetraplet = tetraplets.remove(0);
let result = ValueAggregate::new(value, tetraplet, trace_ctx.trace_pos());
Ok(result)
}
fn apply_json_argument(
vl: &VariableWithLambda<'_>,
exec_ctx: &ExecutionCtx<'_>,
trace_ctx: &TraceHandler,
) -> ExecutionResult<ValueAggregate> {
let variable = Variable::from_ast(&vl.variable);
let (jvalue, mut tetraplets) = apply_lambda(variable, &vl.lambda, exec_ctx)?;
let variable = Variable::scalar(scalar_name);
let (jvalue, mut tetraplets) = apply_lambda(variable, lambda, exec_ctx)?;
let tetraplet = tetraplets
.pop()

View File

@ -19,8 +19,8 @@ use super::ExecutionResult;
use crate::execution_step::Generation;
use air_interpreter_data::ApResult;
use air_parser::ast;
use air_parser::ast::Ap;
use air_parser::ast::AstVariable;
use air_trace_handler::MergerApResult;
pub(super) fn ap_result_to_generation(ap_result: &MergerApResult) -> Generation {
@ -30,7 +30,7 @@ pub(super) fn ap_result_to_generation(ap_result: &MergerApResult) -> Generation
}
}
pub(super) fn try_match_result_to_instr(merger_ap_result: &MergerApResult, instr: &Ap<'_>) -> ExecutionResult<()> {
pub(super) fn try_match_trace_to_instr(merger_ap_result: &MergerApResult, instr: &Ap<'_>) -> ExecutionResult<()> {
let res_generation = match merger_ap_result {
MergerApResult::ApResult { res_generation } => *res_generation,
MergerApResult::Empty => return Ok(()),
@ -40,15 +40,16 @@ pub(super) fn try_match_result_to_instr(merger_ap_result: &MergerApResult, instr
}
fn match_position_variable(
variable: &AstVariable<'_>,
variable: &ast::Variable<'_>,
generation: Option<u32>,
ap_result: &MergerApResult,
) -> ExecutionResult<()> {
use crate::execution_step::ExecutionError::ApResultNotCorrespondToInstr;
use ast::Variable::*;
match (variable, generation) {
(AstVariable::Stream(_), Some(_)) => Ok(()),
(AstVariable::Scalar(_), None) => Ok(()),
(Stream(_), Some(_)) => Ok(()),
(Scalar(_), None) => Ok(()),
_ => return crate::exec_err!(ApResultNotCorrespondToInstr(ap_result.clone())),
}
}
@ -71,13 +72,15 @@ fn option_to_vec(value: Option<u32>) -> Vec<u32> {
}
}
fn variable_to_generations(variable: &AstVariable<'_>, exec_ctx: &ExecutionCtx<'_>) -> Vec<u32> {
fn variable_to_generations(variable: &ast::Variable<'_>, exec_ctx: &ExecutionCtx<'_>) -> Vec<u32> {
use ast::Variable::*;
match variable {
AstVariable::Scalar(_) => vec![],
AstVariable::Stream(name) => {
Scalar(_) => vec![],
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(*name).unwrap();
let stream = exec_ctx.streams.get(stream.name).unwrap();
let generation = match stream.borrow().generations_count() {
0 => 0,
n => n - 1,

View File

@ -16,7 +16,6 @@
use super::*;
use crate::execution_step::execution_context::*;
use crate::execution_step::AstVariable;
use crate::execution_step::Generation;
use crate::execution_step::Stream;
use crate::execution_step::ValueAggregate;
@ -24,6 +23,7 @@ use crate::execution_step::ValueAggregate;
use air_interpreter_data::CallResult;
use air_interpreter_data::Value;
use air_parser::ast::CallOutputValue;
use air_parser::ast::Variable;
use air_trace_handler::TraceHandler;
use std::cell::RefCell;
@ -38,18 +38,18 @@ pub(crate) fn set_local_result<'i>(
) -> ExecutionResult<CallResult> {
let result_value = executed_result.result.clone();
match output {
CallOutputValue::Variable(AstVariable::Scalar(name)) => {
exec_ctx.scalars.set_value(*name, executed_result)?;
CallOutputValue::Variable(Variable::Scalar(scalar)) => {
exec_ctx.scalars.set_value(scalar.name, executed_result)?;
Ok(CallResult::executed_scalar(result_value))
}
CallOutputValue::Variable(AstVariable::Stream(name)) => {
CallOutputValue::Variable(Variable::Stream(stream)) => {
// TODO: refactor this generation handling
let generation = match exec_ctx.streams.get(*name) {
let generation = match exec_ctx.streams.get(stream.name) {
Some(stream) => Generation::Nth(stream.borrow().generations_count() as u32 - 1),
None => Generation::Last,
};
let generation = set_stream_result(executed_result, generation, name.to_string(), exec_ctx)?;
let generation = set_stream_result(executed_result, generation, stream.name.to_string(), exec_ctx)?;
Ok(CallResult::executed_stream(result_value, generation))
}
CallOutputValue::None => Ok(CallResult::executed_scalar(result_value)),
@ -64,14 +64,14 @@ pub(crate) fn set_result_from_value<'i>(
exec_ctx: &mut ExecutionCtx<'i>,
) -> ExecutionResult<()> {
match (output, value) {
(CallOutputValue::Variable(AstVariable::Scalar(name)), Value::Scalar(value)) => {
(CallOutputValue::Variable(Variable::Scalar(scalar)), Value::Scalar(value)) => {
let result = ValueAggregate::new(value, tetraplet, trace_pos);
exec_ctx.scalars.set_value(*name, result)?;
exec_ctx.scalars.set_value(scalar.name, result)?;
}
(CallOutputValue::Variable(AstVariable::Stream(name)), Value::Stream { value, generation }) => {
(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, name.to_string(), exec_ctx)?;
let _ = set_stream_result(result, generation, stream.name.to_string(), exec_ctx)?;
}
// it isn't needed to check there that output and value matches because
// it's been already checked in trace handler

View File

@ -18,7 +18,7 @@
use super::call_result_setter::*;
use super::prev_result_handler::*;
use super::triplet::Triplet;
use super::triplet::resolve;
use super::*;
use crate::execution_step::RSecurityTetraplet;
use crate::execution_step::SecurityTetraplets;
@ -28,7 +28,7 @@ use crate::SecurityTetraplet;
use air_interpreter_data::CallResult;
use air_interpreter_interface::CallRequestParams;
use air_parser::ast::{AstVariable, CallInstrArgValue, CallOutputValue};
use air_parser::ast;
use air_trace_handler::MergerCallResult;
use air_trace_handler::TraceHandler;
@ -39,8 +39,8 @@ use std::rc::Rc;
#[derive(Debug, Clone, PartialEq)]
pub(super) struct ResolvedCall<'i> {
tetraplet: RSecurityTetraplet,
function_arg_paths: Rc<Vec<CallInstrArgValue<'i>>>,
output: CallOutputValue<'i>,
function_arg_paths: Rc<Vec<ast::Value<'i>>>,
output: ast::CallOutputValue<'i>,
}
#[derive(Debug, Clone, PartialEq)]
@ -52,8 +52,7 @@ struct ResolvedArguments {
impl<'i> ResolvedCall<'i> {
/// Build `ResolvedCall` from `Call` by transforming `PeerPart` & `FunctionPart` into `ResolvedTriplet`.
pub(super) fn new(raw_call: &Call<'i>, exec_ctx: &ExecutionCtx<'i>) -> ExecutionResult<Self> {
let triplet = Triplet::try_from(&raw_call.peer_part, &raw_call.function_part)?;
let triplet = triplet.resolve(exec_ctx)?;
let triplet = resolve(&raw_call.triplet, exec_ctx)?;
let tetraplet = SecurityTetraplet::from_triplet(triplet);
let tetraplet = Rc::new(RefCell::new(tetraplet));
@ -167,11 +166,11 @@ impl<'i> ResolvedCall<'i> {
/// Check output type name for being already in execution context.
// TODO: this check should be moved on a parsing stage
fn check_output_name(output: &CallOutputValue<'_>, exec_ctx: &ExecutionCtx<'_>) -> ExecutionResult<()> {
fn check_output_name(output: &ast::CallOutputValue<'_>, exec_ctx: &ExecutionCtx<'_>) -> ExecutionResult<()> {
use crate::execution_step::boxed_value::ScalarRef;
let scalar_name = match output {
CallOutputValue::Variable(AstVariable::Scalar(name)) => *name,
ast::CallOutputValue::Variable(ast::Variable::Scalar(scalar)) => scalar.name,
_ => return Ok(()),
};

View File

@ -20,77 +20,40 @@ use super::ExecutionResult;
use crate::exec_err;
use crate::JValue;
use air_parser::ast::{AstVariable, CallInstrValue, FunctionPart, PeerPart};
use air_parser::ast::CallInstrValue;
use air_parser::ast::Triplet;
use polyplets::ResolvedTriplet;
/// Triplet represents a location of the executable code in the network.
/// It is build from `PeerPart` and `FunctionPart` of a `Call` instruction.
pub(super) struct Triplet<'a, 'i> {
pub(super) peer_pk: &'a CallInstrValue<'i>,
pub(super) service_id: &'a CallInstrValue<'i>,
pub(super) function_name: &'a CallInstrValue<'i>,
}
/// Resolve variables, literals, etc in the `Triplet`, and build a `ResolvedTriplet`.
pub(crate) fn resolve<'i>(triplet: &Triplet<'i>, ctx: &ExecutionCtx<'i>) -> ExecutionResult<ResolvedTriplet> {
let Triplet {
peer_pk,
service_id,
function_name,
} = triplet;
impl<'a, 'i> Triplet<'a, 'i> {
/// Build a `Triplet` from `Call`'s `PeerPart` and `FunctionPart`
pub fn try_from(peer: &'a PeerPart<'i>, f: &'a FunctionPart<'i>) -> ExecutionResult<Self> {
use air_parser::ast::FunctionPart::*;
use air_parser::ast::PeerPart::*;
let peer_pk = resolve_to_string(peer_pk, ctx)?;
let service_id = resolve_to_string(service_id, ctx)?;
let function_name = resolve_to_string(function_name, ctx)?;
let (peer_pk, service_id, function_name) = match (peer, f) {
(PeerPkWithServiceId(peer_pk, _peer_service_id), ServiceIdWithFuncName(service_id, func_name)) => {
Ok((peer_pk, service_id, func_name))
}
(PeerPkWithServiceId(peer_pk, peer_service_id), FuncName(func_name)) => {
Ok((peer_pk, peer_service_id, func_name))
}
(PeerPk(peer_pk), ServiceIdWithFuncName(service_id, func_name)) => Ok((peer_pk, service_id, func_name)),
(PeerPk(_), FuncName(_)) => exec_err!(ExecutionError::IncorrectCallTriplet),
}?;
Ok(Self {
peer_pk,
service_id,
function_name,
})
}
/// Resolve variables, literals, etc in the `Triplet`, and build a `ResolvedTriplet`.
pub fn resolve(self, ctx: &ExecutionCtx<'i>) -> ExecutionResult<ResolvedTriplet> {
let Triplet {
peer_pk,
service_id,
function_name,
} = self;
let peer_pk = resolve_to_string(peer_pk, ctx)?;
let service_id = resolve_to_string(service_id, ctx)?;
let function_name = resolve_to_string(function_name, ctx)?;
Ok(ResolvedTriplet {
peer_pk,
service_id,
function_name,
})
}
Ok(ResolvedTriplet {
peer_pk,
service_id,
function_name,
})
}
/// Resolve value to string by either resolving variable from `ExecutionCtx`, taking literal value, or etc.
// TODO: return Rc<String> to avoid excess cloning
fn resolve_to_string<'i>(value: &CallInstrValue<'i>, ctx: &ExecutionCtx<'i>) -> ExecutionResult<String> {
use crate::execution_step::utils::resolve_ast_variable;
use crate::execution_step::utils::resolve_ast_variable_wl;
let resolved = match value {
CallInstrValue::InitPeerId => ctx.init_peer_id.clone(),
CallInstrValue::Literal(value) => value.to_string(),
CallInstrValue::Variable(variable) => {
let resolved = resolve_ast_variable(variable, ctx)?;
let jvalue = resolved.into_jvalue();
jvalue_to_string(jvalue)?
}
CallInstrValue::VariableWithLambda(vl) => {
let resolved = resolve_ast_variable(&vl.variable, ctx)?;
let resolved = resolved.apply_lambda(&vl.lambda)?;
vec_to_string(resolved, &vl.variable)?
let (resolved, _) = resolve_ast_variable_wl(variable, ctx)?;
jvalue_to_string(resolved)?
}
};
@ -105,15 +68,3 @@ fn jvalue_to_string(jvalue: JValue) -> ExecutionResult<String> {
_ => exec_err!(IncompatibleJValueType(jvalue, "string")),
}
}
fn vec_to_string(values: Vec<&JValue>, variable: &AstVariable<'_>) -> ExecutionResult<String> {
if values.is_empty() {
let variable_name = match variable {
AstVariable::Stream(name) => name,
AstVariable::Scalar(name) => name,
};
return exec_err!(ExecutionError::VariableNotFound(variable_name.to_string()));
}
jvalue_to_string(values[0].clone())
}

View File

@ -16,57 +16,50 @@
use crate::execution_step::air::ExecutionResult;
use crate::execution_step::execution_context::ExecutionCtx;
use crate::execution_step::utils::resolve_ast_variable;
use crate::execution_step::utils::prepare_last_error;
use crate::execution_step::utils::resolve_ast_variable_wl;
use crate::JValue;
use air_parser::ast;
use air_parser::ast::MatchableValue;
pub(crate) fn are_matchable_eq<'ctx>(
left: &MatchableValue<'_>,
right: &MatchableValue<'_>,
left: &ast::Value<'_>,
right: &ast::Value<'_>,
exec_ctx: &'ctx ExecutionCtx<'_>,
) -> ExecutionResult<bool> {
use MatchableValue::*;
use ast::Value::*;
match (left, right) {
(InitPeerId, InitPeerId) => Ok(true),
(InitPeerId, matchable) => compare_matchable(
matchable,
exec_ctx,
make_string_comparator(exec_ctx.init_peer_id.as_str()),
),
(matchable, InitPeerId) => compare_matchable(
(InitPeerId, matchable) | (matchable, InitPeerId) => compare_matchable(
matchable,
exec_ctx,
make_string_comparator(exec_ctx.init_peer_id.as_str()),
),
(LastError(path), matchable) | (matchable, LastError(path)) => {
let (value, _) = prepare_last_error(path, exec_ctx)?;
compare_matchable(matchable, exec_ctx, make_object_comparator(value))
}
(Literal(left_name), Literal(right_name)) => Ok(left_name == right_name),
(Literal(value), matchable) => compare_matchable(matchable, exec_ctx, make_string_comparator(value)),
(matchable, Literal(value)) => compare_matchable(matchable, exec_ctx, make_string_comparator(value)),
(Literal(value), matchable) | (matchable, Literal(value)) => {
compare_matchable(matchable, exec_ctx, make_string_comparator(value))
}
(Boolean(value), matchable) => compare_matchable(matchable, exec_ctx, make_bool_comparator(value)),
(matchable, Boolean(value)) => compare_matchable(matchable, exec_ctx, make_bool_comparator(value)),
(Boolean(left_boolean), Boolean(right_boolean)) => Ok(left_boolean == right_boolean),
(Boolean(value), matchable) | (matchable, Boolean(value)) => {
compare_matchable(matchable, exec_ctx, make_object_comparator((*value).into()))
}
(Number(value), matchable) => compare_matchable(matchable, exec_ctx, make_number_comparator(value)),
(matchable, Number(value)) => compare_matchable(matchable, exec_ctx, make_number_comparator(value)),
(Number(left_number), Number(right_number)) => Ok(left_number == right_number),
(Number(value), matchable) | (matchable, Number(value)) => {
compare_matchable(matchable, exec_ctx, make_object_comparator(value.into()))
}
(Variable(left_variable), Variable(right_variable)) => {
let left_jvaluable = resolve_ast_variable(left_variable, exec_ctx)?;
let left_value = left_jvaluable.as_jvalue();
let right_jvaluable = resolve_ast_variable(right_variable, exec_ctx)?;
let right_value = right_jvaluable.as_jvalue();
Ok(left_value == right_value)
}
(VariableWithLambda(lhs), VariableWithLambda(rhs)) => {
let left_jvaluable = resolve_ast_variable(&lhs.variable, exec_ctx)?;
let left_value = left_jvaluable.apply_lambda(&lhs.lambda)?;
let right_jvaluable = resolve_ast_variable(&rhs.variable, exec_ctx)?;
let right_value = right_jvaluable.apply_lambda(&rhs.lambda)?;
let (left_value, _) = resolve_ast_variable_wl(left_variable, exec_ctx)?;
let (right_value, _) = resolve_ast_variable_wl(right_variable, exec_ctx)?;
Ok(left_value == right_value)
}
@ -78,11 +71,11 @@ use std::borrow::Cow;
type Comparator<'a> = Box<dyn Fn(Cow<'_, JValue>) -> bool + 'a>;
fn compare_matchable<'ctx>(
matchable: &MatchableValue<'_>,
matchable: &ast::Value<'_>,
exec_ctx: &'ctx ExecutionCtx<'_>,
comparator: Comparator<'ctx>,
) -> ExecutionResult<bool> {
use MatchableValue::*;
use ast::Value::*;
match matchable {
InitPeerId => {
@ -90,6 +83,10 @@ fn compare_matchable<'ctx>(
let jvalue = init_peer_id.into();
Ok(comparator(Cow::Owned(jvalue)))
}
LastError(error_path) => {
let (jvalue, _) = prepare_last_error(error_path, exec_ctx)?;
Ok(comparator(Cow::Owned(jvalue)))
}
Literal(str) => {
let jvalue = str.to_string().into();
Ok(comparator(Cow::Owned(jvalue)))
@ -107,22 +104,8 @@ fn compare_matchable<'ctx>(
Ok(comparator(Cow::Owned(jvalue)))
}
Variable(variable) => {
let jvaluable = resolve_ast_variable(variable, exec_ctx)?;
let jvalue = jvaluable.as_jvalue();
Ok(comparator(jvalue))
}
VariableWithLambda(vl) => {
let jvaluable = resolve_ast_variable(&vl.variable, exec_ctx)?;
let jvalues = jvaluable.apply_lambda(&vl.lambda)?;
// TODO: it's known that apply_lambda always returns array with one value that is
// intended to support multi-return in the future, this check is needed just in
// case and should be refactored after introducing boxed values
if jvalues.len() != 1 {
return Ok(false);
}
Ok(comparator(Cow::Borrowed(jvalues[0])))
let (jvalue, _) = resolve_ast_variable_wl(variable, exec_ctx)?;
Ok(comparator(Cow::Owned(jvalue)))
}
}
}
@ -138,21 +121,8 @@ fn make_string_comparator(comparable_string: &str) -> Comparator<'_> {
})
}
fn make_bool_comparator(comparable_bool: &bool) -> Comparator<'_> {
fn make_object_comparator(comparable_value: JValue) -> Comparator<'static> {
use std::ops::Deref;
let comparable_bool = *comparable_bool;
Box::new(move |jvalue: Cow<'_, JValue>| -> bool {
match jvalue.deref() {
JValue::Bool(jvalue) => jvalue == &comparable_bool,
_ => false,
}
})
}
fn make_number_comparator(comparable_number: &ast::Number) -> Comparator<'_> {
use std::ops::Deref;
let comparable_jvalue: JValue = comparable_number.into();
Box::new(move |jvalue: Cow<'_, JValue>| -> bool { jvalue.deref() == &comparable_jvalue })
Box::new(move |jvalue: Cow<'_, JValue>| -> bool { jvalue.deref() == &comparable_value })
}

View File

@ -40,14 +40,12 @@ pub(crate) enum FoldIterableStream {
/// Constructs iterable value for given scalar iterable.
pub(crate) fn construct_scalar_iterable_value<'ctx>(
ast_iterable: &ast::IterableScalarValue<'ctx>,
iterable: &ast::ScalarWithLambda<'ctx>,
exec_ctx: &ExecutionCtx<'ctx>,
) -> ExecutionResult<FoldIterableScalar> {
match ast_iterable {
ast::IterableScalarValue::ScalarVariable(scalar_name) => create_scalar_iterable(exec_ctx, scalar_name),
ast::IterableScalarValue::VariableWithLambda { scalar_name, lambda } => {
create_scalar_lambda_iterable(exec_ctx, scalar_name, lambda)
}
match &iterable.lambda {
None => create_scalar_iterable(exec_ctx, iterable.name),
Some(lambda) => create_scalar_lambda_iterable(exec_ctx, iterable.name, lambda),
}
}

View File

@ -36,7 +36,7 @@ impl<'i> ExecutableInstruction<'i> for FoldScalar<'i> {
FoldIterableScalar::Scalar(iterable) => fold(
iterable,
IterableType::Scalar,
self.iterator,
self.iterator.name,
self.instruction.clone(),
exec_ctx,
trace_ctx,

View File

@ -29,7 +29,7 @@ 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.stream_name, exec_ctx)? {
let iterables = match construct_stream_iterable_value(self.iterable.name, exec_ctx)? {
FoldIterableStream::Empty => return Ok(()),
FoldIterableStream::Stream(iterables) => iterables,
};
@ -51,7 +51,7 @@ impl<'i> ExecutableInstruction<'i> for FoldStream<'i> {
fold(
iterable,
IterableType::Stream(fold_id),
self.iterator,
self.iterator.name,
self.instruction.clone(),
exec_ctx,
trace_ctx,

View File

@ -28,7 +28,7 @@ impl<'i> super::ExecutableInstruction<'i> for Next<'i> {
fn execute(&self, exec_ctx: &mut ExecutionCtx<'i>, trace_ctx: &mut TraceHandler) -> ExecutionResult<()> {
log_instruction!(next, exec_ctx, trace_ctx);
let iterator_name = self.0;
let iterator_name = &self.iterator.name;
let fold_state = exec_ctx.scalars.get_iterable_mut(iterator_name)?;
maybe_meet_iteration_end(fold_state, trace_ctx)?;

View File

@ -15,7 +15,7 @@
*/
use super::Generation;
use air_parser::ast::AstVariable;
use air_parser::ast;
#[derive(Clone, Copy, Debug)]
pub(crate) enum Variable<'i> {
@ -24,21 +24,20 @@ pub(crate) enum Variable<'i> {
}
impl<'i> Variable<'i> {
pub(crate) fn from_ast(ast_variable: &AstVariable<'i>) -> Self {
match ast_variable {
AstVariable::Scalar(name) => Variable::Scalar(name),
AstVariable::Stream(name) => Variable::Stream {
name,
generation: Generation::Last,
},
}
pub(crate) fn scalar(name: &'i str) -> Self {
Self::Scalar(name)
}
#[allow(dead_code)]
pub(crate) fn from_ast_with_generation(ast_variable: &AstVariable<'i>, generation: Generation) -> Self {
pub(crate) fn from_ast_with_generation(ast_variable: &ast::Variable<'i>, generation: Generation) -> Self {
use ast::Variable::*;
match ast_variable {
AstVariable::Scalar(name) => Variable::Scalar(name),
AstVariable::Stream(name) => Variable::Stream { name, generation },
Scalar(scalar) => Variable::Scalar(scalar.name),
Stream(stream) => Variable::Stream {
name: stream.name,
generation,
},
}
}
@ -47,3 +46,31 @@ impl<'i> Variable<'i> {
Self::Stream { name, generation }
}
}
impl<'i> From<&ast::Variable<'i>> for Variable<'i> {
fn from(ast_variable: &ast::Variable<'i>) -> Self {
use ast::Variable::*;
match ast_variable {
Scalar(scalar) => Self::Scalar(scalar.name),
Stream(stream) => Self::Stream {
name: stream.name,
generation: Generation::Last,
},
}
}
}
impl<'i> From<&ast::VariableWithLambda<'i>> for Variable<'i> {
fn from(ast_variable: &ast::VariableWithLambda<'i>) -> Self {
use ast::VariableWithLambda::*;
match ast_variable {
Scalar(scalar) => Self::Scalar(scalar.name),
Stream(stream) => Self::Stream {
name: stream.name,
generation: Generation::Last,
},
}
}
}

View File

@ -38,10 +38,6 @@ use std::rc::Rc;
#[derive(ThisError, EnumDiscriminants, Debug)]
#[strum_discriminants(derive(EnumIter))]
pub(crate) enum ExecutionError {
/// Semantic errors in a call instructions.
#[error("call should have service id specified by peer part or function part")]
IncorrectCallTriplet,
/// An error is occurred while calling local service via call_service.
#[error("Local service error, ret_code is {0}, error message is '{1}'")]
LocalServiceError(i32, Rc<String>),

View File

@ -41,8 +41,6 @@ type ExecutionResult<T> = std::result::Result<T, Rc<ExecutionError>>;
type RSecurityTetraplet = Rc<RefCell<crate::SecurityTetraplet>>;
type SecurityTetraplets = Vec<RSecurityTetraplet>;
use air_parser::ast::AstVariable;
#[macro_export]
macro_rules! exec_err {
($err:expr) => {

View File

@ -24,8 +24,7 @@ use crate::JValue;
use crate::LambdaAST;
use crate::SecurityTetraplet;
use air_parser::ast::AstVariable;
use air_parser::ast::CallInstrArgValue;
use air_parser::ast;
use air_parser::ast::LastErrorPath;
use serde_json::json;
@ -34,24 +33,19 @@ use std::rc::Rc;
/// Resolve value to called function arguments.
pub(crate) fn resolve_to_args<'i>(
value: &CallInstrArgValue<'i>,
value: &ast::Value<'i>,
ctx: &ExecutionCtx<'i>,
) -> ExecutionResult<(JValue, SecurityTetraplets)> {
use ast::Value::*;
match value {
CallInstrArgValue::InitPeerId => prepare_const(ctx.init_peer_id.clone(), ctx),
CallInstrArgValue::LastError(path) => prepare_last_error(path, ctx),
CallInstrArgValue::Literal(value) => prepare_const(value.to_string(), ctx),
CallInstrArgValue::Boolean(value) => prepare_const(*value, ctx),
CallInstrArgValue::Number(value) => prepare_const(value, ctx),
CallInstrArgValue::EmptyArray => prepare_const(json!([]), ctx),
CallInstrArgValue::Variable(variable) => {
let variable = Variable::from_ast(variable);
prepare_variable(variable, ctx)
}
CallInstrArgValue::VariableWithLambda(var_with_lambda) => {
let variable = Variable::from_ast(&var_with_lambda.variable);
apply_lambda(variable, &var_with_lambda.lambda, ctx)
}
InitPeerId => prepare_const(ctx.init_peer_id.clone(), ctx),
LastError(path) => prepare_last_error(path, ctx),
Literal(value) => prepare_const(value.to_string(), ctx),
Boolean(value) => prepare_const(*value, ctx),
Number(value) => prepare_const(value, ctx),
EmptyArray => prepare_const(json!([]), ctx),
Variable(variable) => resolve_ast_variable_wl(variable, ctx),
}
}
@ -86,17 +80,6 @@ pub(crate) fn prepare_last_error(
Ok((jvalue, vec![tetraplets]))
}
fn prepare_variable<'i>(
variable: Variable<'_>,
ctx: &ExecutionCtx<'i>,
) -> ExecutionResult<(JValue, SecurityTetraplets)> {
let resolved = resolve_variable(variable, ctx)?;
let tetraplets = resolved.as_tetraplets();
let jvalue = resolved.into_jvalue();
Ok((jvalue, tetraplets))
}
pub(crate) fn resolve_variable<'ctx, 'i>(
variable: Variable<'_>,
ctx: &'ctx ExecutionCtx<'i>,
@ -119,20 +102,27 @@ pub(crate) fn resolve_variable<'ctx, 'i>(
}
}
pub(crate) fn resolve_ast_variable<'ctx, 'i>(
variable: &AstVariable<'_>,
ctx: &'ctx ExecutionCtx<'i>,
) -> ExecutionResult<Box<dyn JValuable + 'ctx>> {
let variable = Variable::from_ast(variable);
resolve_variable(variable, ctx)
pub(crate) fn resolve_ast_variable_wl<'ctx, 'i>(
ast_variable: &ast::VariableWithLambda<'_>,
exec_ctx: &'ctx ExecutionCtx<'i>,
) -> ExecutionResult<(JValue, SecurityTetraplets)> {
let variable: Variable<'_> = ast_variable.into();
match ast_variable.lambda() {
Some(lambda) => apply_lambda(variable, lambda, exec_ctx),
None => {
let value = resolve_variable(variable, exec_ctx)?;
let tetraplets = value.as_tetraplets();
Ok((value.into_jvalue(), tetraplets))
}
}
}
pub(crate) fn apply_lambda<'i>(
variable: Variable<'_>,
lambda: &LambdaAST<'i>,
ctx: &ExecutionCtx<'i>,
exec_ctx: &ExecutionCtx<'i>,
) -> ExecutionResult<(JValue, SecurityTetraplets)> {
let resolved = resolve_variable(variable, ctx)?;
let resolved = resolve_variable(variable, exec_ctx)?;
let (jvalue, tetraplets) = resolved.apply_lambda_with_tetraplets(lambda)?;
// it's known that apply_lambda_with_tetraplets returns vec of one value

View File

@ -109,7 +109,7 @@ fn duplicate_variables() {
let result = call_vm!(vm, "asd", script, "", "");
assert_eq!(result.ret_code, 1003);
assert_eq!(result.ret_code, 1002);
assert!(result.next_peer_pks.is_empty());
}

View File

@ -157,7 +157,7 @@ fn inner_fold_with_same_iterator() {
let result = call_vm!(vm, "", script, "", "");
assert_eq!(result.ret_code, 1008);
assert_eq!(result.ret_code, 1007);
}
#[test]

View File

@ -192,11 +192,11 @@ fn match_without_xor() {
let result = call_vm!(set_variable_vm, "", &script, "", "");
let result = call_vm!(vm, "", &script, "", result.data);
assert_eq!(result.ret_code, 1011);
assert_eq!(result.ret_code, 1010);
let result = call_vm!(vm, "", script, "", result.data);
assert_eq!(result.ret_code, 1011);
assert_eq!(result.ret_code, 1010);
}
#[test]

View File

@ -143,11 +143,11 @@ fn mismatch_without_xor() {
let result = call_vm!(set_variable_vm, "asd", &script, "", "");
let result = call_vm!(vm, "asd", &script, "", result.data);
assert_eq!(result.ret_code, 1012);
assert_eq!(result.ret_code, 1011);
let result = call_vm!(vm, "asd", script, "", result.data);
assert_eq!(result.ret_code, 1012);
assert_eq!(result.ret_code, 1011);
}
#[test]

View File

@ -169,7 +169,7 @@ fn dont_wait_on_json_path_on_scalars() {
let init_peer_id = "asd";
let result = call_vm!(set_variable_vm, init_peer_id, &script, "", "");
let array_result = call_vm!(array_consumer, init_peer_id, &script, "", result.data.clone());
assert_eq!(array_result.ret_code, 1004);
assert_eq!(array_result.ret_code, 1003);
assert_eq!(
array_result.error_message,
r#"value '[1,2,3,4,5]' does not contain element for idx = '5'"#
@ -188,7 +188,7 @@ fn dont_wait_on_json_path_on_scalars() {
let init_peer_id = "asd";
let result = call_vm!(set_variable_vm, init_peer_id, &script, "", "");
let object_result = call_vm!(object_consumer, init_peer_id, script, "", result.data);
assert_eq!(object_result.ret_code, 1004);
assert_eq!(object_result.ret_code, 1003);
assert_eq!(
object_result.error_message,
r#"value '{"err_msg":"","is_authenticated":1,"ret_code":0}' does not contain element with field name = 'non_exist_path'"#

View File

@ -37,5 +37,5 @@ fn json_path_not_allowed_for_non_objects_and_arrays() {
let result = checked_call_vm!(set_variable_vm, "asd", &script, "", "");
let result = call_vm!(local_vm, "asd", script, "", result.data);
assert_eq!(result.ret_code, 1004);
assert_eq!(result.ret_code, 1003);
}

View File

@ -140,7 +140,7 @@ fn par_early_exit() {
];
let setter_3_malicious_data = raw_data_from_trace(setter_3_malicious_trace);
let init_result_3 = call_vm!(init, "", &script, init_result_2.data.clone(), setter_3_malicious_data);
assert_eq!(init_result_3.ret_code, 1013);
assert_eq!(init_result_3.ret_code, 1012);
let actual_trace = trace_from_result(&init_result_3);
let expected_trace = trace_from_result(&init_result_2);

View File

@ -13,6 +13,7 @@ categories = ["wasm"]
lalrpop = "0.19.6"
[dependencies]
air-lambda-ast = { path = "../lambda/ast" }
air-lambda-parser = { path = "../lambda/parser" }
lalrpop-util = "0.19.6"

View File

@ -0,0 +1,88 @@
/*
* 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.
*/
mod traits;
use super::Variable;
use super::VariableWithLambda;
use crate::ast::ScalarWithLambda;
use crate::parser::lexer::LastErrorPath;
use crate::parser::lexer::Number;
use serde::Deserialize;
use serde::Serialize;
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub enum CallInstrValue<'i> {
InitPeerId,
Literal(&'i str),
Variable(VariableWithLambda<'i>),
}
/// The peer part of a call instruction triplet
#[derive(Serialize, Debug, PartialEq)]
pub enum PeerPart<'i> {
PeerPk(CallInstrValue<'i>),
PeerPkWithServiceId(CallInstrValue<'i>, CallInstrValue<'i>),
}
/// The function part of a call instruction triplet
#[derive(Serialize, Debug, PartialEq)]
pub enum FunctionPart<'i> {
FuncName(CallInstrValue<'i>),
ServiceIdWithFuncName(CallInstrValue<'i>, CallInstrValue<'i>),
}
/// Triplet represents a location of the executable code in the network.
/// It is build from `PeerPart` and `FunctionPart` of a `Call` instruction.
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct Triplet<'i> {
#[serde(borrow)]
pub peer_pk: CallInstrValue<'i>,
#[serde(borrow)]
pub service_id: CallInstrValue<'i>,
#[serde(borrow)]
pub function_name: CallInstrValue<'i>,
}
/// Represents all values that is possible to set in AIR scripts.
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub enum Value<'i> {
InitPeerId,
LastError(LastErrorPath),
Literal(&'i str),
Number(Number),
Boolean(bool),
EmptyArray, // only empty arrays are allowed now
Variable(VariableWithLambda<'i>),
}
#[derive(Serialize, Debug, PartialEq, Clone)]
pub enum CallOutputValue<'i> {
Variable(Variable<'i>),
None,
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub enum ApArgument<'i> {
InitPeerId,
LastError(LastErrorPath),
Literal(&'i str),
Number(Number),
Boolean(bool),
EmptyArray,
Scalar(ScalarWithLambda<'i>),
}

View File

@ -0,0 +1,107 @@
/*
* 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::*;
use std::fmt;
impl fmt::Display for Value<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use Value::*;
match self {
InitPeerId => write!(f, "%init_peer_id%"),
LastError(error_path) => write!(f, "%last_error%{}", error_path),
Literal(literal) => write!(f, r#""{}""#, literal),
Number(number) => write!(f, "{}", number),
Boolean(bool) => write!(f, "{}", bool),
EmptyArray => write!(f, "[]"),
Variable(variable) => write!(f, "{}", variable),
}
}
}
impl fmt::Display for CallInstrValue<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use CallInstrValue::*;
match self {
InitPeerId => write!(f, "%init_peer_id%"),
Literal(literal) => write!(f, r#""{}""#, literal),
Variable(variable) => write!(f, "{}", variable),
}
}
}
impl fmt::Display for CallOutputValue<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use CallOutputValue::*;
match self {
Variable(variable) => write!(f, "{}", variable),
None => Ok(()),
}
}
}
impl fmt::Display for ApArgument<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use ApArgument::*;
match self {
InitPeerId => write!(f, "%init_peer_id%"),
LastError(last_error) => write!(f, "{}", last_error),
Literal(str) => write!(f, r#""{}""#, str),
Number(number) => write!(f, "{}", number),
Boolean(bool) => write!(f, "{}", bool),
EmptyArray => write!(f, "[]"),
Scalar(scalar) => write!(f, "{}", scalar),
}
}
}
impl fmt::Display for PeerPart<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use PeerPart::*;
match self {
PeerPk(peer_pk) => write!(f, "{}", peer_pk),
PeerPkWithServiceId(peer_pk, service_id) => write!(f, "({} {})", peer_pk, service_id),
}
}
}
impl fmt::Display for FunctionPart<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use FunctionPart::*;
match self {
FuncName(func_name) => write!(f, "{}", func_name),
ServiceIdWithFuncName(service_id, func_name) => {
write!(f, "({} {})", service_id, func_name)
}
}
}
}
impl fmt::Display for Triplet<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{} ({} {})",
self.peer_pk, self.service_id, self.function_name
)
}
}

View File

@ -0,0 +1,110 @@
/*
* 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.
*/
mod impls;
mod traits;
use super::*;
use serde::Serialize;
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>),
Par(Par<'i>),
Xor(Xor<'i>),
Match(Match<'i>),
MisMatch(MisMatch<'i>),
FoldScalar(FoldScalar<'i>),
FoldStream(FoldStream<'i>),
Next(Next<'i>),
Error,
}
/// (call (peer part of a triplet: PeerPart) (function part of a triplet: FunctionPart) [arguments] output)
#[derive(Serialize, Debug, PartialEq)]
pub struct Call<'i> {
pub triplet: Triplet<'i>,
pub args: Rc<Vec<Value<'i>>>,
pub output: CallOutputValue<'i>,
}
/// (ap argument result)
#[derive(Serialize, Debug, PartialEq)]
pub struct Ap<'i> {
pub argument: ApArgument<'i>,
pub result: Variable<'i>,
}
/// (seq instruction instruction)
#[derive(Serialize, Debug, PartialEq)]
pub struct Seq<'i>(pub Box<Instruction<'i>>, pub Box<Instruction<'i>>);
/// (par instruction instruction)
#[derive(Serialize, Debug, PartialEq)]
pub struct Par<'i>(pub Box<Instruction<'i>>, pub Box<Instruction<'i>>);
/// (xor instruction instruction)
#[derive(Serialize, Debug, PartialEq)]
pub struct Xor<'i>(pub Box<Instruction<'i>>, pub Box<Instruction<'i>>);
/// (match left_value right_value instruction)
#[derive(Serialize, Debug, PartialEq)]
pub struct Match<'i> {
pub left_value: Value<'i>,
pub right_value: Value<'i>,
pub instruction: Box<Instruction<'i>>,
}
/// (mismatch left_value right_value instruction)
#[derive(Serialize, Debug, PartialEq)]
pub struct MisMatch<'i> {
pub left_value: Value<'i>,
pub right_value: Value<'i>,
pub instruction: Box<Instruction<'i>>,
}
/// (fold scalar_iterable iterator instruction)
#[derive(Serialize, Debug, PartialEq)]
pub struct FoldScalar<'i> {
pub iterable: ScalarWithLambda<'i>,
#[serde(borrow)]
pub iterator: Scalar<'i>,
pub instruction: Rc<Instruction<'i>>,
}
/// (fold stream_iterable iterator instruction)
#[derive(Serialize, Debug, PartialEq)]
pub struct FoldStream<'i> {
pub iterable: Stream<'i>,
#[serde(borrow)]
pub iterator: Scalar<'i>,
pub instruction: Rc<Instruction<'i>>,
}
/// (fold stream_iterable iterator instruction)
#[derive(Serialize, Debug, PartialEq)]
pub struct Next<'i> {
pub iterator: Scalar<'i>,
}
/// (null)
#[derive(Serialize, Debug, PartialEq)]
pub struct Null;

View File

@ -0,0 +1,23 @@
/*
* 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::*;
impl<'i> Ap<'i> {
pub fn new(argument: ApArgument<'i>, result: Variable<'i>) -> Self {
Self { argument, result }
}
}

View File

@ -0,0 +1,109 @@
/*
* 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::*;
use std::fmt;
impl fmt::Display for Instruction<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use Instruction::*;
match self {
Null(null) => write!(f, "{}", null),
Call(call) => write!(f, "{}", call),
Ap(ap) => write!(f, "{}", ap),
Seq(seq) => write!(f, "{}", seq),
Par(par) => write!(f, "{}", par),
Xor(xor) => write!(f, "{}", xor),
Match(match_) => write!(f, "{}", match_),
MisMatch(mismatch) => write!(f, "{}", mismatch),
FoldScalar(fold) => write!(f, "{}", fold),
FoldStream(fold) => write!(f, "{}", fold),
Next(next) => write!(f, "{}", next),
Error => Ok(()),
}
}
}
impl fmt::Display for Call<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use itertools::Itertools;
let args = self.args.iter().map(|arg| format!("{}", arg)).join(" ");
write!(f, "call {} [{}] {}", self.triplet, args, self.output)
}
}
impl fmt::Display for Ap<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ap {} {}", self.argument, self.result)
}
}
impl fmt::Display for FoldScalar<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "fold {} {}", self.iterable, self.iterator)
}
}
impl fmt::Display for FoldStream<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "fold {} {}", self.iterable, self.iterator)
}
}
impl fmt::Display for Seq<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "seq")
}
}
impl fmt::Display for Par<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "par")
}
}
impl fmt::Display for Null {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "null")
}
}
impl fmt::Display for Xor<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "xor")
}
}
impl fmt::Display for Match<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "match {} {}", self.left_value, self.right_value)
}
}
impl fmt::Display for MisMatch<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "mismatch {} {}", self.left_value, self.right_value)
}
}
impl fmt::Display for Next<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "next")
}
}

View File

@ -0,0 +1,26 @@
/*
* 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.
*/
mod instruction_arguments;
mod instructions;
mod values;
pub use instruction_arguments::*;
pub use instructions::*;
pub use values::*;
pub use crate::parser::lexer::LastErrorPath;
pub use crate::parser::lexer::Number;

View File

@ -0,0 +1,69 @@
/*
* 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.
*/
mod impls;
mod traits;
use air_lambda_parser::LambdaAST;
use serde::Deserialize;
use serde::Serialize;
/// A scalar value without lambda.
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct Scalar<'i> {
pub name: &'i str,
}
/// A scalar value with possible lambda expression.
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct ScalarWithLambda<'i> {
pub name: &'i str,
#[serde(borrow)]
pub lambda: Option<LambdaAST<'i>>,
}
/// A stream without lambda.
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct Stream<'i> {
pub name: &'i str,
}
/// A stream with possible lambda expression.
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct StreamWithLambda<'i> {
pub name: &'i str,
#[serde(borrow)]
pub lambda: Option<LambdaAST<'i>>,
}
/// A variable that could be either scalar or stream without lambda.
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub enum Variable<'i> {
#[serde(borrow)]
Scalar(Scalar<'i>),
#[serde(borrow)]
Stream(Stream<'i>),
}
/// A variable that could be either scalar or stream with possible lambda expression.
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub enum VariableWithLambda<'i> {
#[serde(borrow)]
Scalar(ScalarWithLambda<'i>),
#[serde(borrow)]
Stream(StreamWithLambda<'i>),
}

View File

@ -0,0 +1,131 @@
/*
* 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::*;
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 }
}
// 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 {
let lambda = unsafe { LambdaAST::new_unchecked(lambda) };
Self {
name,
lambda: Some(lambda),
}
}
}
impl<'i> StreamWithLambda<'i> {
pub fn new(name: &'i str, lambda: Option<LambdaAST<'i>>) -> Self {
Self { name, lambda }
}
// 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 {
let lambda = unsafe { LambdaAST::new_unchecked(lambda) };
Self {
name,
lambda: Some(lambda),
}
}
}
impl<'i> Scalar<'i> {
pub fn new(name: &'i str) -> Self {
Self { name }
}
}
impl<'i> Stream<'i> {
pub fn new(name: &'i str) -> Self {
Self { name }
}
}
impl<'i> Variable<'i> {
pub fn scalar(name: &'i str) -> Self {
Self::Scalar(Scalar { name })
}
pub fn stream(name: &'i str) -> Self {
Self::Stream(Stream { name })
}
pub fn name(&self) -> &str {
match self {
Variable::Scalar(scalar) => scalar.name,
Variable::Stream(stream) => stream.name,
}
}
}
impl<'i> VariableWithLambda<'i> {
pub fn scalar(name: &'i str) -> Self {
Self::Scalar(ScalarWithLambda { name, lambda: None })
}
pub fn scalar_wl(name: &'i str, lambda: LambdaAST<'i>) -> Self {
Self::Scalar(ScalarWithLambda {
name,
lambda: Some(lambda),
})
}
pub fn stream(name: &'i str) -> Self {
Self::Stream(StreamWithLambda { name, lambda: None })
}
pub fn stream_wl(name: &'i str, lambda: LambdaAST<'i>) -> Self {
Self::Stream(StreamWithLambda {
name,
lambda: Some(lambda),
})
}
pub fn name(&self) -> &str {
match self {
VariableWithLambda::Scalar(scalar) => scalar.name,
VariableWithLambda::Stream(stream) => stream.name,
}
}
pub fn lambda(&self) -> &Option<LambdaAST> {
match self {
VariableWithLambda::Scalar(scalar) => &scalar.lambda,
VariableWithLambda::Stream(stream) => &stream.lambda,
}
}
// 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);
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);
Self::Stream(stream)
}
}

View File

@ -0,0 +1,71 @@
/*
* 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::*;
use air_lambda_ast::format_ast;
use std::fmt;
impl fmt::Display for Scalar<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.name)
}
}
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)),
None => write!(f, "{}", self.name),
}
}
}
impl fmt::Display for Stream<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.name)
}
}
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)),
None => write!(f, "{}", self.name),
}
}
}
impl fmt::Display for Variable<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use Variable::*;
match self {
Scalar(scalar) => write!(f, "{}", scalar),
Stream(stream) => write!(f, "{}", stream),
}
}
}
impl fmt::Display for VariableWithLambda<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use VariableWithLambda::*;
match self {
Scalar(scalar) => write!(f, "{}", scalar),
Stream(stream) => write!(f, "{}", stream),
}
}
}

View File

@ -24,9 +24,9 @@
unreachable_patterns
)]
pub mod ast;
mod parser;
pub use parser::ast;
pub use parser::parse;
pub use parser::AIRLexer;
pub use parser::AIRParser;

View File

@ -1,9 +1,12 @@
use crate::parser::ast::*;
use crate::parser::air_parser::make_stream_iterable_error;
use crate::ast::*;
use crate::parser::ParserError;
use crate::parser::VariableValidator;
use crate::parser::Span;
use crate::parser::lexer::Token;
use crate::parser::lexer::LastErrorPath;
use crate::parser::lexer::Number;
use crate::parser::air_utils::*;
use crate::make_user_error;
use air_lambda_parser::LambdaAST;
use lalrpop_util::ErrorRecovery;
@ -15,25 +18,27 @@ grammar<'err, 'input, 'v>(input: &'input str, errors: &'err mut Vec<ErrorRecover
pub AIR = Instr;
Instr: Box<Instruction<'input>> = {
<left: @L> "(" call <peer_part:PeerPart> <function_part:FPart> <args:Args> <output:Output?> ")" <right: @R> => {
<left: @L> "(" call <peer_part:PeerPart> <function_part:FPart> <args:Args> <output:CallOutput?> ")" <right: @R> => {
let triplet = match try_to_raw_triplet(peer_part, function_part) {
Some(raw_triplet) => raw_triplet,
None => {
// none means error
errors.push(make_user_error!(InvalidCallTriplet, left, Token::Call, right));
return Box::new(Instruction::Error);
}
};
let output = output.map(CallOutputValue::Variable).unwrap_or(CallOutputValue::None);
let args = Rc::new(args);
let call = Call { peer_part, function_part, args, output };
let call = Call { triplet, args, output};
let span = Span { left, right };
validator.met_call(&call, span);
Box::new(Instruction::Call(call))
},
<left: @L> "(" ap <arg:ApArgument> <res:Output> ")" <right: @R> => {
if let ApArgument::VariableWithLambda(vl) = &arg {
if let AstVariable::Stream(_) = &vl.variable {
let token = Token::VariableWithLambda(vl.variable.clone(), vl.lambda.clone());
errors.push(make_stream_iterable_error(left, token, right));
};
}
let apply = Ap::new(arg, res);
<left: @L> "(" ap <arg:ApArgument> <result:ApResult> ")" <right: @R> => {
let apply = Ap::new(arg, result);
let span = Span { left, right };
validator.met_ap(&apply, span);
@ -44,7 +49,8 @@ Instr: Box<Instruction<'input>> = {
"(" par <l:Instr> <r:Instr> ")" => Box::new(Instruction::Par(Par(l, r))),
"(" null ")" => Box::new(Instruction::Null(Null)),
<left: @L> "(" fold <iterable:ScalarIterable> <iterator:Alphanumeric> <i:Instr> ")" <right: @R> => {
<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 };
@ -53,17 +59,21 @@ Instr: Box<Instruction<'input>> = {
Box::new(Instruction::FoldScalar(fold))
},
<left: @L> "(" fold <stream:Stream> <iterator:Alphanumeric> <i:Instr> ")" <right: @R> => {
<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 { stream_name: stream, iterator, instruction };
let fold = FoldStream { iterable, iterator, instruction };
let span = Span { left, right };
validator.meet_fold_stream(&fold, span);
Box::new(Instruction::FoldStream(fold))
},
<left: @L> "(" next <i:Alphanumeric> ")" <right: @R> => {
let next = Next(i);
<left: @L> "(" next <iterator_name:Scalar> ")" <right: @R> => {
let iterator = Scalar { name: iterator_name };
let next = Next { iterator };
let span = Span { left, right };
validator.met_next(&next, span);
@ -72,7 +82,7 @@ Instr: Box<Instruction<'input>> = {
"(" xor <l:Instr> <r:Instr> ")" => Box::new(Instruction::Xor(Xor(l, r))),
<left: @L> "(" match_ <l:Matchable> <r:Matchable> <i:Instr> ")" <right: @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 };
validator.met_match(&match_, span);
@ -80,7 +90,7 @@ Instr: Box<Instruction<'input>> = {
Box::new(Instruction::Match(match_))
},
<left: @L> "(" mismatch <l:Matchable> <r:Matchable> <i:Instr> ")" <right: @R> => {
<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 };
validator.met_mismatch(&mismatch, span);
@ -91,7 +101,7 @@ Instr: Box<Instruction<'input>> = {
! => { errors.push(<>); Box::new(Instruction::Error) },
}
Args: Vec<CallInstrArgValue<'input>> = {
Args: Vec<Value<'input>> = {
"[" <args:(<Arg>)*> "]" => args
}
@ -105,9 +115,17 @@ PeerPart: PeerPart<'input> = {
"(" <pid:PeerId> <sid:ServiceId> ")" => PeerPart::PeerPkWithServiceId(pid, sid),
}
Output: AstVariable<'input> = {
<a:Alphanumeric> => AstVariable::Scalar(a),
<s:Stream> => AstVariable::Stream(s),
ApResult = ScriptVariable;
CallOutput = ScriptVariable;
ScriptVariable: Variable<'input> = {
<scalar:Scalar> => Variable::scalar(scalar),
<stream:Stream> => Variable::stream(stream),
};
FoldScalarIterable: ScalarWithLambda<'input> = {
<scalar:Scalar> => ScalarWithLambda::new(scalar, None),
<scalar:ScalarWithLambda> => ScalarWithLambda::new(scalar.0, Some(scalar.1)),
};
Function = CallInstrValue;
@ -115,71 +133,38 @@ PeerId = CallInstrValue;
ServiceId = CallInstrValue;
CallInstrValue: CallInstrValue<'input> = {
<l:Literal> => CallInstrValue::Literal(l),
<a:Alphanumeric> => CallInstrValue::Variable(AstVariable::Scalar(a)),
<s:Stream> => CallInstrValue::Variable(AstVariable::Stream(s)),
<left: @L> <vl:VariableWithLambda> <right: @R> => {
let variable = vl.0;
let lambda = vl.1;
CallInstrValue::VariableWithLambda(VariableWithLambda::new(variable, lambda))
},
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)),
}
Arg = CallInstrArgValue;
Arg = Value;
CallInstrArgValue: CallInstrArgValue<'input> = {
<s:Literal> => CallInstrArgValue::Literal(s),
<v:Alphanumeric> => CallInstrArgValue::Variable(AstVariable::Scalar(v)),
<v:Stream> => CallInstrArgValue::Variable(AstVariable::Stream(v)),
<vl:VariableWithLambda> => CallInstrArgValue::VariableWithLambda(VariableWithLambda::new(vl.0, vl.1)),
<n:Number> => CallInstrArgValue::Number(n),
<b:Boolean> => CallInstrArgValue::Boolean(b),
InitPeerId => CallInstrArgValue::InitPeerId,
EmptyArray => CallInstrArgValue::EmptyArray,
<p:LastError> => CallInstrArgValue::LastError(p),
Value: Value<'input> = {
InitPeerId => Value::InitPeerId,
<e:LastError> => Value::LastError(e),
<l:Literal> => Value::Literal(l),
<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)),
}
ApArgument: ApArgument<'input> = {
<a:Alphanumeric> => ApArgument::ScalarVariable(a),
<vl:VariableWithLambda> => ApArgument::VariableWithLambda(VariableWithLambda::new(vl.0, vl.1)),
InitPeerId => ApArgument::InitPeerId,
<e:LastError> => ApArgument::LastError(e),
<l:Literal> => ApArgument::Literal(l),
<n:Number> => ApArgument::Number(n),
<b:Boolean> => ApArgument::Boolean(b),
EmptyArray => ApArgument::EmptyArray,
<s:Literal> => ApArgument::Literal(s),
<p:LastError> => ApArgument::LastError(p),
};
ScalarIterable: IterableScalarValue<'input> = {
<v:Alphanumeric> => IterableScalarValue::ScalarVariable(v),
<l:@L> <vl:VariableWithLambda> <r:@R> => {
use crate::parser::air::AstVariable::*;
let variable = vl.0;
let lambda = vl.1;
let scalar_name = match variable {
Stream(name) => {
let token = Token::VariableWithLambda(variable, lambda.clone());
errors.push(make_stream_iterable_error(l, token, r));
name
}
Scalar(name) => name,
};
IterableScalarValue::VariableWithLambda { scalar_name, lambda }
}
}
Matchable: MatchableValue<'input> = {
InitPeerId => MatchableValue::InitPeerId,
<v:Alphanumeric> => MatchableValue::Variable(AstVariable::Scalar(v)),
<v:Stream> => MatchableValue::Variable(AstVariable::Stream(v)),
<s:Literal> => MatchableValue::Literal(s),
<b:Boolean> => MatchableValue::Boolean(b),
<n:Number> => MatchableValue::Number(n),
EmptyArray => MatchableValue::EmptyArray,
<vl:VariableWithLambda> => MatchableValue::VariableWithLambda(VariableWithLambda::new(vl.0, vl.1)),
<scalar:Scalar> => ApArgument::Scalar(ScalarWithLambda { name: scalar, lambda: None }),
<scalar:ScalarWithLambda> => ApArgument::Scalar(ScalarWithLambda { name: scalar.0, lambda: Some(scalar.1) }),
}
extern {
@ -194,9 +179,10 @@ extern {
EmptyArray => Token::SquareBrackets,
Literal => Token::StringLiteral(<&'input str>),
Alphanumeric => Token::Alphanumeric(<&'input str>),
Stream => Token::Stream(<&'input str>),
VariableWithLambda => Token::VariableWithLambda(<AstVariable<'input>>, <LambdaAST<'input>>),
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>>},
Number => Token::Number(<Number>),
Boolean => Token::Boolean(<bool>),

File diff suppressed because it is too large Load Diff

View File

@ -15,12 +15,12 @@
*/
use super::air;
use super::ast::Instruction;
use super::lexer::AIRLexer;
use super::lexer::LexerError;
use super::lexer::Token;
use super::ParserError;
use crate::ast::Instruction;
use crate::parser::VariableValidator;
use air::AIRParser;
@ -133,6 +133,9 @@ fn parser_error_to_label(file_id: usize, error: ParserError) -> Label<usize> {
UndefinedVariable(start, end, _) => {
Label::primary(file_id, start..end).with_message(error.to_string())
}
InvalidCallTriplet(start, end) => {
Label::primary(file_id, start..end).with_message(error.to_string())
}
}
}
@ -180,25 +183,3 @@ fn lexical_error_to_label(file_id: usize, error: LexerError) -> Label<usize> {
}
}
}
macro_rules! make_user_error(
($error_type:ident, $start_pos: ident, $token:ident, $end_pos: ident) => { {
let error = ParserError::$error_type($start_pos, $end_pos);
let error = ParseError::User { error };
let dropped_tokens = vec![($start_pos, $token, $end_pos)];
ErrorRecovery {
error,
dropped_tokens,
}
}}
);
pub(super) fn make_stream_iterable_error(
start_pos: usize,
token: Token<'_>,
end_pos: usize,
) -> ErrorRecovery<usize, Token<'_>, ParserError> {
make_user_error!(LambdaAppliedToStream, start_pos, token, end_pos)
}

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.
*/
mod triplet;
pub(crate) use triplet::try_to_raw_triplet;
#[macro_export]
macro_rules! make_user_error(
($error_type:ident, $start_pos: ident, $token:expr, $end_pos: ident) => { {
let error = crate::parser::ParserError::$error_type($start_pos, $end_pos);
let error = lalrpop_util::ParseError::User { error };
let dropped_tokens = vec![($start_pos, $token, $end_pos)];
ErrorRecovery {
error,
dropped_tokens,
}
}}
);

View File

@ -0,0 +1,49 @@
/*
* 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::ast::FunctionPart;
use crate::ast::PeerPart;
use crate::ast::Triplet;
/// Build a `Triplet` from `Call`'s `PeerPart` and `FunctionPart`
pub(crate) fn try_to_raw_triplet<'i>(
peer: PeerPart<'i>,
f: FunctionPart<'i>,
) -> Option<Triplet<'i>> {
use FunctionPart::*;
use PeerPart::*;
let (peer_pk, service_id, function_name) = match (peer, f) {
(PeerPkWithServiceId(peer_pk, _), ServiceIdWithFuncName(service_id, func_name)) => {
(peer_pk, service_id, func_name)
}
(PeerPkWithServiceId(peer_pk, peer_service_id), FuncName(func_name)) => {
(peer_pk, peer_service_id, func_name)
}
(PeerPk(peer_pk), ServiceIdWithFuncName(service_id, func_name)) => {
(peer_pk, service_id, func_name)
}
(PeerPk(_), FuncName(_)) => return None,
};
let raw_triplet = Triplet {
peer_pk,
service_id,
function_name,
};
Some(raw_triplet)
}

View File

@ -1,178 +0,0 @@
/*
* 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.
*/
mod impls;
mod traits;
pub use crate::parser::lexer::AstVariable;
pub use crate::parser::lexer::LastErrorPath;
pub use crate::parser::lexer::Number;
use air_lambda_parser::LambdaAST;
use serde::Deserialize;
use serde::Serialize;
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>),
Par(Par<'i>),
Xor(Xor<'i>),
Match(Match<'i>),
MisMatch(MisMatch<'i>),
FoldScalar(FoldScalar<'i>),
FoldStream(FoldStream<'i>),
Next(Next<'i>),
Error,
}
#[derive(Serialize, Debug, PartialEq)]
pub enum PeerPart<'i> {
PeerPk(CallInstrValue<'i>),
PeerPkWithServiceId(CallInstrValue<'i>, CallInstrValue<'i>),
}
#[derive(Serialize, Debug, PartialEq)]
pub enum FunctionPart<'i> {
FuncName(CallInstrValue<'i>),
ServiceIdWithFuncName(CallInstrValue<'i>, CallInstrValue<'i>),
}
#[derive(Serialize, Debug, PartialEq)]
pub struct Call<'i> {
pub peer_part: PeerPart<'i>,
pub function_part: FunctionPart<'i>,
pub args: Rc<Vec<CallInstrArgValue<'i>>>,
pub output: CallOutputValue<'i>,
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub enum ApArgument<'i> {
ScalarVariable(&'i str),
VariableWithLambda(VariableWithLambda<'i>),
Number(Number),
Boolean(bool),
Literal(&'i str),
EmptyArray,
LastError(LastErrorPath),
}
#[derive(Serialize, Debug, PartialEq)]
pub struct Ap<'i> {
pub argument: ApArgument<'i>,
pub result: AstVariable<'i>,
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub enum CallInstrValue<'i> {
InitPeerId,
Literal(&'i str),
Variable(AstVariable<'i>),
VariableWithLambda(VariableWithLambda<'i>),
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub enum CallInstrArgValue<'i> {
InitPeerId,
LastError(LastErrorPath),
Literal(&'i str),
Number(Number),
Boolean(bool),
EmptyArray, // only empty arrays are allowed now
Variable(AstVariable<'i>),
VariableWithLambda(VariableWithLambda<'i>),
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub enum IterableScalarValue<'i> {
ScalarVariable(&'i str),
VariableWithLambda {
scalar_name: &'i str,
lambda: LambdaAST<'i>,
},
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub enum MatchableValue<'i> {
InitPeerId,
Literal(&'i str),
Number(Number),
Boolean(bool),
EmptyArray,
Variable(AstVariable<'i>),
VariableWithLambda(VariableWithLambda<'i>),
}
#[derive(Serialize, Debug, PartialEq, Clone)]
pub enum CallOutputValue<'i> {
Variable(AstVariable<'i>),
None,
}
#[derive(Serialize, Debug, PartialEq)]
pub struct Seq<'i>(pub Box<Instruction<'i>>, pub Box<Instruction<'i>>);
#[derive(Serialize, Debug, PartialEq)]
pub struct Par<'i>(pub Box<Instruction<'i>>, pub Box<Instruction<'i>>);
#[derive(Serialize, Debug, PartialEq)]
pub struct Xor<'i>(pub Box<Instruction<'i>>, pub Box<Instruction<'i>>);
#[derive(Serialize, Debug, PartialEq)]
pub struct Match<'i> {
pub left_value: MatchableValue<'i>,
pub right_value: MatchableValue<'i>,
pub instruction: Box<Instruction<'i>>,
}
#[derive(Serialize, Debug, PartialEq)]
pub struct MisMatch<'i> {
pub left_value: MatchableValue<'i>,
pub right_value: MatchableValue<'i>,
pub instruction: Box<Instruction<'i>>,
}
#[derive(Serialize, Debug, PartialEq)]
pub struct FoldScalar<'i> {
pub iterable: IterableScalarValue<'i>,
pub iterator: &'i str,
pub instruction: Rc<Instruction<'i>>,
}
#[derive(Serialize, Debug, PartialEq)]
pub struct FoldStream<'i> {
pub stream_name: &'i str,
pub iterator: &'i str,
pub instruction: Rc<Instruction<'i>>,
}
#[derive(Serialize, Debug, PartialEq)]
pub struct Next<'i>(pub &'i str);
#[derive(Serialize, Debug, PartialEq)]
pub struct Null;
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct VariableWithLambda<'i> {
pub variable: AstVariable<'i>,
#[serde(borrow)]
pub lambda: LambdaAST<'i>,
}

View File

@ -1,47 +0,0 @@
/*
* 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 super::*;
use air_lambda_parser::ValueAccessor;
impl<'i> Ap<'i> {
pub fn new(argument: ApArgument<'i>, result: AstVariable<'i>) -> Self {
Self { argument, result }
}
}
impl<'i> VariableWithLambda<'i> {
pub fn new(variable: AstVariable<'i>, lambda: LambdaAST<'i>) -> Self {
Self { variable, lambda }
}
// This function is unsafe and lambda must be non-empty, although it's used only for tests
pub fn from_raw_algebras(variable: AstVariable<'i>, lambda: Vec<ValueAccessor<'i>>) -> Self {
let lambda = unsafe { LambdaAST::new_unchecked(lambda) };
Self { variable, lambda }
}
}
impl<'i> IterableScalarValue<'i> {
// This function is unsafe and lambda must be non-empty, although it's used only for tests
pub fn new_vl(scalar_name: &'i str, lambda: Vec<ValueAccessor<'i>>) -> Self {
let lambda = unsafe { LambdaAST::new_unchecked(lambda) };
Self::VariableWithLambda {
scalar_name,
lambda,
}
}
}

View File

@ -1,228 +0,0 @@
/*
* 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 super::*;
use std::fmt;
impl fmt::Display for CallInstrArgValue<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use CallInstrArgValue::*;
match self {
InitPeerId => write!(f, "%init_peer_id%"),
LastError(error_path) => write!(f, "%last_error%{}", error_path),
Literal(str) => write!(f, r#""{}""#, str),
Number(number) => write!(f, "{}", number),
Boolean(bool) => write!(f, "{}", bool),
EmptyArray => write!(f, "[]"),
Variable(str) => write!(f, "{}", str),
VariableWithLambda(vl) => write!(f, "{}", vl),
}
}
}
impl fmt::Display for CallInstrValue<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use CallInstrValue::*;
match self {
InitPeerId => write!(f, "%init_peer_id%"),
Literal(str) => write!(f, r#""{}""#, str),
Variable(str) => write!(f, "{}", str),
VariableWithLambda(vl) => write!(f, "{}", vl),
}
}
}
impl fmt::Display for IterableScalarValue<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use IterableScalarValue::*;
match self {
ScalarVariable(str) => write!(f, "{}", str),
VariableWithLambda {
scalar_name,
lambda,
} => write!(f, "{}.$.{:?}", scalar_name, lambda),
}
}
}
impl fmt::Display for MatchableValue<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use MatchableValue::*;
match self {
InitPeerId => write!(f, "%init_peer_id%"),
Literal(str) => write!(f, r#""{}""#, str),
Number(number) => write!(f, "{}", number),
Boolean(bool) => write!(f, "{}", bool),
EmptyArray => write!(f, "[]"),
Variable(str) => write!(f, "{}", str),
VariableWithLambda(vl) => write!(f, "{}", vl),
}
}
}
impl fmt::Display for CallOutputValue<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use CallOutputValue::*;
match self {
Variable(variable) => write!(f, "{}", variable),
None => Ok(()),
}
}
}
impl fmt::Display for PeerPart<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use PeerPart::*;
match self {
PeerPk(peer_pk) => write!(f, "{}", peer_pk),
PeerPkWithServiceId(peer_pk, service_id) => write!(f, "({} {})", peer_pk, service_id),
}
}
}
impl fmt::Display for FunctionPart<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use FunctionPart::*;
match self {
FuncName(func_name) => write!(f, "{}", func_name),
ServiceIdWithFuncName(service_id, func_name) => {
write!(f, "({} {})", service_id, func_name)
}
}
}
}
impl fmt::Display for Instruction<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use Instruction::*;
match self {
Null(null) => write!(f, "{}", null),
Call(call) => write!(f, "{}", call),
Ap(ap) => write!(f, "{}", ap),
Seq(seq) => write!(f, "{}", seq),
Par(par) => write!(f, "{}", par),
Xor(xor) => write!(f, "{}", xor),
Match(match_) => write!(f, "{}", match_),
MisMatch(mismatch) => write!(f, "{}", mismatch),
FoldScalar(fold) => write!(f, "{}", fold),
FoldStream(fold) => write!(f, "{}", fold),
Next(next) => write!(f, "{}", next),
Error => Ok(()),
}
}
}
impl fmt::Display for Call<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use itertools::Itertools;
write!(f, "call {} {}", self.peer_part, self.function_part)?;
let args = self.args.iter().map(|arg| format!("{}", arg)).join(" ");
write!(f, " [{}]", args)?;
write!(f, " {}", self.output)
}
}
impl fmt::Display for Ap<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ap {} {}", self.argument, self.result)
}
}
impl fmt::Display for ApArgument<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ApArgument::ScalarVariable(name) => write!(f, "{}", name),
ApArgument::VariableWithLambda(vl) => write!(f, "{}", vl),
ApArgument::LastError(error_path) => write!(f, "{}", error_path),
ApArgument::Number(value) => write!(f, "{}", value),
ApArgument::Boolean(value) => write!(f, "{}", value),
ApArgument::Literal(value) => write!(f, "{}", value),
ApArgument::EmptyArray => write!(f, "[]"),
}
}
}
impl fmt::Display for FoldScalar<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "fold {} {}", self.iterable, self.iterator)
}
}
impl fmt::Display for FoldStream<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "fold {} {}", self.stream_name, self.iterator)
}
}
impl fmt::Display for Seq<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "seq")
}
}
impl fmt::Display for Par<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "par")
}
}
impl fmt::Display for Null {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "null")
}
}
impl fmt::Display for Xor<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "xor")
}
}
impl fmt::Display for Match<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "match {} {}", self.left_value, self.right_value)
}
}
impl fmt::Display for MisMatch<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "mismatch {} {}", self.left_value, self.right_value)
}
}
impl fmt::Display for Next<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "next")
}
}
impl fmt::Display for VariableWithLambda<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.$.{:?}", self.variable, self.lambda,)
}
}

View File

@ -30,6 +30,10 @@ pub enum ParserError {
#[error("iterable '{2}' wasn't defined")]
UndefinedIterable(usize, usize, String),
/// Semantic errors in a call instructions.
#[error("call should have service id specified by peer part or function part")]
InvalidCallTriplet(usize, usize),
}
impl From<std::convert::Infallible> for ParserError {

View File

@ -14,7 +14,6 @@
* limitations under the License.
*/
use super::AstVariable;
use super::LexerError;
use super::LexerResult;
use super::Token;
@ -273,59 +272,57 @@ impl<'input> CallVariableParser<'input> {
self.current_pos() == self.string_to_parse.len() - 1
}
fn to_variable<'v>(&self, variable_name: &'v str) -> AstVariable<'v> {
fn to_variable_token<'v>(&self, name: &'v str) -> Token<'v> {
if self.state.is_first_stream_tag {
// TODO: cut the stream tag after the refactoring.
AstVariable::Stream(variable_name)
Token::Stream { name }
} else {
AstVariable::Scalar(variable_name)
Token::Scalar { name }
}
}
fn try_to_variable_and_lambda(
&self,
pos: usize,
) -> LexerResult<(&'input str, LambdaAST<'input>)> {
// +2 to ignore ".$" prefix
let lambda = crate::parse_lambda(&self.string_to_parse[pos + 2..]).map_err(|e| {
LexerError::LambdaParserError(
self.start_pos + pos,
self.start_pos + self.string_to_parse.len(),
e.to_string(),
)
})?;
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 }
} else {
Token::ScalarWithLambda { name, lambda }
}
}
Ok((&self.string_to_parse[0..pos], lambda))
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| {
LexerError::LambdaParserError(
self.start_pos + lambda_start_pos,
self.start_pos + self.string_to_parse.len(),
e.to_string(),
)
})?;
let token =
self.to_variable_token_with_lambda(&self.string_to_parse[0..lambda_start_pos], lambda);
Ok(token)
}
fn to_token(&self) -> LexerResult<Token<'input>> {
use super::token::UnparsedNumber;
match (self.is_possible_to_parse_as_number(), self.dot_met()) {
(true, false) => {
let is_number = self.is_possible_to_parse_as_number();
let token = match (is_number, self.state.first_dot_met_pos) {
(true, None) => {
let number = UnparsedNumber::Int(self.string_to_parse, self.start_pos);
let number: super::Number = number.try_into()?;
Ok(number.into())
number.into()
}
(true, true) => {
(true, Some(_)) => {
let number = UnparsedNumber::Float(self.string_to_parse, self.start_pos);
let number: super::Number = number.try_into()?;
Ok(number.into())
number.into()
}
(false, false) => {
if self.state.is_first_stream_tag {
Ok(Token::Stream(self.string_to_parse))
} else {
Ok(Token::Alphanumeric(self.string_to_parse))
}
}
(false, true) => {
let lambda_start_pos = self.state.first_dot_met_pos.unwrap();
let (variable, lambda) = self.try_to_variable_and_lambda(lambda_start_pos)?;
let variable = self.to_variable(variable);
(false, None) => self.to_variable_token(self.string_to_parse),
(false, Some(lambda_start_pos)) => self.try_to_variable_and_lambda(lambda_start_pos)?,
};
Ok(Token::VariableWithLambda(variable, lambda))
}
}
Ok(token)
}
}

View File

@ -25,7 +25,6 @@ mod tests;
pub use air_lexer::AIRLexer;
pub use errors::LexerError;
pub use token::AstVariable;
pub use token::LastErrorPath;
pub use token::Number;
pub use token::Token;

View File

@ -16,7 +16,6 @@
use super::air_lexer::Spanned;
use super::AIRLexer;
use super::AstVariable;
use super::LastErrorPath;
use super::LexerError;
use super::Number;
@ -159,7 +158,10 @@ fn init_peer_id() {
fn stream() {
const STREAM: &str = "$stream____asdasd";
lexer_test(STREAM, Single(Ok((0, Token::Stream(STREAM), STREAM.len()))));
lexer_test(
STREAM,
Single(Ok((0, Token::Stream { name: STREAM }, STREAM.len()))),
);
}
#[test]
@ -267,20 +269,22 @@ fn too_big_float_number() {
fn lambda() {
// this lambda contains all allowed in lambda characters
const LAMBDA: &str = r#"value.$.field[1]"#;
let variable = AstVariable::Scalar("value");
lexer_test(
LAMBDA,
Single(Ok((
0,
Token::VariableWithLambda(variable, unsafe {
LambdaAST::new_unchecked(vec![
ValueAccessor::FieldAccess {
field_name: "field",
},
ValueAccessor::ArrayAccess { idx: 1 },
])
}),
Token::ScalarWithLambda {
name: "value",
lambda: unsafe {
LambdaAST::new_unchecked(vec![
ValueAccessor::FieldAccess {
field_name: "field",
},
ValueAccessor::ArrayAccess { idx: 1 },
])
},
},
LAMBDA.len(),
))),
);
@ -447,7 +451,9 @@ fn booleans() {
NON_BOOL_CONST,
Single(Ok((
0,
Token::Alphanumeric(NON_BOOL_CONST),
Token::Scalar {
name: NON_BOOL_CONST,
},
NON_BOOL_CONST.len(),
))),
);

View File

@ -32,9 +32,20 @@ pub enum Token<'input> {
SquareBrackets, // [] symbolize empty array, it's possible to have it only in an argument position
StringLiteral(&'input str),
Alphanumeric(&'input str),
Stream(&'input str),
VariableWithLambda(AstVariable<'input>, LambdaAST<'input>),
Scalar {
name: &'input str,
},
ScalarWithLambda {
name: &'input str,
lambda: LambdaAST<'input>,
},
Stream {
name: &'input str,
},
StreamWithLambda {
name: &'input str,
lambda: LambdaAST<'input>,
},
Number(Number),
Boolean(bool),
@ -53,12 +64,6 @@ pub enum Token<'input> {
MisMatch,
}
#[derive(Debug, Clone, PartialEq, Hash, Serialize, Deserialize)]
pub enum AstVariable<'input> {
Scalar(&'input str),
Stream(&'input str),
}
#[derive(Debug, Clone, PartialEq, Hash, Serialize, Deserialize)]
pub enum LastErrorPath {
// %last_error%.instruction

View File

@ -43,17 +43,6 @@ impl fmt::Display for Number {
}
}
impl fmt::Display for AstVariable<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use AstVariable::*;
match self {
Scalar(name) => write!(f, "{}", name),
Stream(name) => write!(f, "&{}", name),
}
}
}
impl From<Number> for Token<'_> {
fn from(value: Number) -> Self {
Token::Number(value)

View File

@ -15,14 +15,14 @@
*/
pub mod air_parser;
mod lexer;
mod air_utils;
pub(crate) mod lexer;
// air is auto-generated, so exclude it from `cargo fmt -- --check` and `cargo clippy`
#[rustfmt::skip]
#[allow(clippy::all)]
mod air;
pub mod ast;
mod errors;
mod validator;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,76 @@
/*
* 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::*;
#[test]
fn ap_with_literal() {
let source_code = r#"
(ap "some_string" $stream)
"#;
let actual = parse(source_code);
let expected = ap(
ApArgument::Literal("some_string"),
Variable::stream("$stream"),
);
assert_eq!(actual, expected);
}
#[test]
fn ap_with_number() {
let source_code = r#"
(ap -100 $stream)
"#;
let actual = parse(source_code);
let expected = ap(
ApArgument::Number(Number::Int(-100)),
Variable::stream("$stream"),
);
assert_eq!(actual, expected);
}
#[test]
fn ap_with_bool() {
let source_code = r#"
(ap true $stream)
"#;
let actual = parse(source_code);
let expected = ap(ApArgument::Boolean(true), Variable::stream("$stream"));
assert_eq!(actual, expected);
}
#[test]
fn ap_with_last_error() {
let source_code = r#"
(ap %last_error%.$.msg! $stream)
"#;
let actual = parse(source_code);
let expected = ap(
ApArgument::LastError(LastErrorPath::Message),
Variable::stream("$stream"),
);
assert_eq!(actual, expected);
}

View File

@ -0,0 +1,494 @@
/*
* 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 air_lambda_ast::ValueAccessor;
use lalrpop_util::ParseError;
use std::rc::Rc;
#[test]
fn parse_json_path() {
let source_code = r#"
(call peer_id.$.a! ("service_id" "function_name") ["hello" name] $void)
"#;
let instruction = parse(source_code);
let expected = call(
CallInstrValue::Variable(VariableWithLambda::from_raw_lambda_scalar(
"peer_id",
vec![ValueAccessor::FieldAccess { field_name: "a" }],
)),
CallInstrValue::Literal("service_id"),
CallInstrValue::Literal("function_name"),
Rc::new(vec![
Value::Literal("hello"),
Value::Variable(VariableWithLambda::scalar("name")),
]),
CallOutputValue::Variable(Variable::stream("$void")),
);
assert_eq!(instruction, expected);
}
#[test]
fn parse_empty_array() {
let source_code = r#"
(call peer_id (service_id "function_name") ["" [] arg])
"#;
let actual = parse(source_code);
let expected = call(
CallInstrValue::Variable(VariableWithLambda::scalar("peer_id")),
CallInstrValue::Variable(VariableWithLambda::scalar("service_id")),
CallInstrValue::Literal("function_name"),
Rc::new(vec![
Value::Literal(""),
Value::EmptyArray,
Value::Variable(VariableWithLambda::scalar("arg")),
]),
CallOutputValue::None,
);
assert_eq!(actual, expected);
}
#[test]
fn parse_empty_array_2() {
let source_code = r#"
(call peer_id ("service_id" "function_name") [k [] []])
"#;
let actual = parse(source_code);
let expected = call(
CallInstrValue::Variable(VariableWithLambda::scalar("peer_id")),
CallInstrValue::Literal("service_id"),
CallInstrValue::Literal("function_name"),
Rc::new(vec![
Value::Variable(VariableWithLambda::scalar("k")),
Value::EmptyArray,
Value::EmptyArray,
]),
CallOutputValue::None,
);
assert_eq!(actual, expected);
}
#[test]
fn parse_undefined_variable() {
let source_code = r#"
(call id.$.a ("" "f") ["hello" name] $void)
"#;
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(), 2);
for i in 0..2 {
let error = &errors[i].error;
let parser_error = match error {
ParseError::User { error } => error,
_ => panic!("unexpected error type"),
};
assert!(matches!(parser_error, ParserError::UndefinedVariable(..)));
}
}
#[test]
fn parse_undefined_stream_without_json_path() {
let source_code = r#"
(call "" ("" "") [$stream])
"#;
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!(errors.is_empty());
}
#[test]
fn parse_undefined_stream_with_lambda() {
let source_code = r#"
(call "" ("" "") [$stream.$.json_path])
"#;
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);
let error = &errors[0].error;
let parser_error = match error {
ParseError::User { error } => error,
_ => panic!("unexpected error type"),
};
assert!(matches!(parser_error, ParserError::UndefinedVariable(..)));
}
#[test]
fn parse_call_with_invalid_triplet() {
let source_code = r#"
(call "" "" [$stream.$.json_path])
"#;
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");
assert_eq!(errors.len(), 1);
let error = &errors[0].error;
let parser_error = match error {
ParseError::User { error } => error,
_ => panic!("unexpected error type"),
};
assert!(matches!(parser_error, ParserError::InvalidCallTriplet(..)));
}
#[test]
fn parse_json_path_complex() {
let source_code = r#"
(seq
(call m.$.[1]! ("service_id" "function_name") [] void)
(call m.$.abc[0].cde[1][0].cde[1]! ("service_id" "function_name") [] void)
)
"#;
let instruction = parse(source_code);
let expected = seq(
call(
CallInstrValue::Variable(VariableWithLambda::from_raw_lambda_scalar(
"m",
vec![ValueAccessor::ArrayAccess { idx: 1 }],
)),
CallInstrValue::Literal("service_id"),
CallInstrValue::Literal("function_name"),
Rc::new(vec![]),
CallOutputValue::Variable(Variable::scalar("void")),
),
call(
CallInstrValue::Variable(VariableWithLambda::from_raw_lambda_scalar(
"m",
vec![
ValueAccessor::FieldAccess { field_name: "abc" },
ValueAccessor::ArrayAccess { idx: 0 },
ValueAccessor::FieldAccess { field_name: "cde" },
ValueAccessor::ArrayAccess { idx: 1 },
ValueAccessor::ArrayAccess { idx: 0 },
ValueAccessor::FieldAccess { field_name: "cde" },
ValueAccessor::ArrayAccess { idx: 1 },
],
)),
CallInstrValue::Literal("service_id"),
CallInstrValue::Literal("function_name"),
Rc::new(vec![]),
CallOutputValue::Variable(Variable::scalar("void")),
),
);
assert_eq!(instruction, expected);
}
#[test]
fn json_path_square_braces() {
let source_code = r#"
(call u.$.peer_id! ("return" "") [u.$[1].cde[0][0].abc u.$.name] $void)
"#;
let instruction = parse(source_code);
let expected = call(
CallInstrValue::Variable(VariableWithLambda::from_raw_lambda_scalar(
"u",
vec![ValueAccessor::FieldAccess {
field_name: "peer_id",
}],
)),
CallInstrValue::Literal("return"),
CallInstrValue::Literal(""),
Rc::new(vec![
Value::Variable(VariableWithLambda::from_raw_lambda_scalar(
"u",
vec![
ValueAccessor::ArrayAccess { idx: 1 },
ValueAccessor::FieldAccess { field_name: "cde" },
ValueAccessor::ArrayAccess { idx: 0 },
ValueAccessor::ArrayAccess { idx: 0 },
ValueAccessor::FieldAccess { field_name: "abc" },
],
)),
Value::Variable(VariableWithLambda::from_raw_lambda_scalar(
"u",
vec![ValueAccessor::FieldAccess { field_name: "name" }],
)),
]),
CallOutputValue::Variable(Variable::stream("$void")),
);
assert_eq!(instruction, expected);
}
#[test]
fn parse_init_peer_id() {
let peer_id = "some_peer_id";
let source_code = format!(
r#"
(seq
(call "{}" ("local_service_id" "local_fn_name") [])
(call %init_peer_id% ("service_id" "fn_name") [])
)"#,
peer_id
);
let instruction = parse(&source_code);
let expected = seq(
call(
CallInstrValue::Literal(peer_id),
CallInstrValue::Literal("local_service_id"),
CallInstrValue::Literal("local_fn_name"),
Rc::new(vec![]),
CallOutputValue::None,
),
call(
CallInstrValue::InitPeerId,
CallInstrValue::Literal("service_id"),
CallInstrValue::Literal("fn_name"),
Rc::new(vec![]),
CallOutputValue::None,
),
);
assert_eq!(instruction, expected);
}
#[test]
fn parse_last_error() {
let source_code = format!(
r#"
(seq
(call %init_peer_id% ("service_id" "fn_name") [%last_error%])
(null)
)"#,
);
let instruction = parse(&source_code);
let expected = seq(
call(
CallInstrValue::InitPeerId,
CallInstrValue::Literal("service_id"),
CallInstrValue::Literal("fn_name"),
Rc::new(vec![Value::LastError(LastErrorPath::None)]),
CallOutputValue::None,
),
null(),
);
assert_eq!(instruction, expected);
}
#[test]
fn seq_par_call() {
let peer_id = "some_peer_id";
let source_code = format!(
r#"
(seq
(par
(call "{0}" ("local_service_id" "local_fn_name") [] result_1)
(call "{0}" ("service_id" "fn_name") [] g)
)
(call "{0}" ("local_service_id" "local_fn_name") [] result_2)
)"#,
peer_id,
);
let instruction = parse(&source_code);
let expected = seq(
par(
call(
CallInstrValue::Literal(peer_id),
CallInstrValue::Literal("local_service_id"),
CallInstrValue::Literal("local_fn_name"),
Rc::new(vec![]),
CallOutputValue::Variable(Variable::scalar("result_1")),
),
call(
CallInstrValue::Literal(peer_id),
CallInstrValue::Literal("service_id"),
CallInstrValue::Literal("fn_name"),
Rc::new(vec![]),
CallOutputValue::Variable(Variable::scalar("g")),
),
),
call(
CallInstrValue::Literal(peer_id),
CallInstrValue::Literal("local_service_id"),
CallInstrValue::Literal("local_fn_name"),
Rc::new(vec![]),
CallOutputValue::Variable(Variable::scalar("result_2")),
),
);
assert_eq!(instruction, expected);
}
#[test]
fn seq_with_empty_and_dash() {
let source_code = r#"
(seq
(seq
(seq
(call "set_variables" ("" "") ["module-bytes"] module-bytes)
(call "set_variables" ("" "") ["module_config"] module_config)
)
(call "set_variables" ("" "") ["blueprint"] blueprint)
)
(seq
(call "A" ("add_module" "") [module-bytes module_config] module)
(seq
(call "A" ("add_blueprint" "") [blueprint] blueprint_id)
(seq
(call "A" ("create" "") [blueprint_id] service_id)
(call "remote_peer_id" ("" "") [service_id] client_result)
)
)
)
)
"#;
let instruction = parse(source_code);
let expected = seq(
seq(
seq(
call(
CallInstrValue::Literal("set_variables"),
CallInstrValue::Literal(""),
CallInstrValue::Literal(""),
Rc::new(vec![Value::Literal("module-bytes")]),
CallOutputValue::Variable(Variable::scalar("module-bytes")),
),
call(
CallInstrValue::Literal("set_variables"),
CallInstrValue::Literal(""),
CallInstrValue::Literal(""),
Rc::new(vec![Value::Literal("module_config")]),
CallOutputValue::Variable(Variable::scalar("module_config")),
),
),
call(
CallInstrValue::Literal("set_variables"),
CallInstrValue::Literal(""),
CallInstrValue::Literal(""),
Rc::new(vec![Value::Literal("blueprint")]),
CallOutputValue::Variable(Variable::scalar("blueprint")),
),
),
seq(
call(
CallInstrValue::Literal("A"),
CallInstrValue::Literal("add_module"),
CallInstrValue::Literal(""),
Rc::new(vec![
Value::Variable(VariableWithLambda::scalar("module-bytes")),
Value::Variable(VariableWithLambda::scalar("module_config")),
]),
CallOutputValue::Variable(Variable::scalar("module")),
),
seq(
Instruction::Call(Call {
triplet: Triplet {
peer_pk: CallInstrValue::Literal("A"),
service_id: CallInstrValue::Literal("add_blueprint"),
function_name: CallInstrValue::Literal(""),
},
args: Rc::new(vec![Value::Variable(VariableWithLambda::scalar(
"blueprint",
))]),
output: CallOutputValue::Variable(Variable::scalar("blueprint_id")),
}),
seq(
call(
CallInstrValue::Literal("A"),
CallInstrValue::Literal("create"),
CallInstrValue::Literal(""),
Rc::new(vec![Value::Variable(VariableWithLambda::scalar(
"blueprint_id",
))]),
CallOutputValue::Variable(Variable::scalar("service_id")),
),
call(
CallInstrValue::Literal("remote_peer_id"),
CallInstrValue::Literal(""),
CallInstrValue::Literal(""),
Rc::new(vec![Value::Variable(VariableWithLambda::scalar(
"service_id",
))]),
CallOutputValue::Variable(Variable::scalar("client_result")),
),
),
),
),
);
assert_eq!(instruction, expected);
}
#[test]
fn no_output() {
let source_code = r#"
(call peer (service fname) [])
"#;
let actual = parse(source_code);
let expected = call(
CallInstrValue::Variable(VariableWithLambda::scalar("peer")),
CallInstrValue::Variable(VariableWithLambda::scalar("service")),
CallInstrValue::Variable(VariableWithLambda::scalar("fname")),
Rc::new(vec![]),
CallOutputValue::None,
);
assert_eq!(actual, expected);
}

View File

@ -0,0 +1,121 @@
/*
* 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::ast::*;
use std::rc::Rc;
pub(super) fn call<'i>(
peer_pk: CallInstrValue<'i>,
service_id: CallInstrValue<'i>,
function_name: CallInstrValue<'i>,
args: Rc<Vec<Value<'i>>>,
output: CallOutputValue<'i>,
) -> Instruction<'i> {
let triplet = Triplet {
peer_pk,
service_id,
function_name,
};
Instruction::Call(Call {
triplet,
args,
output,
})
}
pub(super) fn seq<'a>(l: Instruction<'a>, r: Instruction<'a>) -> Instruction<'a> {
Instruction::Seq(Seq(Box::new(l), Box::new(r)))
}
pub(super) fn par<'a>(l: Instruction<'a>, r: Instruction<'a>) -> Instruction<'a> {
Instruction::Par(Par(Box::new(l), Box::new(r)))
}
pub(super) fn xor<'a>(l: Instruction<'a>, r: Instruction<'a>) -> Instruction<'a> {
Instruction::Xor(Xor(Box::new(l), Box::new(r)))
}
pub(super) fn seqnn() -> Instruction<'static> {
seq(null(), null())
}
pub(super) fn null() -> Instruction<'static> {
Instruction::Null(Null)
}
pub(super) fn fold_scalar<'a>(
iterable: ScalarWithLambda<'a>,
iterator: &'a str,
instruction: Instruction<'a>,
) -> Instruction<'a> {
Instruction::FoldScalar(FoldScalar {
iterable,
iterator: Scalar::new(iterator),
instruction: std::rc::Rc::new(instruction),
})
}
pub(super) fn fold_stream<'a>(
stream_name: &'a str,
iterator: &'a str,
instruction: Instruction<'a>,
) -> Instruction<'a> {
Instruction::FoldStream(FoldStream {
iterable: Stream::new(stream_name),
iterator: Scalar::new(iterator),
instruction: std::rc::Rc::new(instruction),
})
}
pub(super) fn match_<'a>(
left_value: Value<'a>,
right_value: Value<'a>,
instruction: Instruction<'a>,
) -> Instruction<'a> {
Instruction::Match(Match {
left_value,
right_value,
instruction: Box::new(instruction),
})
}
pub(super) fn mismatch<'a>(
left_value: Value<'a>,
right_value: Value<'a>,
instruction: Instruction<'a>,
) -> Instruction<'a> {
Instruction::MisMatch(MisMatch {
left_value,
right_value,
instruction: Box::new(instruction),
})
}
pub(super) fn ap<'i>(argument: ApArgument<'i>, result: Variable<'i>) -> Instruction<'i> {
Instruction::Ap(Ap { argument, result })
}
pub(super) fn binary_instruction<'a, 'b>(
name: &'a str,
) -> impl Fn(Instruction<'b>, Instruction<'b>) -> Instruction<'b> {
match name {
"xor" => |l, r| xor(l, r),
"par" => |l, r| par(l, r),
"seq" => |l, r| seq(l, r),
_ => unreachable!(),
}
}

View File

@ -0,0 +1,147 @@
/*
* 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 air_lambda_ast::ValueAccessor;
use fstrings::f;
use lalrpop_util::ParseError;
#[test]
fn parse_undefined_iterable() {
let source_code = r#"
(seq
(call "" ("" "") [] iterable)
(fold iterable i
(seq
(call "" ("" "") ["hello" ""] $void)
(next j)
)
)
)
"#;
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);
let error = &errors[0].error;
let parser_error = match error {
ParseError::User { error } => error,
_ => panic!("unexpected error type"),
};
assert!(matches!(parser_error, ParserError::UndefinedIterable(..)));
}
#[test]
fn parse_fold() {
let source_code = r#"
(fold iterable i
(null)
)
"#;
let instruction = parse(&source_code);
let expected = fold_scalar(ScalarWithLambda::new("iterable", None), "i", null());
assert_eq!(instruction, expected);
}
#[test]
fn fold_json_path() {
let source_code = r#"
; comment
(fold members.$.[123321] m (null)) ;;; comment
;;; comment
"#;
let instruction = parse(source_code);
let expected = fold_scalar(
ScalarWithLambda::from_raw_lambda(
"members",
vec![ValueAccessor::ArrayAccess { idx: 123321 }],
),
"m",
null(),
);
assert_eq!(instruction, expected);
}
#[test]
fn fold_on_stream() {
let source_code = r#"
(fold $stream iterator (null))
"#;
let instruction = parse(source_code);
let expected = fold_stream("$stream", "iterator", null());
assert_eq!(instruction, expected);
}
#[test]
fn comments() {
let source_code = r#"
; comment
(fold members.$.field[1] m (null)) ;;; comment ;;?()()
;;; comme;?!.$. nt[][][][()()()null;$::!
"#;
let instruction = parse(source_code);
let expected = fold_scalar(
ScalarWithLambda::from_raw_lambda(
"members",
vec![
ValueAccessor::FieldAccess {
field_name: "field",
},
ValueAccessor::ArrayAccess { idx: 1 },
],
),
"m",
null(),
);
assert_eq!(instruction, expected);
}
fn source_fold_with(name: &str) -> String {
f!(r#"(fold iterable i
({name} (null) (null))
)"#)
}
#[test]
fn parse_fold_with_xor_par_seq() {
for name in &["xor", "par", "seq"] {
let source_code = source_fold_with(name);
let instruction = parse(&source_code);
let instr = binary_instruction(*name);
let expected = fold_scalar(
ScalarWithLambda::new("iterable", None),
"i",
instr(null(), null()),
);
assert_eq!(instruction, expected);
}
}

View File

@ -0,0 +1,101 @@
/*
* 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::*;
#[test]
fn parse_match() {
let source_code = r#"
(match v1 v2
(null)
)
"#;
let instruction = parse(&source_code);
let expected = match_(
Value::Variable(VariableWithLambda::scalar("v1")),
Value::Variable(VariableWithLambda::scalar("v2")),
null(),
);
assert_eq!(instruction, expected);
}
#[test]
fn parse_match_with_init_peer_id() {
let source_code = r#"
(match v1 %init_peer_id%
(null)
)
"#;
let instruction = parse(&source_code);
let expected = match_(
Value::Variable(VariableWithLambda::scalar("v1")),
Value::InitPeerId,
null(),
);
assert_eq!(instruction, expected);
}
#[test]
fn parse_mismatch() {
let source_code = r#"
(mismatch v1 v2
(null)
)
"#;
let instruction = parse(&source_code);
let expected = mismatch(
Value::Variable(VariableWithLambda::scalar("v1")),
Value::Variable(VariableWithLambda::scalar("v2")),
null(),
);
assert_eq!(instruction, expected);
}
#[test]
fn match_with_bool() {
let source_code = r#"
(match isOnline true
(null)
)
"#;
let left_value = Value::Variable(VariableWithLambda::scalar("isOnline"));
let right_value = Value::Boolean(true);
let null = null();
let expected = match_(left_value, right_value, null);
let instruction = parse(source_code);
assert_eq!(expected, instruction);
}
#[test]
fn mismatch_with_bool() {
let source_code = r#"
(mismatch true isOnline
(null)
)
"#;
let left_value = Value::Boolean(true);
let right_value = Value::Variable(VariableWithLambda::scalar("isOnline"));
let null = null();
let expected = mismatch(left_value, right_value, null);
let instruction = parse(source_code);
assert_eq!(expected, instruction);
}

View File

@ -0,0 +1,43 @@
/*
* 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.
*/
mod ap;
mod call;
mod dsl;
mod fold;
mod match_;
mod null;
mod par;
mod seq;
use crate::ast::Instruction;
use crate::parser::AIRParser;
thread_local!(static TEST_PARSER: AIRParser = AIRParser::new());
fn parse(source_code: &str) -> Instruction {
*TEST_PARSER.with(|parser| {
let mut errors = Vec::new();
let lexer = crate::parser::AIRLexer::new(source_code);
let mut validator = crate::parser::VariableValidator::new();
let res = parser
.parse(source_code, &mut errors, &mut validator, lexer)
.expect("parsing should be successful");
println!("{:?}", errors);
res
})
}

View File

@ -0,0 +1,33 @@
/*
* 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::*;
#[test]
fn parse_null() {
let source_code = r#"
(seq
(null)
( null )
)
"#;
let instruction = parse(source_code);
let expected = Instruction::Seq(Seq(Box::new(null()), Box::new(null())));
assert_eq!(instruction, expected)
}

View File

@ -0,0 +1,90 @@
/*
* 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 std::rc::Rc;
#[test]
fn parse_seq() {
let source_code = r#"
(par
(call "" ("" "") [])
(call "" ("" "") [])
)
"#;
let instruction = parse(source_code);
let expected = par(
call(
CallInstrValue::Literal(""),
CallInstrValue::Literal(""),
CallInstrValue::Literal(""),
Rc::new(vec![]),
CallOutputValue::None,
),
call(
CallInstrValue::Literal(""),
CallInstrValue::Literal(""),
CallInstrValue::Literal(""),
Rc::new(vec![]),
CallOutputValue::None,
),
);
assert_eq!(instruction, expected);
}
#[test]
fn parse_par_par() {
let source_code = r#"
(par
(par
(call "" ("" "") [])
(call ("" "") ("" "") [])
)
(call "" ("" "") [])
)
"#;
let instruction = parse(source_code);
let expected = par(
par(
call(
CallInstrValue::Literal(""),
CallInstrValue::Literal(""),
CallInstrValue::Literal(""),
Rc::new(vec![]),
CallOutputValue::None,
),
call(
CallInstrValue::Literal(""),
CallInstrValue::Literal(""),
CallInstrValue::Literal(""),
Rc::new(vec![]),
CallOutputValue::None,
),
),
call(
CallInstrValue::Literal(""),
CallInstrValue::Literal(""),
CallInstrValue::Literal(""),
Rc::new(vec![]),
CallOutputValue::None,
),
);
assert_eq!(instruction, expected);
}

View File

@ -0,0 +1,120 @@
/*
* 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 std::rc::Rc;
#[test]
fn parse_seq() {
let source_code = r#"
(seq
(call peer_id (service_id function_name) [[] []] output)
(call "peer_id" ("service_id" "function_name") ["hello" [] name])
)
"#;
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")),
Rc::new(vec![Value::EmptyArray, Value::EmptyArray]),
CallOutputValue::Variable(Variable::scalar("output")),
),
call(
CallInstrValue::Literal("peer_id"),
CallInstrValue::Literal("service_id"),
CallInstrValue::Literal("function_name"),
Rc::new(vec![
Value::Literal("hello"),
Value::EmptyArray,
Value::Variable(VariableWithLambda::scalar("name")),
]),
CallOutputValue::None,
),
);
assert_eq!(instruction, expected);
}
#[test]
fn parse_seq_seq() {
let source_code = r#"
(seq
(seq
(call peer_id (service_id function_name) [])
(call (peer_id service_A) ("service_B" function_name) [])
)
(call "peer_id" ("service_id" "function_name") ["hello" name] $output)
)
"#;
let instruction = parse(source_code);
let expected = seq(
seq(
call(
CallInstrValue::Variable(VariableWithLambda::scalar("peer_id")),
CallInstrValue::Variable(VariableWithLambda::scalar("service_id")),
CallInstrValue::Variable(VariableWithLambda::scalar("function_name")),
Rc::new(vec![]),
CallOutputValue::None,
),
call(
CallInstrValue::Variable(VariableWithLambda::scalar("peer_id")),
CallInstrValue::Literal("service_B"),
CallInstrValue::Variable(VariableWithLambda::scalar("function_name")),
Rc::new(vec![]),
CallOutputValue::None,
),
),
call(
CallInstrValue::Literal("peer_id"),
CallInstrValue::Literal("service_id"),
CallInstrValue::Literal("function_name"),
Rc::new(vec![
Value::Literal("hello"),
Value::Variable(VariableWithLambda::scalar("name")),
]),
CallOutputValue::Variable(Variable::stream("$output")),
),
);
assert_eq!(instruction, expected);
}
fn source_seq_with(name: &'static str) -> String {
f!(r#"
(seq
({name}
(seq (null) (null))
(null)
)
({name} (null) (seq (null) (null)) )
)
"#)
}
#[test]
fn parse_seq_par_xor_seq() {
for name in &["xor", "par", "seq"] {
let source_code = source_seq_with(name);
let instruction = parse(&source_code);
let instr = binary_instruction(*name);
let expected = seq(instr(seqnn(), null()), instr(null(), seqnn()));
assert_eq!(instruction, expected);
}
}

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
use super::ast::*;
use crate::ast::*;
use crate::parser::lexer::Token;
use crate::parser::ParserError;
@ -55,9 +55,11 @@ impl<'i> VariableValidator<'i> {
}
pub(super) fn met_call(&mut self, call: &Call<'i>, span: Span) {
self.met_peer_part(&call.peer_part, span);
self.met_function_part(&call.function_part, span);
self.met_args(&call.args, span);
self.met_call_instr_value(&call.triplet.peer_pk, span);
self.met_call_instr_value(&call.triplet.service_id, span);
self.met_call_instr_value(&call.triplet.function_name, span);
self.met_args(call.args.deref(), span);
match &call.output {
CallOutputValue::Variable(variable) => self.met_variable_definition(variable, span),
@ -76,32 +78,34 @@ impl<'i> VariableValidator<'i> {
}
pub(super) fn met_fold_scalar(&mut self, fold: &FoldScalar<'i>, span: Span) {
self.met_iterable_value(&fold.iterable, span);
self.met_iterator_definition(fold.iterator, span);
self.met_variable_name(fold.iterable.name, span);
self.met_iterator_definition(&fold.iterator, span);
}
pub(super) fn meet_fold_stream(&mut self, fold: &FoldStream<'i>, span: Span) {
self.met_variable(&AstVariable::Stream(fold.stream_name), span);
self.met_iterator_definition(fold.iterator, span);
self.met_variable_name(fold.iterable.name, span);
self.met_iterator_definition(&fold.iterator, span);
}
pub(super) fn met_next(&mut self, next: &Next<'i>, span: Span) {
let iterable_name = next.0;
// due to the right to left convolution in lalrpop, next will be met earlier than
// a corresponding fold with the definition of this iterable, so they're just put
// without a check for being already met
let iterable_name = next.iterator.name;
// due to the right to left convolution in lalrpop, a next instruction will be met earlier
// than a corresponding fold instruction with the definition of this iterable, so they're
// just put without a check for being already met
self.unresolved_iterables.insert(iterable_name, span);
}
pub(super) fn met_ap(&mut self, ap: &Ap<'i>, span: Span) {
match &ap.argument {
ApArgument::ScalarVariable(name) => self.met_variable(&AstVariable::Scalar(name), span),
ApArgument::VariableWithLambda(vl) => self.met_variable(&vl.variable, span),
ApArgument::Number(_)
| ApArgument::InitPeerId
| ApArgument::Boolean(_)
| ApArgument::Literal(_)
| ApArgument::EmptyArray
| ApArgument::LastError(_) => {}
ApArgument::Scalar(scalar) => {
self.met_variable_wl(&VariableWithLambda::Scalar(scalar.clone()), span)
}
}
self.met_variable_definition(&ap.result, span);
}
@ -123,59 +127,37 @@ impl<'i> VariableValidator<'i> {
errors
}
fn met_peer_part(&mut self, peer_part: &PeerPart<'i>, span: Span) {
match peer_part {
PeerPart::PeerPk(peer_pk) => self.met_instr_value(peer_pk, span),
PeerPart::PeerPkWithServiceId(peer_pk, service_id) => {
self.met_instr_value(peer_pk, span);
self.met_instr_value(service_id, span);
}
}
}
fn met_function_part(&mut self, function_part: &FunctionPart<'i>, span: Span) {
match function_part {
FunctionPart::FuncName(func_name) => self.met_instr_value(func_name, span),
FunctionPart::ServiceIdWithFuncName(service_id, func_name) => {
self.met_instr_value(service_id, span);
self.met_instr_value(func_name, span);
}
}
}
fn met_args(&mut self, args: &[CallInstrArgValue<'i>], span: Span) {
fn met_args(&mut self, args: &[Value<'i>], span: Span) {
for arg in args {
self.met_instr_arg_value(arg, span);
}
}
fn met_instr_value(&mut self, instr_value: &CallInstrValue<'i>, span: Span) {
match instr_value {
CallInstrValue::VariableWithLambda(vl) => self.met_variable(&vl.variable, span),
CallInstrValue::Variable(variable) => self.met_variable(variable, span),
_ => {}
fn met_call_instr_value(&mut self, instr_value: &CallInstrValue<'i>, span: Span) {
if let CallInstrValue::Variable(variable) = instr_value {
self.met_variable_wl(variable, span);
}
}
fn met_instr_arg_value(&mut self, instr_arg_value: &CallInstrArgValue<'i>, span: Span) {
match instr_arg_value {
CallInstrArgValue::VariableWithLambda(vl) => self.met_variable(&vl.variable, span),
CallInstrArgValue::Variable(variable) => {
// skipping streams here allows treating non-defined streams as empty arrays
if let AstVariable::Scalar(_) = variable {
self.met_variable(variable, span)
fn met_instr_arg_value(&mut self, instr_arg_value: &Value<'i>, span: Span) {
if let Value::Variable(variable) = instr_arg_value {
// skipping streams without lambdas here allows treating non-defined streams as empty arrays
if let VariableWithLambda::Stream(stream) = variable {
if stream.lambda.is_none() {
return;
}
}
_ => {}
self.met_variable_wl(variable, span);
}
}
fn met_variable(&mut self, variable: &AstVariable<'i>, span: Span) {
let name = match variable {
AstVariable::Scalar(name) => name,
AstVariable::Stream(name) => name,
};
fn met_variable_wl(&mut self, variable: &VariableWithLambda<'i>, span: Span) {
let name = variable_wl_name(variable);
self.met_variable_name(name, span);
}
fn met_variable_name(&mut self, name: &'i str, span: Span) {
if !self.contains_variable(name, span) {
self.unresolved_variables.insert(name, span);
}
@ -196,15 +178,15 @@ impl<'i> VariableValidator<'i> {
found_spans.iter().any(|s| s < &key_span)
}
fn met_variable_definition(&mut self, variable: &AstVariable<'i>, span: Span) {
fn met_variable_definition(&mut self, variable: &Variable<'i>, span: Span) {
let name = variable_name(variable);
self.met_variable_name_definition(name, span);
}
fn met_variable_name_definition(&mut self, name: &'i str, span: Span) {
use std::collections::hash_map::Entry;
let variable_name = match variable {
AstVariable::Scalar(name) => name,
AstVariable::Stream(name) => name,
};
match self.met_variables.entry(variable_name) {
match self.met_variables.entry(name) {
Entry::Occupied(occupied) => {
if occupied.get() > &span {
*occupied.into_mut() = span;
@ -216,15 +198,15 @@ impl<'i> VariableValidator<'i> {
}
}
fn met_matchable(&mut self, matchable: &MatchableValue<'i>, span: Span) {
fn met_matchable(&mut self, matchable: &Value<'i>, span: Span) {
match matchable {
MatchableValue::InitPeerId
| MatchableValue::Number(_)
| MatchableValue::Boolean(_)
| MatchableValue::Literal(_)
| MatchableValue::EmptyArray => {}
MatchableValue::Variable(variable) => self.met_variable(variable, span),
MatchableValue::VariableWithLambda(vl) => self.met_variable(&vl.variable, span),
Value::InitPeerId
| Value::Number(_)
| Value::Boolean(_)
| Value::Literal(_)
| Value::LastError(_)
| Value::EmptyArray => {}
Value::Variable(variable) => self.met_variable_wl(variable, span),
}
}
@ -240,23 +222,14 @@ impl<'i> VariableValidator<'i> {
.any(|s| s.left < key_span.left && s.right > key_span.right)
}
fn met_iterable_value(&mut self, iterable_value: &IterableScalarValue<'i>, span: Span) {
match iterable_value {
IterableScalarValue::VariableWithLambda { scalar_name, .. } => {
self.met_variable(&AstVariable::Scalar(scalar_name), span)
}
IterableScalarValue::ScalarVariable(name) => {
self.met_variable(&AstVariable::Scalar(name), span)
}
}
}
fn met_iterator_definition(&mut self, iterator: &'i str, span: Span) {
self.met_iterators.insert(iterator, span);
fn met_iterator_definition(&mut self, iterator: &Scalar<'i>, span: Span) {
self.met_iterators.insert(iterator.name, span);
}
}
use std::cmp::Ordering;
use std::ops::Deref;
impl PartialOrd for Span {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
let self_min = std::cmp::min(self.left, self.right);
@ -294,3 +267,17 @@ fn add_to_errors<'err, 'i>(
errors.push(error);
}
fn variable_name<'v>(variable: &Variable<'v>) -> &'v str {
match variable {
Variable::Scalar(scalar) => scalar.name,
Variable::Stream(stream) => stream.name,
}
}
fn variable_wl_name<'v>(variable: &VariableWithLambda<'v>) -> &'v str {
match variable {
VariableWithLambda::Scalar(scalar) => scalar.name,
VariableWithLambda::Stream(stream) => stream.name,
}
}

View File

@ -1,6 +1,6 @@
[package]
name = "air-lambda-ast"
description = "Parser of AIR value algebra values"
description = "Definition of the AIR lambda AST"
version = "0.1.0"
authors = ["Fluence Labs"]
edition = "2018"

View File

@ -99,10 +99,10 @@ pub(crate) enum ValueType<'i> {
impl<'i> ValueType<'i> {
pub(self) fn from_output_value(output_value: &'i CallOutputValue<'_>) -> Self {
use air_parser::ast::AstVariable;
use air_parser::ast::Variable;
match output_value {
CallOutputValue::Variable(AstVariable::Stream(stream_name)) => ValueType::Stream(stream_name),
CallOutputValue::Variable(Variable::Stream(stream)) => ValueType::Stream(stream.name),
_ => ValueType::Scalar,
}
}