Introduce fail instruction (#196)

This commit is contained in:
Mike Voronov 2021-12-16 21:34:27 +03:00 committed by GitHub
parent 5ef8bfc940
commit 8dbae91bda
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 2017 additions and 1312 deletions

View File

@ -1,3 +1,11 @@
## Version 0.19.0 (2021-12-15)
[PR 196](https://github.com/fluencelabs/aquavm/pull/196):
Introduced fail instruction.
[PR 194](https://github.com/fluencelabs/aquavm/pull/194):
Added variables names in resolve errors.
## Version 0.18.0 (2021-12-14)
[PR 192](https://github.com/fluencelabs/aquavm/pull/172):

4
Cargo.lock generated
View File

@ -13,7 +13,7 @@ dependencies = [
[[package]]
name = "air"
version = "0.18.0"
version = "0.19.0"
dependencies = [
"air-execution-info-collector",
"air-interpreter-data",
@ -50,7 +50,7 @@ version = "0.1.0"
[[package]]
name = "air-interpreter"
version = "0.18.0"
version = "0.19.0"
dependencies = [
"air",
"air-log-targets",

View File

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

View File

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

View File

@ -0,0 +1,90 @@
/*
* 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::ExecutionCtx;
use super::ExecutionError;
use super::ExecutionResult;
use super::LastErrorDescriptor;
use super::TraceHandler;
use crate::log_instruction;
use air_parser::ast::Fail;
use polyplets::SecurityTetraplet;
use std::cell::RefCell;
use std::rc::Rc;
impl<'i> super::ExecutableInstruction<'i> for Fail<'i> {
fn execute(&self, exec_ctx: &mut ExecutionCtx<'i>, trace_ctx: &mut TraceHandler) -> ExecutionResult<()> {
log_instruction!(fail, exec_ctx, trace_ctx);
match self {
&Fail::Literal {
ret_code,
error_message,
} => fail_with_literals(ret_code, error_message, self, exec_ctx),
// bubble last error up
Fail::LastError => fail_with_last_error(exec_ctx),
}
}
}
fn fail_with_literals<'i>(
ret_code: i64,
error_message: &str,
fail: &Fail<'_>,
exec_ctx: &mut ExecutionCtx<'i>,
) -> ExecutionResult<()> {
let fail_error = ExecutionError::FailWithoutXorError {
ret_code,
error_message: error_message.to_string(),
};
let fail_error = Rc::new(fail_error);
let instruction = fail.to_string();
// TODO: wrap exec.init_peer_id in Rc
let literal_tetraplet = SecurityTetraplet::literal_tetraplet(exec_ctx.init_peer_id.clone());
let literal_tetraplet = Rc::new(RefCell::new(literal_tetraplet));
let last_error = LastErrorDescriptor::new(
fail_error.clone(),
instruction,
// init_peer_id is used here in order to obtain determinism,
// so %last_error%.peer_id will produce the same result on each peer
exec_ctx.init_peer_id.clone(),
Some(literal_tetraplet),
);
exec_ctx.last_error = Some(last_error);
update_context_state(exec_ctx);
Err(fail_error)
}
fn fail_with_last_error(exec_ctx: &mut ExecutionCtx<'_>) -> ExecutionResult<()> {
let last_error = match &exec_ctx.last_error {
Some(last_error) => last_error.error.clone(),
None => return Ok(()),
};
update_context_state(exec_ctx);
Err(last_error)
}
fn update_context_state(exec_ctx: &mut ExecutionCtx<'_>) {
exec_ctx.subtree_complete = false;
exec_ctx.last_error_could_be_set = false;
}

View File

@ -17,6 +17,7 @@
mod ap;
mod call;
mod compare_matchable;
mod fail;
mod fold;
mod fold_scalar;
mod fold_stream;
@ -102,6 +103,7 @@ impl<'i> ExecutableInstruction<'i> for Instruction<'i> {
Instruction::Call(call) => call.execute(exec_ctx, trace_ctx),
Instruction::Ap(ap) => execute!(self, ap, exec_ctx, trace_ctx),
Instruction::Fail(fail) => execute!(self, fail, exec_ctx, trace_ctx),
Instruction::FoldScalar(fold) => execute!(self, fold, exec_ctx, trace_ctx),
Instruction::FoldStream(fold) => execute!(self, fold, exec_ctx, trace_ctx),
Instruction::New(new) => execute!(self, new, exec_ctx, trace_ctx),

View File

@ -92,6 +92,10 @@ pub(crate) enum ExecutionError {
#[error("mismatch is used without corresponding xor")]
MismatchWithoutXorError,
/// This error type is produced by a fail instruction.
#[error("fail with ret_code '{ret_code}' and error_message '{error_message}' is used without corresponding xor")]
FailWithoutXorError { ret_code: i64, error_message: String },
/// Errors bubbled from a trace handler.
#[error(transparent)]
TraceError(#[from] TraceHandlerError),

View File

@ -0,0 +1,108 @@
/*
* 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 air_test_utils::prelude::*;
use fstrings::f;
use fstrings::format_args_f;
#[test]
fn fail_with_last_error() {
let local_peer_id = "local_peer_id";
let fallible_service_id = "service_id_1";
let mut vm = create_avm(fallible_call_service(fallible_service_id), local_peer_id);
let script = f!(r#"
(xor
(call "{local_peer_id}" ("service_id_1" "local_fn_name") [] result_1)
(fail %last_error%)
)"#);
let result = call_vm!(vm, "", script, "", "");
assert_eq!(result.ret_code, 1000);
assert_eq!(
result.error_message,
r#"Local service error, ret_code is 1, error message is '"failed result from fallible_call_service"'"#
);
}
#[test]
fn fail_with_literals() {
let local_peer_id = "local_peer_id";
let mut vm = create_avm(echo_call_service(), local_peer_id);
let script = format!(
r#"
(xor
(fail 1337 "error message")
(fail %last_error%)
)"#,
);
let result = call_vm!(vm, "", script, "", "");
assert_eq!(result.ret_code, 1012);
assert_eq!(
result.error_message,
"fail with ret_code '1337' and error_message 'error message' is used without corresponding xor"
);
}
#[test]
fn fail_with_last_error_tetraplets() {
let local_peer_id = "local_peer_id";
let fallible_service_id = "service_id_1";
let (host_closure, tetraplet_anchor) = tetraplet_host_function(fallible_call_service(fallible_service_id));
let mut vm = create_avm(host_closure, local_peer_id);
let local_fn_name = "local_fn_name";
let script = f!(r#"
(xor
(xor
(call "{local_peer_id}" ("{fallible_service_id}" "{local_fn_name}") [] result_1)
(fail %last_error%)
)
(call "{local_peer_id}" ("" "") [%last_error%])
)
"#);
let result = checked_call_vm!(vm, local_peer_id, script, "", "");
assert_eq!(
tetraplet_anchor.borrow()[0][0],
SecurityTetraplet::new(local_peer_id, fallible_service_id, local_fn_name, "")
);
}
#[test]
fn fail_with_literals_tetraplets() {
let local_peer_id = "local_peer_id";
let (host_closure, tetraplet_anchor) = tetraplet_host_function(echo_call_service());
let mut vm = create_avm(host_closure, local_peer_id);
let script = f!(r#"
(xor
(xor
(fail 1337 "error message")
(fail %last_error%)
)
(call "{local_peer_id}" ("" "") [%last_error%])
)"#);
let _ = checked_call_vm!(vm, local_peer_id, script, "", "");
assert_eq!(
tetraplet_anchor.borrow()[0][0],
SecurityTetraplet::literal_tetraplet(local_peer_id)
);
}

View File

@ -16,6 +16,7 @@
mod ap;
mod call;
mod fail;
mod fold;
mod match_;
mod mismatch;

View File

@ -222,7 +222,7 @@ fn variable_names_shown_in_error() {
assert_eq!(
trace[1],
executed_state::scalar(json!(
"expected JValue type 'string' for variable `-relay-`, but got '1'"
"expected JValue type 'string' for the variable `-relay-`, but got '1'"
))
);
}

View File

@ -141,7 +141,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, 1012);
assert_eq!(init_result_3.ret_code, 1013);
let actual_trace = trace_from_result(&init_result_3);
let expected_trace = trace_from_result(&init_result_2);

View File

@ -20,7 +20,6 @@ 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;
@ -86,3 +85,9 @@ pub enum ApArgument<'i> {
EmptyArray,
Scalar(ScalarWithLambda<'i>),
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub enum Number {
Int(i64),
Float(f64),
}

View File

@ -105,3 +105,29 @@ impl fmt::Display for Triplet<'_> {
)
}
}
impl fmt::Display for Number {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use Number::*;
match self {
Int(number) => write!(f, "{}", number),
Float(number) => write!(f, "{}", number),
}
}
}
impl From<Number> for serde_json::Value {
fn from(number: Number) -> Self {
(&number).into()
}
}
impl From<&Number> for serde_json::Value {
fn from(number: &Number) -> Self {
match number {
Number::Int(value) => (*value).into(),
Number::Float(value) => (*value).into(),
}
}
}

View File

@ -21,6 +21,7 @@ use super::*;
use serde::Serialize;
use std::rc::Rc;
// TODO: sort instruction in alphanumeric order
#[allow(clippy::large_enum_variant)] // for Null and Error variants
#[derive(Serialize, Debug, PartialEq)]
pub enum Instruction<'i> {
@ -31,6 +32,7 @@ pub enum Instruction<'i> {
Xor(Xor<'i>),
Match(Match<'i>),
MisMatch(MisMatch<'i>),
Fail(Fail<'i>),
FoldScalar(FoldScalar<'i>),
FoldStream(FoldStream<'i>),
New(New<'i>),
@ -82,6 +84,17 @@ pub struct MisMatch<'i> {
pub instruction: Box<Instruction<'i>>,
}
/// (fail 1337 "error message")
/// (fail %last_error%)
#[derive(Serialize, Debug, PartialEq)]
pub enum Fail<'i> {
Literal {
ret_code: i64,
error_message: &'i str,
},
LastError,
}
/// (fold scalar_iterable iterator instruction)
#[derive(Serialize, Debug, PartialEq)]
pub struct FoldScalar<'i> {

View File

@ -30,6 +30,7 @@ impl fmt::Display for Instruction<'_> {
Xor(xor) => write!(f, "{}", xor),
Match(match_) => write!(f, "{}", match_),
MisMatch(mismatch) => write!(f, "{}", mismatch),
Fail(fail) => write!(f, "{}", fail),
FoldScalar(fold) => write!(f, "{}", fold),
FoldStream(fold) => write!(f, "{}", fold),
Next(next) => write!(f, "{}", next),
@ -55,6 +56,18 @@ impl fmt::Display for Ap<'_> {
}
}
impl fmt::Display for Fail<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Fail::Literal {
ret_code,
error_message,
} => write!(f, "fail {} {}", ret_code, error_message),
Fail::LastError => write!(f, "fail %last_error%"),
}
}
}
impl fmt::Display for FoldScalar<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "fold {} {}", self.iterable, self.iterator)

View File

@ -23,5 +23,4 @@ pub use instructions::*;
pub use values::*;
pub use crate::parser::lexer::LastErrorPath;
pub use crate::parser::lexer::Number;
pub use crate::parser::Span;

View File

@ -4,7 +4,6 @@ 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;
@ -60,6 +59,10 @@ Instr: Box<Instruction<'input>> = {
Box::new(Instruction::New(new))
},
"(" fail <fail_body: FailBody> ")" => {
Box::new(Instruction::Fail(fail_body))
},
<left: @L> "(" fold <iterable:FoldScalarIterable> <iterator:Scalar> <i:Instr> ")" <right: @R> => {
let iterator = Scalar::new(iterator.0, iterator.1);
let span = Span::new(left, right);
@ -133,6 +136,20 @@ ScriptVariable: Variable<'input> = {
<stream:Stream> => Variable::stream(stream.0, stream.1),
};
FailBody: Fail<'input> = {
<ret_code:I64> <error_message:Literal> => Fail::Literal {
ret_code,
error_message,
},
<left: @L> <last_error_path:LastError> <right: @R> => {
// it's possible to write smth like that `(fail %last_error%.msg)`, that would be ambigious
if !matches!(last_error_path, LastErrorPath::None) {
errors.push(make_user_error!(AmbiguousFailLastError, left, Token::Fail, right));
}
Fail::LastError
}
}
FoldScalarIterable: ScalarWithLambda<'input> = {
<scalar:Scalar> => ScalarWithLambda::new(scalar.0, None, scalar.1),
<scalar:ScalarWithLambda> => ScalarWithLambda::new(scalar.0, Some(scalar.1), scalar.2),
@ -151,6 +168,11 @@ CallInstrValue: CallInstrValue<'input> = {
<stream:StreamWithLambda> => CallInstrValue::Variable(VariableWithLambda::stream_wl(stream.0, stream.1, stream.2)),
}
Number: Number = {
<integer:I64> => Number::Int(integer),
<float:F64> => Number::Float(float),
}
Arg = Value;
Value: Value<'input> = {
@ -187,12 +209,14 @@ extern {
"[" => Token::OpenSquareBracket,
"]" => Token::CloseSquareBracket,
Literal => Token::StringLiteral(<&'input str>),
Scalar => Token::Scalar { name:<&'input str>, position: <usize> },
ScalarWithLambda => Token::ScalarWithLambda { name: <&'input str>, lambda: <LambdaAST<'input>>, position: <usize> },
Stream => Token::Stream { name: <&'input str>, position: <usize> },
StreamWithLambda => Token::StreamWithLambda {name: <&'input str>, lambda:<LambdaAST<'input>>, position: <usize>},
Number => Token::Number(<Number>),
Literal => Token::StringLiteral(<&'input str>),
I64 => Token::I64(<i64>),
F64 => Token::F64(<f64>),
Boolean => Token::Boolean(<bool>),
InitPeerId => Token::InitPeerId,
@ -202,6 +226,7 @@ extern {
ap => Token::Ap,
seq => Token::Seq,
par => Token::Par,
fail => Token::Fail,
fold => Token::Fold,
xor => Token::Xor,
new => Token::New,

File diff suppressed because it is too large Load Diff

View File

@ -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())
}
AmbiguousFailLastError(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())
}

View File

@ -31,6 +31,9 @@ pub enum ParserError {
#[error("iterable '{2}' wasn't defined")]
UndefinedIterable(usize, usize, String),
#[error("last error with non-empty path is ambiguous, please use just %last_error%")]
AmbiguousFailLastError(usize, usize),
/// Semantic errors in a call instructions.
#[error("call should have service id specified by peer part or function part")]
InvalidCallTriplet(usize, usize),

View File

@ -180,6 +180,7 @@ fn string_to_token(input: &str, start_pos: usize) -> LexerResult<Token> {
AP_INSTR => Ok(Token::Ap),
SEQ_INSTR => Ok(Token::Seq),
PAR_INSTR => Ok(Token::Par),
FAIL_INSTR => Ok(Token::Fail),
FOLD_INSTR => Ok(Token::Fold),
XOR_INSTR => Ok(Token::Xor),
NEW_INSTR => Ok(Token::New),
@ -224,6 +225,7 @@ const CALL_INSTR: &str = "call";
const AP_INSTR: &str = "ap";
const SEQ_INSTR: &str = "seq";
const PAR_INSTR: &str = "par";
const FAIL_INSTR: &str = "fail";
const FOLD_INSTR: &str = "fold";
const XOR_INSTR: &str = "xor";
const NEW_INSTR: &str = "new";

View File

@ -19,7 +19,6 @@ use super::LexerResult;
use super::Token;
use crate::LambdaAST;
use std::convert::TryInto;
use std::iter::Peekable;
use std::str::CharIndices;
@ -318,25 +317,45 @@ impl<'input> CallVariableParser<'input> {
Ok(token)
}
fn to_token(&self) -> LexerResult<Token<'input>> {
use super::token::UnparsedNumber;
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()?;
number.into()
}
(true, Some(_)) => {
let number = UnparsedNumber::Float(self.string_to_parse, self.start_pos);
let number: super::Number = number.try_into()?;
number.into()
}
(false, None) => self.to_variable_token(self.string_to_parse),
(false, Some(lambda_start_pos)) => self.try_to_variable_and_lambda(lambda_start_pos)?,
};
fn try_to_i64(&self) -> LexerResult<Token<'input>> {
let raw_value = self.string_to_parse;
let number = raw_value.parse::<i64>().map_err(|e| {
let start_pos = self.start_pos;
LexerError::ParseIntError(start_pos, start_pos + raw_value.len(), e)
})?;
let token = Token::I64(number);
Ok(token)
}
fn try_to_f64(&self) -> LexerResult<Token<'input>> {
// safe threshold for floating-point numbers to obtain determinism
const SAFE_FLOAT_SIGNIFICAND_SIZE: usize = 11;
let raw_value = self.string_to_parse;
let start_pos = self.start_pos;
if raw_value.len() > SAFE_FLOAT_SIGNIFICAND_SIZE {
return Err(LexerError::TooBigFloat(
start_pos,
start_pos + raw_value.len(),
));
}
let number = raw_value
.parse::<f64>()
.map_err(|e| LexerError::ParseFloatError(start_pos, start_pos + raw_value.len(), e))?;
let token = Token::F64(number);
Ok(token)
}
fn to_token(&self) -> LexerResult<Token<'input>> {
let is_number = self.is_possible_to_parse_as_number();
match (is_number, self.state.first_dot_met_pos) {
(true, None) => self.try_to_i64(),
(true, Some(_)) => self.try_to_f64(),
(false, None) => Ok(self.to_variable_token(self.string_to_parse)),
(false, Some(lambda_start_pos)) => self.try_to_variable_and_lambda(lambda_start_pos),
}
}
}

View File

@ -26,7 +26,6 @@ mod tests;
pub use air_lexer::AIRLexer;
pub use errors::LexerError;
pub use token::LastErrorPath;
pub use token::Number;
pub use token::Token;
pub(super) type LexerResult<T> = std::result::Result<T, LexerError>;

View File

@ -18,7 +18,6 @@ use super::air_lexer::Spanned;
use super::AIRLexer;
use super::LastErrorPath;
use super::LexerError;
use super::Number;
use super::Token;
use air_lambda_parser::{LambdaAST, ValueAccessor};
@ -99,6 +98,8 @@ fn air_instructions() {
]),
);
lexer_test("fail", Single(Ok((0, Token::Fail, 4))));
lexer_test("fold", Single(Ok((0, Token::Fold, 4))));
lexer_test(
@ -187,64 +188,66 @@ fn string_literal() {
#[test]
fn integer_numbers() {
const NUMBER_WITH_PLUS_SIGN: &str = "+123";
let number = Number::Int(123);
let test_integer = 123;
let number_with_plus_sign = format!("+{}", test_integer);
lexer_test(
NUMBER_WITH_PLUS_SIGN,
&number_with_plus_sign,
Single(Ok((
0,
Token::Number(number.clone()),
NUMBER_WITH_PLUS_SIGN.len(),
Token::I64(test_integer),
number_with_plus_sign.len(),
))),
);
const NUMBER: &str = "123";
let number = format!("{}", test_integer);
lexer_test(
NUMBER,
Single(Ok((0, Token::Number(number.clone()), NUMBER.len()))),
&number,
Single(Ok((0, Token::I64(test_integer), number.len()))),
);
const NUMBER_WITH_MINUS_SIGN: &str = "-123";
let number = Number::Int(-123);
let number_with_minus_sign = format!("-{}", test_integer);
lexer_test(
NUMBER_WITH_MINUS_SIGN,
Single(Ok((0, Token::Number(number), NUMBER_WITH_MINUS_SIGN.len()))),
&number_with_minus_sign,
Single(Ok((
0,
Token::I64(-test_integer),
number_with_minus_sign.len(),
))),
);
}
#[test]
fn float_number() {
const FNUMBER_WITH_PLUS_SIGN: &str = "+123.123";
let number = Number::Float(123.123);
let test_float = 123.123;
let float_number_with_plus_sign = format!("+{}", test_float);
lexer_test(
FNUMBER_WITH_PLUS_SIGN,
&float_number_with_plus_sign,
Single(Ok((
0,
Token::Number(number.clone()),
FNUMBER_WITH_PLUS_SIGN.len(),
Token::F64(test_float),
float_number_with_plus_sign.len(),
))),
);
const FNUMBER: &str = "123.123";
let float_number = format!("{}", test_float);
lexer_test(
FNUMBER,
Single(Ok((0, Token::Number(number), FNUMBER.len()))),
&float_number,
Single(Ok((0, Token::F64(test_float), float_number.len()))),
);
const FNUMBER_WITH_MINUS_SIGN: &str = "-123.123";
let number = Number::Float(-123.123);
let float_number_with_minus_sign = format!("-{}", test_float);
lexer_test(
FNUMBER_WITH_MINUS_SIGN,
&float_number_with_minus_sign,
Single(Ok((
0,
Token::Number(number),
FNUMBER_WITH_MINUS_SIGN.len(),
Token::F64(-test_float),
float_number_with_minus_sign.len(),
))),
);
}

View File

@ -16,8 +16,6 @@
mod traits;
use super::LexerError;
use super::LexerResult;
use crate::LambdaAST;
use serde::Deserialize;
@ -30,7 +28,6 @@ pub enum Token<'input> {
OpenSquareBracket,
CloseSquareBracket,
StringLiteral(&'input str),
Scalar {
name: &'input str,
position: usize,
@ -49,7 +46,10 @@ pub enum Token<'input> {
lambda: LambdaAST<'input>,
position: usize,
},
Number(Number),
StringLiteral(&'input str),
I64(i64),
F64(f64),
Boolean(bool),
InitPeerId,
@ -59,6 +59,7 @@ pub enum Token<'input> {
Ap,
Seq,
Par,
Fail,
Fold,
Xor,
New,
@ -79,15 +80,3 @@ pub enum LastErrorPath {
// %last_error%
None,
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub enum Number {
Int(i64),
Float(f64),
}
pub(crate) enum UnparsedNumber<'input> {
// raw value and starting pos
Int(&'input str, usize),
Float(&'input str, usize),
}

View File

@ -16,7 +16,6 @@
use super::*;
use std::convert::TryFrom;
use std::fmt;
impl fmt::Display for LastErrorPath {
@ -31,68 +30,3 @@ impl fmt::Display for LastErrorPath {
}
}
}
impl fmt::Display for Number {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use Number::*;
match self {
Int(number) => write!(f, "{}", number),
Float(number) => write!(f, "{}", number),
}
}
}
impl From<Number> for Token<'_> {
fn from(value: Number) -> Self {
Token::Number(value)
}
}
impl From<Number> for serde_json::Value {
fn from(number: Number) -> Self {
(&number).into()
}
}
impl From<&Number> for serde_json::Value {
fn from(number: &Number) -> Self {
match number {
Number::Int(value) => (*value).into(),
Number::Float(value) => (*value).into(),
}
}
}
impl TryFrom<UnparsedNumber<'_>> for Number {
type Error = LexerError;
fn try_from(value: UnparsedNumber<'_>) -> LexerResult<Number> {
match value {
UnparsedNumber::Int(raw_value, start_pos) => {
let number = raw_value.parse::<i64>().map_err(|e| {
LexerError::ParseIntError(start_pos, start_pos + raw_value.len(), e)
})?;
let number = Self::Int(number);
Ok(number)
}
UnparsedNumber::Float(raw_value, start_pos) => {
if raw_value.len() > 11 {
return Err(LexerError::TooBigFloat(
start_pos,
start_pos + raw_value.len(),
));
}
let number = raw_value.parse::<f64>().map_err(|e| {
LexerError::ParseFloatError(start_pos, start_pos + raw_value.len(), e)
})?;
let number = Self::Float(number);
Ok(number)
}
}
}
}

View File

@ -37,15 +37,15 @@ pub(super) fn call<'i>(
})
}
pub(super) fn seq<'a>(l: Instruction<'a>, r: Instruction<'a>) -> Instruction<'a> {
pub(super) fn seq<'i>(l: Instruction<'i>, r: Instruction<'i>) -> Instruction<'i> {
Instruction::Seq(Seq(Box::new(l), Box::new(r)))
}
pub(super) fn par<'a>(l: Instruction<'a>, r: Instruction<'a>) -> Instruction<'a> {
pub(super) fn par<'i>(l: Instruction<'i>, r: Instruction<'i>) -> Instruction<'i> {
Instruction::Par(Par(Box::new(l), Box::new(r)))
}
pub(super) fn xor<'a>(l: Instruction<'a>, r: Instruction<'a>) -> Instruction<'a> {
pub(super) fn xor<'i>(l: Instruction<'i>, r: Instruction<'i>) -> Instruction<'i> {
Instruction::Xor(Xor(Box::new(l), Box::new(r)))
}
@ -69,12 +69,23 @@ pub(super) fn null() -> Instruction<'static> {
Instruction::Null(Null)
}
pub(super) fn fold_scalar<'a>(
iterable: ScalarWithLambda<'a>,
iterator: Scalar<'a>,
instruction: Instruction<'a>,
pub(super) fn fail_literals(ret_code: i64, error_message: &str) -> Instruction<'_> {
Instruction::Fail(Fail::Literal {
ret_code,
error_message,
})
}
pub(super) fn fail_last_error() -> Instruction<'static> {
Instruction::Fail(Fail::LastError)
}
pub(super) fn fold_scalar<'i>(
iterable: ScalarWithLambda<'i>,
iterator: Scalar<'i>,
instruction: Instruction<'i>,
span: Span,
) -> Instruction<'a> {
) -> Instruction<'i> {
Instruction::FoldScalar(FoldScalar {
iterable,
iterator,
@ -83,12 +94,12 @@ pub(super) fn fold_scalar<'a>(
})
}
pub(super) fn fold_stream<'a>(
iterable: Stream<'a>,
iterator: Scalar<'a>,
instruction: Instruction<'a>,
pub(super) fn fold_stream<'i>(
iterable: Stream<'i>,
iterator: Scalar<'i>,
instruction: Instruction<'i>,
span: Span,
) -> Instruction<'a> {
) -> Instruction<'i> {
Instruction::FoldStream(FoldStream {
iterable,
iterator,
@ -97,11 +108,11 @@ pub(super) fn fold_stream<'a>(
})
}
pub(super) fn match_<'a>(
left_value: Value<'a>,
right_value: Value<'a>,
instruction: Instruction<'a>,
) -> Instruction<'a> {
pub(super) fn match_<'i>(
left_value: Value<'i>,
right_value: Value<'i>,
instruction: Instruction<'i>,
) -> Instruction<'i> {
Instruction::Match(Match {
left_value,
right_value,
@ -109,11 +120,11 @@ pub(super) fn match_<'a>(
})
}
pub(super) fn mismatch<'a>(
left_value: Value<'a>,
right_value: Value<'a>,
instruction: Instruction<'a>,
) -> Instruction<'a> {
pub(super) fn mismatch<'i>(
left_value: Value<'i>,
right_value: Value<'i>,
instruction: Instruction<'i>,
) -> Instruction<'i> {
Instruction::MisMatch(MisMatch {
left_value,
right_value,
@ -125,8 +136,8 @@ pub(super) fn ap<'i>(argument: ApArgument<'i>, result: Variable<'i>) -> Instruct
Instruction::Ap(Ap { argument, result })
}
pub(super) fn binary_instruction<'a, 'b>(
name: &'a str,
pub(super) fn binary_instruction<'i, 'b>(
name: &'i str,
) -> impl Fn(Instruction<'b>, Instruction<'b>) -> Instruction<'b> {
match name {
"xor" => |l, r| xor(l, r),

View File

@ -0,0 +1,38 @@
/*
* 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;
#[test]
fn parse_fail_last_error() {
let source_code = r#"
(fail %last_error%)
"#;
let instruction = parse(source_code);
let expected = fail_last_error();
assert_eq!(instruction, expected)
}
#[test]
fn parse_fail_literals() {
let source_code = r#"
(fail 1 "error message")
"#;
let instruction = parse(source_code);
let expected = fail_literals(1, "error message");
assert_eq!(instruction, expected)
}

View File

@ -17,6 +17,7 @@
mod ap;
mod call;
mod dsl;
mod fail;
mod fold;
mod match_;
mod new;

View File

@ -16,7 +16,6 @@
use super::dsl::*;
use super::parse;
use crate::ast::*;
#[test]
fn parse_null() {
@ -28,6 +27,6 @@ fn parse_null() {
)
"#;
let instruction = parse(source_code);
let expected = Instruction::Seq(Seq(Box::new(null()), Box::new(null())));
let expected = seq(null(), null());
assert_eq!(instruction, expected)
}

View File

@ -55,10 +55,10 @@ impl SecurityTetraplet {
/// Create a tetraplet for string literals defined in the script
/// such as variable here `(call ("" "") "" ["variable_1"])`.
pub fn literal_tetraplet(init_peer_id: String) -> Self {
pub fn literal_tetraplet(init_peer_id: impl Into<String>) -> Self {
Self {
// these variables represent the initiator peer
peer_pk: init_peer_id,
peer_pk: init_peer_id.into(),
service_id: String::new(),
function_name: String::new(),
// json path can't be applied to the string literals

View File

@ -17,7 +17,10 @@
use super::*;
use serde_json::json;
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
pub fn unit_call_service() -> CallServiceClosure {
Box::new(|_| -> CallServiceResult {
@ -85,3 +88,19 @@ pub fn fallible_call_service(fallible_service_id: impl Into<String>) -> CallServ
}
})
}
pub type ArgTetraplets = Vec<Vec<SecurityTetraplet>>;
pub fn tetraplet_host_function(
closure: impl Fn(CallRequestParams) -> CallServiceResult + 'static,
) -> (CallServiceClosure, Rc<RefCell<ArgTetraplets>>) {
let arg_tetraplets = Rc::new(RefCell::new(ArgTetraplets::new()));
let arg_tetraplets_inner = arg_tetraplets.clone();
let host_function: CallServiceClosure = Box::new(move |params| -> CallServiceResult {
*arg_tetraplets_inner.borrow_mut() = params.tetraplets.clone();
closure(params)
});
(host_function, arg_tetraplets)
}