mirror of
https://github.com/fluencelabs/aquavm
synced 2024-12-04 15:20:16 +00:00
Introduce fail instruction (#196)
This commit is contained in:
parent
5ef8bfc940
commit
8dbae91bda
@ -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
4
Cargo.lock
generated
@ -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",
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
90
air/src/execution_step/air/fail.rs
Normal file
90
air/src/execution_step/air/fail.rs
Normal 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;
|
||||
}
|
@ -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),
|
||||
|
@ -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),
|
||||
|
108
air/tests/test_module/instructions/fail.rs
Normal file
108
air/tests/test_module/instructions/fail.rs
Normal 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)
|
||||
);
|
||||
}
|
@ -16,6 +16,7 @@
|
||||
|
||||
mod ap;
|
||||
mod call;
|
||||
mod fail;
|
||||
mod fold;
|
||||
mod match_;
|
||||
mod mismatch;
|
||||
|
@ -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'"
|
||||
))
|
||||
);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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),
|
||||
}
|
||||
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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> {
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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
@ -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())
|
||||
}
|
||||
|
@ -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),
|
||||
|
@ -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";
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>;
|
||||
|
@ -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(),
|
||||
))),
|
||||
);
|
||||
}
|
||||
|
@ -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),
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
|
38
crates/air-lib/air-parser/src/parser/tests/fail.rs
Normal file
38
crates/air-lib/air-parser/src/parser/tests/fail.rs
Normal 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)
|
||||
}
|
@ -17,6 +17,7 @@
|
||||
mod ap;
|
||||
mod call;
|
||||
mod dsl;
|
||||
mod fail;
|
||||
mod fold;
|
||||
mod match_;
|
||||
mod new;
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user