feat(execution-engine): a new :error: runtime attribute according with FLIP-11 [fixes VM-329] (#683)

* feat(execution-engine): a new :error: runtime attribute according with FLIP-11 [fixes VM-329]
This commit is contained in:
raftedproc 2023-09-04 22:57:51 +03:00 committed by GitHub
parent 2fc1686e19
commit 20afb79e3f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 2165 additions and 1447 deletions

View File

@ -14,8 +14,8 @@
* limitations under the License.
*/
use super::ErrorAffectable;
use super::Joinable;
use super::LastErrorAffectable;
use crate::execution_step::execution_context::errors::StreamMapError;
use crate::execution_step::execution_context::LastErrorObjectError;
use crate::execution_step::lambda_applier::LambdaError;
@ -40,11 +40,11 @@ pub enum CatchableError {
LocalServiceError(i32, Rc<String>),
/// This error type is produced by a match to notify xor that compared values aren't equal.
#[error("match is used without corresponding xor")]
#[error("compared values do not match")]
MatchValuesNotEqual,
/// This error type is produced by a mismatch to notify xor that compared values aren't equal.
#[error("mismatch is used without corresponding xor")]
#[error("compared values do not mismatch")]
MismatchValuesEqual,
/// Variable with such a name wasn't defined during AIR script execution.
@ -119,13 +119,17 @@ impl ToErrorCode for CatchableError {
}
}
impl LastErrorAffectable for CatchableError {
impl ErrorAffectable for CatchableError {
fn affects_last_error(&self) -> bool {
!matches!(
self,
CatchableError::MatchValuesNotEqual | CatchableError::MismatchValuesEqual
)
}
fn affects_error(&self) -> bool {
true
}
}
macro_rules! log_join {

View File

@ -14,9 +14,10 @@
* limitations under the License.
*/
/// This trait is intended to figuring out whether a last error should be set or not.
pub(crate) trait LastErrorAffectable {
/// This trait controls whether to set %last_error% and :error: or not.
pub(crate) trait ErrorAffectable {
/// Return true, if this error type affects last error
/// (meaning that it should be set after occurring such an error).
fn affects_last_error(&self) -> bool;
fn affects_error(&self) -> bool;
}

View File

@ -15,8 +15,8 @@
*/
use super::CatchableError;
use super::ErrorAffectable;
use super::Joinable;
use super::LastErrorAffectable;
use super::UncatchableError;
use crate::ToErrorCode;
@ -91,11 +91,18 @@ impl Joinable for ExecutionError {
}
}
impl LastErrorAffectable for ExecutionError {
impl ErrorAffectable for ExecutionError {
fn affects_last_error(&self) -> bool {
match self {
ExecutionError::Catchable(err) => err.affects_last_error(),
ExecutionError::Uncatchable(_) => false,
}
}
fn affects_error(&self) -> bool {
match self {
ExecutionError::Catchable(_) => true,
ExecutionError::Uncatchable(_) => false,
}
}
}

View File

@ -15,16 +15,16 @@
*/
mod catchable_errors;
mod error_effectable;
mod execution_errors;
mod joinable;
mod last_error_affectable;
mod uncatchable_errors;
pub use catchable_errors::CatchableError;
pub use execution_errors::ExecutionError;
pub use uncatchable_errors::UncatchableError;
pub(crate) use error_effectable::ErrorAffectable;
pub(crate) use joinable::Joinable;
pub(crate) use last_error_affectable::LastErrorAffectable;
use super::Stream;

View File

@ -14,12 +14,16 @@
* limitations under the License.
*/
use super::ErrorDescriptor;
use super::ExecutionCidState;
use super::LastError;
use super::InstructionError;
use super::LastErrorDescriptor;
use super::Scalars;
use super::StreamMaps;
use super::Streams;
use crate::execution_step::ErrorAffectable;
use crate::execution_step::RcSecurityTetraplet;
use crate::ToErrorCode;
use air_execution_info_collector::InstructionTracker;
use air_interpreter_cid::CID;
@ -51,9 +55,12 @@ pub(crate) struct ExecutionCtx<'i> {
pub(crate) run_parameters: RcRunParameters,
/// Last error produced by local service.
/// None means that there weren't any error.
/// There is the special not-an-error value means there was no error.
pub(crate) last_error_descriptor: LastErrorDescriptor,
/// Error produced by some instructions, e.g. call, match, fail.
pub(crate) error_descriptor: ErrorDescriptor,
/// Indicates that previous executed subgraph is complete.
/// A subgraph treats as a complete if all subgraph elements satisfy the following rules:
/// - at least one of par subgraphs is completed
@ -116,8 +123,12 @@ impl<'i> ExecutionCtx<'i> {
}
}
pub(crate) fn last_error(&self) -> &LastError {
self.last_error_descriptor.last_error()
pub(crate) fn last_error(&self) -> &InstructionError {
self.last_error_descriptor.error()
}
pub(crate) fn error(&self) -> &InstructionError {
self.error_descriptor.error()
}
pub(crate) fn next_call_request_id(&mut self) -> u32 {
@ -150,6 +161,41 @@ impl ExecutionCtx<'_> {
pub(crate) fn flush_subgraph_completeness(&mut self) {
self.subgraph_completeness = true;
}
// This routine sets %last_error% and :error:.
// Most instructions, except Call, Canon, CanonMapScalar does not set :error:.$.peer_id b/c
// it would be a non-deterministic peer_id.
pub(crate) fn set_errors(
&mut self,
error: &(impl ErrorAffectable + ToErrorCode + ToString),
instruction: &str,
tetraplet: Option<RcSecurityTetraplet>,
use_tetraplet_and_log_peer_id: bool,
) {
let last_error_peer_id = match &tetraplet {
// use tetraplet if they set, because an error could be propagated from data
// (from CallServiceFailed state) and exec_ctx.run_parameters.current_peer_id won't mean
// a peer where the error was occurred
Some(tetraplet) if use_tetraplet_and_log_peer_id => Some(tetraplet.peer_pk.as_str()),
_ => Some(self.run_parameters.current_peer_id.as_str()),
};
self.last_error_descriptor.try_to_set_last_error_from_exec_error(
error,
instruction,
last_error_peer_id,
tetraplet.clone(),
);
let peer_id = if use_tetraplet_and_log_peer_id {
last_error_peer_id
} else {
None
};
self.error_descriptor
.try_to_set_error_from_exec_error(error, instruction, peer_id, tetraplet.clone());
}
}
/// Helper struct for ExecCtx construction.

View File

@ -0,0 +1,59 @@
/*
* Copyright 2023 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::no_error;
use super::InstructionError;
use crate::execution_step::ErrorAffectable;
use crate::execution_step::RcSecurityTetraplet;
use crate::ToErrorCode;
pub(crate) struct ErrorDescriptor {
error: InstructionError,
}
impl ErrorDescriptor {
pub(crate) fn try_to_set_error_from_exec_error(
&mut self,
error: &(impl ErrorAffectable + ToErrorCode + ToString),
instruction: &str,
peer_id_option: Option<&str>,
tetraplet: Option<RcSecurityTetraplet>,
) {
use super::get_instruction_error_from_exec_error;
if !error.affects_error() {
return;
}
self.error = get_instruction_error_from_exec_error(error, instruction, peer_id_option, tetraplet);
}
pub(crate) fn error(&self) -> &InstructionError {
&self.error
}
pub(crate) fn clear_error(&mut self) {
self.error = no_error();
}
}
impl Default for ErrorDescriptor {
fn default() -> Self {
let error = no_error();
Self { error }
}
}

View File

@ -0,0 +1,73 @@
/*
* Copyright 2023 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_interpreter_data::Provenance;
use super::instruction_error_definition::error_from_raw_fields;
use super::instruction_error_definition::error_from_raw_fields_w_peerid;
use super::InstructionError;
use crate::execution_step::ErrorAffectable;
use crate::execution_step::RcSecurityTetraplet;
use crate::JValue;
use crate::ToErrorCode;
use std::rc::Rc;
pub(crate) fn get_instruction_error_from_exec_error(
error: &(impl ErrorAffectable + ToErrorCode + ToString),
instruction: &str,
peer_id_option: Option<&str>,
tetraplet: Option<RcSecurityTetraplet>,
) -> InstructionError {
// it is not a call result, but generated from a limited set of unjoinable errors
let provenance = Provenance::literal();
get_instruction_error_from_ingredients(
error.to_error_code(),
&error.to_string(),
instruction,
peer_id_option,
tetraplet,
provenance,
)
}
pub(crate) fn get_instruction_error_from_ingredients(
error_code: i64,
error_message: &str,
instruction: &str,
peer_id_option: Option<&str>,
tetraplet: Option<RcSecurityTetraplet>,
provenance: Provenance,
) -> InstructionError {
let error_object = match peer_id_option {
Some(peer_id) => error_from_raw_fields_w_peerid(error_code, error_message, instruction, peer_id),
None => error_from_raw_fields(error_code, error_message, instruction),
};
get_instruction_error_from_error_object(Rc::new(error_object), tetraplet, provenance)
}
pub(crate) fn get_instruction_error_from_error_object(
error: Rc<JValue>,
tetraplet: Option<RcSecurityTetraplet>,
provenance: Provenance,
) -> InstructionError {
InstructionError {
error,
tetraplet,
provenance,
}
}

View File

@ -32,14 +32,13 @@ pub const PEER_ID_FIELD_NAME: &str = "peer_id";
pub const NO_ERROR_MESSAGE: &str = "";
pub const NO_ERROR_ERROR_CODE: i64 = 0;
/// This struct is intended to track the last arisen error.
/// LastError is essentially a scalar value with support of lambda expressions.
/// The only differences from a scalar are
/// - it's accessed by %last_error% literal
/// - if it's unset before the usage, JValue::Null will be used without join behaviour
/// - it's a global scalar, meaning that fold and new scopes doesn't apply for it
/// This struct tracks the last arisen error.
/// :error: and %last_error% are special scalar values that support lenses.
/// There are some differences b/w mentioned errors and an ordinary scalar:
/// - they are set to not-an-error value by default
/// - these are global scalars, meaning that fold and new scopes do not apply for it
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LastError {
pub struct InstructionError {
/// Error object that represents the last occurred error.
pub error: Rc<JValue>,
@ -50,7 +49,12 @@ pub struct LastError {
pub provenance: Provenance,
}
pub(crate) fn error_from_raw_fields(error_code: i64, error_message: &str, instruction: &str, peer_id: &str) -> JValue {
pub(crate) fn error_from_raw_fields_w_peerid(
error_code: i64,
error_message: &str,
instruction: &str,
peer_id: &str,
) -> JValue {
serde_json::json!({
ERROR_CODE_FIELD_NAME: error_code,
MESSAGE_FIELD_NAME: error_message,
@ -59,6 +63,14 @@ pub(crate) fn error_from_raw_fields(error_code: i64, error_message: &str, instru
})
}
pub(crate) fn error_from_raw_fields(error_code: i64, error_message: &str, instruction: &str) -> JValue {
serde_json::json!({
ERROR_CODE_FIELD_NAME: error_code,
MESSAGE_FIELD_NAME: error_message,
INSTRUCTION_FIELD_NAME: instruction,
})
}
/// Checks that a scalar is a value of an object types that contains at least two fields:
/// - error_code
/// - message
@ -116,16 +128,16 @@ fn ensure_jvalue_is_string(
}
}
pub fn no_error_last_error_object() -> JValue {
pub fn no_error_object() -> JValue {
json!({
ERROR_CODE_FIELD_NAME: NO_ERROR_ERROR_CODE,
MESSAGE_FIELD_NAME: NO_ERROR_MESSAGE,
})
}
pub fn no_error_last_error() -> LastError {
LastError {
error: Rc::new(no_error_last_error_object()),
pub fn no_error() -> InstructionError {
InstructionError {
error: Rc::new(no_error_object()),
tetraplet: None,
provenance: Provenance::literal(),
}

View File

@ -16,10 +16,9 @@
use air_interpreter_data::Provenance;
use super::last_error_definition::error_from_raw_fields;
use super::no_error_last_error;
use super::LastError;
use crate::execution_step::LastErrorAffectable;
use super::no_error;
use super::InstructionError;
use crate::execution_step::ErrorAffectable;
use crate::execution_step::RcSecurityTetraplet;
use crate::JValue;
use crate::ToErrorCode;
@ -27,7 +26,7 @@ use crate::ToErrorCode;
use std::rc::Rc;
pub(crate) struct LastErrorDescriptor {
last_error: LastError,
error: InstructionError,
/// True, if last error could be set. This flag is used to distinguish
/// whether an error is being bubbled up from the bottom or just encountered.
@ -36,44 +35,23 @@ pub(crate) struct LastErrorDescriptor {
}
impl LastErrorDescriptor {
pub(crate) fn try_to_set_from_error(
pub(crate) fn try_to_set_last_error_from_exec_error(
&mut self,
error: &(impl LastErrorAffectable + ToErrorCode + ToString),
error: &(impl ErrorAffectable + ToErrorCode + ToString),
instruction: &str,
peer_id: &str,
peer_id_option: Option<&str>,
tetraplet: Option<RcSecurityTetraplet>,
) -> bool {
// this check is optimization to prevent creation of an error object in case if error
// couldn't be set
) {
use super::get_instruction_error_from_exec_error;
// This check is an optimization to prevent creation of an error object in case if error
// must not be set.
if !self.error_can_be_set || !error.affects_last_error() {
return false;
return;
}
// it is not a call result, but generated from a limited set of unjoinable errors
let provenance = Provenance::literal();
self.set_from_ingredients(
error.to_error_code(),
&error.to_string(),
instruction,
peer_id,
tetraplet,
provenance,
)
}
pub(crate) fn set_from_ingredients(
&mut self,
error_code: i64,
error_message: &str,
instruction: &str,
peer_id: &str,
tetraplet: Option<RcSecurityTetraplet>,
provenance: Provenance,
) -> bool {
let error_object = error_from_raw_fields(error_code, error_message, instruction, peer_id);
self.set_from_error_object(Rc::new(error_object), tetraplet, provenance);
true
self.error = get_instruction_error_from_exec_error(error, instruction, peer_id_option, tetraplet);
self.error_can_be_set = false;
}
pub(crate) fn set_from_error_object(
@ -82,16 +60,14 @@ impl LastErrorDescriptor {
tetraplet: Option<RcSecurityTetraplet>,
provenance: Provenance,
) {
self.last_error = LastError {
error,
tetraplet,
provenance,
};
use super::get_instruction_error_from_error_object;
self.error = get_instruction_error_from_error_object(error, tetraplet, provenance);
self.error_can_be_set = false;
}
pub(crate) fn last_error(&self) -> &LastError {
&self.last_error
pub(crate) fn error(&self) -> &InstructionError {
&self.error
}
pub(crate) fn meet_xor_right_branch(&mut self) {
@ -105,10 +81,10 @@ impl LastErrorDescriptor {
impl Default for LastErrorDescriptor {
fn default() -> Self {
let last_error = no_error_last_error();
let error = no_error();
Self {
last_error,
error,
error_can_be_set: true,
}
}

View File

@ -0,0 +1,37 @@
/*
* Copyright 2023 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 error_descriptor;
mod errors;
mod errors_utils;
mod instruction_error_definition;
mod last_error_descriptor;
pub use errors::LastErrorObjectError;
pub use instruction_error_definition::no_error;
pub use instruction_error_definition::no_error_object;
pub use instruction_error_definition::InstructionError;
pub use instruction_error_definition::ERROR_CODE_FIELD_NAME;
pub use instruction_error_definition::INSTRUCTION_FIELD_NAME;
pub use instruction_error_definition::MESSAGE_FIELD_NAME;
pub use instruction_error_definition::NO_ERROR_ERROR_CODE;
pub use instruction_error_definition::NO_ERROR_MESSAGE;
pub(crate) use error_descriptor::ErrorDescriptor;
pub(super) use errors_utils::*;
pub(crate) use instruction_error_definition::check_error_object;
pub(crate) use instruction_error_definition::error_from_raw_fields_w_peerid;
pub(crate) use last_error_descriptor::LastErrorDescriptor;

View File

@ -1,33 +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 errors;
mod last_error_definition;
mod last_error_descriptor;
pub use errors::LastErrorObjectError;
pub use last_error_definition::no_error_last_error;
pub use last_error_definition::no_error_last_error_object;
pub use last_error_definition::LastError;
pub use last_error_definition::ERROR_CODE_FIELD_NAME;
pub use last_error_definition::INSTRUCTION_FIELD_NAME;
pub use last_error_definition::MESSAGE_FIELD_NAME;
pub use last_error_definition::NO_ERROR_ERROR_CODE;
pub use last_error_definition::NO_ERROR_MESSAGE;
pub(crate) use last_error_definition::check_error_object;
pub(crate) use last_error_definition::error_from_raw_fields;
pub(crate) use last_error_descriptor::LastErrorDescriptor;

View File

@ -16,12 +16,12 @@
mod cid_state;
mod context;
mod last_error;
mod instruction_error;
mod scalar_variables;
mod stream_maps_variables;
mod streams_variables;
pub use last_error::*;
pub use instruction_error::*;
pub use cid_state::ExecutionCidState;
pub(crate) use context::*;

View File

@ -23,8 +23,9 @@ use crate::execution_step::PEEK_ALLOWED_ON_NON_EMPTY;
use crate::UncatchableError;
use air_interpreter_data::Provenance;
use air_lambda_parser::LambdaAST;
use air_lambda_ast::LambdaAST;
use air_parser::ast;
use air_parser::ast::InstructionErrorAST;
pub(crate) fn apply_to_arg(
argument: &ast::ApArgument<'_>,
@ -36,6 +37,7 @@ pub(crate) fn apply_to_arg(
let result = match argument {
InitPeerId => apply_const(exec_ctx.run_parameters.init_peer_id.as_ref(), exec_ctx, trace_ctx),
Error(instruction_error) => apply_error(instruction_error, exec_ctx, trace_ctx),
LastError(error_accessor) => apply_last_error(error_accessor, exec_ctx, trace_ctx),
Literal(value) => apply_const(*value, exec_ctx, trace_ctx),
Timestamp => apply_const(exec_ctx.run_parameters.timestamp, exec_ctx, trace_ctx),
@ -68,6 +70,21 @@ fn apply_const(
Ok(value)
}
fn apply_error<'ctx>(
instruction_error: &InstructionErrorAST<'ctx>,
exec_ctx: &ExecutionCtx<'ctx>,
trace_ctx: &TraceHandler,
) -> ExecutionResult<ValueAggregate> {
let (value, mut tetraplets, provenance) = instruction_error.resolve(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 position = trace_ctx.trace_pos().map_err(UncatchableError::from)?;
let result = ValueAggregate::new(value, tetraplet, position, provenance);
Ok(result)
}
fn apply_last_error<'i>(
error_accessor: &Option<LambdaAST<'i>>,
exec_ctx: &ExecutionCtx<'i>,

View File

@ -41,44 +41,43 @@ impl<'i> super::ExecutableInstruction<'i> for Call<'i> {
exec_ctx.tracker.meet_call();
let resolved_call = joinable!(ResolvedCall::new(self, exec_ctx), exec_ctx, ())
.map_err(|e| set_last_error(self, exec_ctx, e, None))?;
.map_err(|e| set_errors(self, exec_ctx, e, None))?;
let tetraplet = resolved_call.as_tetraplet();
joinable!(resolved_call.execute(self, exec_ctx, trace_ctx), exec_ctx, ())
.map_err(|e| set_last_error(self, exec_ctx, e, Some(tetraplet)))
.map_err(|e| set_errors(self, exec_ctx, e, Some(tetraplet)))
}
}
fn set_last_error<'i>(
fn set_errors<'i>(
call: &Call<'i>,
exec_ctx: &mut ExecutionCtx<'i>,
execution_error: ExecutionError,
tetraplet: Option<RcSecurityTetraplet>,
) -> ExecutionError {
use air_parser::ast::PeerIDErrorLogable;
let catchable_error = match execution_error {
ExecutionError::Catchable(catchable) => catchable,
ExecutionError::Uncatchable(_) => return execution_error,
};
let current_peer_id = match &tetraplet {
// use tetraplet if they set, because an error could be propagated from data
// (from CallServiceFailed state) and exec_ctx.run_parameters.current_peer_id won't mean
// a peer where the error was occurred
Some(tetraplet) => tetraplet.peer_pk.clone(),
None => exec_ctx.run_parameters.current_peer_id.to_string(),
};
log::debug!(
"call failed with an error `{}`, peerId `{}`",
catchable_error,
current_peer_id
);
let _ = exec_ctx.last_error_descriptor.try_to_set_from_error(
exec_ctx.set_errors(
catchable_error.as_ref(),
&call.to_string(),
&current_peer_id,
tetraplet,
tetraplet.clone(),
call.log_errors_with_peer_id(),
);
let peer_id = match &tetraplet {
// use tetraplet if it is set, because an error could be propagated from data
// (from CallServiceFailed state) and exec_ctx.run_parameters.current_peer_id won't mean
// a peer where the error was occurred
Some(tetraplet) => tetraplet.peer_pk.as_str(),
None => exec_ctx.run_parameters.current_peer_id.as_str(),
};
log::debug!("call failed with an error `{}`, peerId `{}`", catchable_error, peer_id);
ExecutionError::Catchable(catchable_error)
}

View File

@ -20,7 +20,6 @@ use super::TraceHandler;
use crate::execution_step::execution_context::check_error_object;
use crate::execution_step::resolver::Resolvable;
use crate::execution_step::CatchableError;
use crate::execution_step::LastError;
use crate::execution_step::RcSecurityTetraplet;
use crate::log_instruction;
use crate::ExecutionError;
@ -75,7 +74,7 @@ fn fail_with_literals(
fail: &Fail<'_>,
exec_ctx: &mut ExecutionCtx<'_>,
) -> ExecutionResult<()> {
let error_object = crate::execution_step::execution_context::error_from_raw_fields(
let error_object = crate::execution_step::execution_context::error_from_raw_fields_w_peerid(
error_code,
error_message,
&fail.to_string(),
@ -103,11 +102,13 @@ fn fail_with_canon_stream(
}
fn fail_with_last_error(exec_ctx: &mut ExecutionCtx<'_>) -> ExecutionResult<()> {
let LastError {
use crate::execution_step::InstructionError;
let InstructionError {
error,
tetraplet,
provenance,
} = exec_ctx.last_error_descriptor.last_error();
} = exec_ctx.last_error_descriptor.error();
// to avoid warnings from https://github.com/rust-lang/rust/issues/59159
let error = error.clone();

View File

@ -46,21 +46,14 @@ use super::ExecutionResult;
use crate::execution_step::TraceHandler;
use air_parser::ast::Instruction;
use air_parser::ast::PeerIDErrorLogable;
// TODO: move all error set logic from macros into the execution context
/// Executes instruction and updates last error if needed.
/// Executes an instruction and updates %last_error% and :error: if necessary.
macro_rules! execute {
($self:expr, $instr:expr, $exec_ctx:ident, $trace_ctx:ident) => {{
match $instr.execute($exec_ctx, $trace_ctx) {
Err(e) => {
$exec_ctx.last_error_descriptor.try_to_set_from_error(
&e,
// TODO: avoid excess copying here
&$instr.to_string(),
$exec_ctx.run_parameters.current_peer_id.as_ref(),
None,
);
$exec_ctx.set_errors(&e, &$instr.to_string(), None, $instr.log_errors_with_peer_id());
Err(e)
}
v => v,
@ -76,13 +69,14 @@ impl<'i> ExecutableInstruction<'i> for Instruction<'i> {
fn execute(&self, exec_ctx: &mut ExecutionCtx<'i>, trace_ctx: &mut TraceHandler) -> ExecutionResult<()> {
match self {
// call isn't wrapped by the execute macro because
// it internally sets last_error with resolved triplet
// it internally maps some Catchables into %last_error%/:error: using resolved triplet.
// Both canons and call set :error:.$.peer_id whilst other instructions do not.
Instruction::Call(call) => call.execute(exec_ctx, trace_ctx),
Instruction::Ap(ap) => execute!(self, ap, exec_ctx, trace_ctx),
Instruction::ApMap(ap_map) => execute!(self, ap_map, exec_ctx, trace_ctx),
Instruction::Canon(canon) => execute!(self, canon, exec_ctx, trace_ctx),
Instruction::CanonStreamMapScalar(canon) => execute!(self, canon, exec_ctx, trace_ctx),
Instruction::Ap(ap) => execute!(self, ap, exec_ctx, trace_ctx),
Instruction::ApMap(ap_map) => execute!(self, ap_map, 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),

View File

@ -33,7 +33,13 @@ impl<'i> super::ExecutableInstruction<'i> for Xor<'i> {
exec_ctx.flush_subgraph_completeness();
exec_ctx.last_error_descriptor.meet_xor_right_branch();
self.1.execute(exec_ctx, trace_ctx)
let right_subgraph_result = self.1.execute(exec_ctx, trace_ctx);
// This sets :error: to a no-error state.
// Please note the right_subgraph_result might be an Error that bubbles up to an :error:
// above this execute().
exec_ctx.error_descriptor.clear_error();
right_subgraph_result
}
res => res,
}

View File

@ -40,10 +40,10 @@ pub mod errors_prelude {
pub(super) use self::instructions::ExecutableInstruction;
pub(super) use self::instructions::FoldState;
pub(crate) use errors::ErrorAffectable;
pub(crate) use errors::Joinable;
pub(crate) use errors::LastErrorAffectable;
pub(crate) use execution_context::ExecutionCtx;
pub(crate) use execution_context::LastError;
pub(crate) use execution_context::InstructionError;
pub(super) use value_types::CanonResultAggregate;
pub(super) use value_types::Generation;
pub(super) use value_types::LiteralAggregate;

View File

@ -21,12 +21,13 @@ use crate::execution_step::lambda_applier::select_by_lambda_from_scalar;
use crate::execution_step::value_types::JValuable;
use crate::execution_step::ExecutionResult;
use crate::JValue;
use crate::LambdaAST;
use crate::SecurityTetraplet;
use air_interpreter_data::Provenance;
use air_lambda_ast::LambdaAST;
use air_parser::ast;
use air_parser::ast::InstructionErrorAST;
use serde_json::json;
use std::rc::Rc;
@ -37,6 +38,7 @@ impl Resolvable for ast::ImmutableValue<'_> {
match self {
InitPeerId => resolve_const(ctx.run_parameters.init_peer_id.as_ref(), ctx),
Error(error_accessor) => error_accessor.resolve(ctx),
LastError(error_accessor) => error_accessor.resolve(ctx),
Literal(value) => resolve_const(value.to_string(), ctx),
Timestamp => resolve_const(ctx.run_parameters.timestamp, ctx),
@ -61,31 +63,47 @@ pub(crate) fn resolve_const(
Ok((jvalue, vec![tetraplet], Provenance::literal()))
}
fn resolve_errors(
instruction_error: &crate::InstructionError,
lens: &Option<LambdaAST<'_>>,
ctx: &ExecutionCtx<'_>,
) -> Result<(serde_json::Value, Vec<Rc<SecurityTetraplet>>, Provenance), crate::ExecutionError> {
use crate::execution_step::InstructionError;
let InstructionError {
error,
tetraplet,
provenance,
} = instruction_error;
let jvalue = match lens {
Some(error_accessor) => select_by_lambda_from_scalar(error.as_ref(), error_accessor, ctx)?.into_owned(),
None => error.as_ref().clone(),
};
let tetraplets = match tetraplet {
Some(tetraplet) => vec![tetraplet.clone()],
None => {
let tetraplet = SecurityTetraplet::literal_tetraplet(ctx.run_parameters.init_peer_id.as_ref());
let tetraplet = Rc::new(tetraplet);
vec![tetraplet]
}
};
Ok((jvalue, tetraplets, provenance.clone()))
}
impl<'lens> Resolvable for InstructionErrorAST<'lens> {
fn resolve(&self, ctx: &ExecutionCtx<'_>) -> ExecutionResult<(JValue, RcSecurityTetraplets, Provenance)> {
let instruction_error = ctx.error();
resolve_errors(instruction_error, &self.lens, ctx)
}
}
impl Resolvable for Option<LambdaAST<'_>> {
fn resolve(&self, ctx: &ExecutionCtx<'_>) -> ExecutionResult<(JValue, RcSecurityTetraplets, Provenance)> {
use crate::LastError;
let LastError {
error,
tetraplet,
provenance,
} = ctx.last_error();
let jvalue = match self {
Some(error_accessor) => select_by_lambda_from_scalar(error.as_ref(), error_accessor, ctx)?.into_owned(),
None => error.as_ref().clone(),
};
let tetraplets = match tetraplet {
Some(tetraplet) => vec![tetraplet.clone()],
None => {
let tetraplet = SecurityTetraplet::literal_tetraplet(ctx.run_parameters.init_peer_id.as_ref());
let tetraplet = Rc::new(tetraplet);
vec![tetraplet]
}
};
Ok((jvalue, tetraplets, provenance.clone()))
let instruction_error = ctx.last_error();
resolve_errors(instruction_error, self, ctx)
}
}

View File

@ -37,10 +37,10 @@ pub use air_interpreter_interface::RunParameters;
pub use air_interpreter_interface::INTERPRETER_SUCCESS;
pub use execution_step::execution_context::errors::unsupported_map_key_type;
pub use execution_step::execution_context::errors::StreamMapError;
pub use execution_step::execution_context::no_error_last_error;
pub use execution_step::execution_context::no_error_last_error_object;
pub use execution_step::execution_context::no_error;
pub use execution_step::execution_context::no_error_object;
pub use execution_step::execution_context::ExecutionCidState;
pub use execution_step::execution_context::LastError;
pub use execution_step::execution_context::InstructionError;
pub use execution_step::execution_context::NO_ERROR_ERROR_CODE;
pub use execution_step::execution_context::NO_ERROR_MESSAGE;
pub use execution_step::CatchableError;

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
use air::no_error_last_error_object;
use air::no_error_object;
use air::CatchableError;
use air::ExecutionCidState;
use air::ExecutionError;
@ -124,7 +124,7 @@ fn not_clear_last_error_in_match() {
let _ = checked_call_vm!(local_vm, <_>::default(), &script, "", result.data);
let actual_value = (*args.borrow()).as_ref().unwrap().clone();
assert_eq!(actual_value, no_error_last_error_object(),);
assert_eq!(actual_value, no_error_object(),);
}
#[test]
@ -159,7 +159,7 @@ fn not_clear_last_error_in_mismatch() {
let _ = checked_call_vm!(local_vm, <_>::default(), &script, "", result.data);
let actual_value = (*args.borrow()).as_ref().unwrap().clone();
assert_eq!(actual_value, no_error_last_error_object(),);
assert_eq!(actual_value, no_error_object(),);
}
#[test]
@ -241,7 +241,7 @@ fn non_initialized_last_error() {
let _ = checked_call_vm!(vm, test_params.clone(), script, "", "");
let actual_value = (*args.borrow()).as_ref().unwrap().clone();
assert_eq!(actual_value, no_error_last_error_object(),);
assert_eq!(actual_value, no_error_object(),);
let actual_tetraplets = (*tetraplets.borrow()).as_ref().unwrap().clone();
assert_eq!(

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
use air::no_error_last_error_object;
use air::no_error_object;
use air::ExecutionCidState;
use air_test_framework::AirScriptExecutor;
use air_test_utils::prelude::*;
@ -198,16 +198,57 @@ fn ap_with_last_error() {
"tetraplet": {"function_name": "", "json_path": "", "peer_pk": "vm_1_peer_id", "service_id": ""},
"values": [
{
"result": no_error_last_error_object(),
"result": no_error_object(),
"tetraplet": {"function_name": "", "json_path": "", "peer_pk": "", "service_id": ""},
"trace_pos": 0
}
]
})),
unused!(
json!([no_error_last_error_object()]),
json!([no_error_object()]),
peer = vm_1_peer_id,
args = [no_error_last_error_object()]
args = [no_error_object()]
),
];
assert_eq!(actual_trace, expected_state);
assert!(result.next_peer_pks.is_empty());
}
#[test]
fn ap_with_error() {
let vm_1_peer_id = "vm_1_peer_id";
let mut vm_1 = create_avm(echo_call_service(), vm_1_peer_id);
let script = format!(
r#"
(seq
(ap :error: $stream)
(seq
(canon "{vm_1_peer_id}" $stream #canon_stream)
(call "{vm_1_peer_id}" ("" "") [#canon_stream])))
"#
);
let result = checked_call_vm!(vm_1, <_>::default(), script, "", "");
let actual_trace = trace_from_result(&result);
let expected_state = vec![
executed_state::ap(0),
executed_state::canon(json!({
"tetraplet": {"function_name": "", "json_path": "", "peer_pk": "vm_1_peer_id", "service_id": ""},
"values": [
{
"result": no_error_object(),
"tetraplet": {"function_name": "", "json_path": "", "peer_pk": "", "service_id": ""},
"trace_pos": 0
}
]
})),
unused!(
json!([no_error_object()]),
peer = vm_1_peer_id,
args = [no_error_object()]
),
];
@ -589,11 +630,11 @@ fn ap_stream_map_with_undefined_last_error() {
SubTraceDesc::new(3.into(), 0),
)]),
unused!(
no_error_last_error_object(),
no_error_object(),
peer = vm_1_peer_id,
service = "m",
function = "f",
args = [no_error_last_error_object()]
args = [no_error_object()]
),
];

View File

@ -451,3 +451,35 @@ fn match_with_undefined_last_error_message() {
)]);
assert_eq!(actual_trace, expected_trace);
}
#[test]
fn match_with_error() {
let local_peer_id = "local_peer_id";
let mut vm = create_avm(echo_call_service(), local_peer_id);
let script = format!(
r#"
(xor
(match 1 2 (null))
(call "local_peer_id" ("test" "error_code") [:error:.$.error_code] scalar)
)
"#
);
let result = checked_call_vm!(vm, <_>::default(), script, "", "");
let actual_trace = trace_from_result(&result);
let mut cid_state = ExecutionCidState::new();
let errcode_lambda_output = json!(10001);
let expected_trace = ExecutionTrace::from(vec![scalar_tracked!(
errcode_lambda_output.clone(),
cid_state,
peer = local_peer_id,
service = "test",
function = "error_code",
args = vec![errcode_lambda_output]
)]);
assert_eq!(actual_trace, expected_trace);
}

View File

@ -203,3 +203,37 @@ fn mismatch_with_two_xors() {
assert_eq!(actual_trace.pop().unwrap(), expected_executed_call_result);
}
#[test]
fn mismatch_with_error() {
use air::ExecutionCidState;
let local_peer_id = "local_peer_id";
let mut vm = create_avm(echo_call_service(), local_peer_id);
let script = format!(
r#"
(xor
(mismatch 42 42 (null))
(call "local_peer_id" ("test" "error_code") [:error:.$.error_code] scalar)
)
"#
);
let result = checked_call_vm!(vm, <_>::default(), script, "", "");
let actual_trace = trace_from_result(&result);
let mut cid_state = ExecutionCidState::new();
let errcode_lambda_output = json!(10002);
let expected_trace = ExecutionTrace::from(vec![scalar_tracked!(
errcode_lambda_output.clone(),
cid_state,
peer = local_peer_id,
service = "test",
function = "error_code",
args = vec![errcode_lambda_output]
)]);
assert_eq!(actual_trace, expected_trace);
}

View File

@ -235,3 +235,34 @@ fn last_error_with_xor() {
assert_eq!(actual_trace[1.into()], expected_state);
}
#[test]
fn error_with_xor() {
let faillible_peer_id = "failible_peer_id";
let mut faillible_vm = create_avm(fallible_call_service("service_id_1"), faillible_peer_id);
let local_peer_id = "local_peer_id";
let mut vm = create_avm(echo_call_service(), local_peer_id);
let script = format!(
r#"
(xor
(call "{faillible_peer_id}" ("service_id_1" "local_fn_name") [] result)
(call "{local_peer_id}" ("service_id_2" "local_fn_name") [:error:.$.message] result)
)"#
);
let result = checked_call_vm!(faillible_vm, <_>::default(), script.clone(), "", "");
let result = checked_call_vm!(vm, <_>::default(), script, "", result.data);
let actual_trace = trace_from_result(&result);
let msg = r#"Local service error, ret_code is 1, error message is '"failed result from fallible_call_service"'"#;
let expected_state = scalar!(
msg,
peer = local_peer_id,
service = "service_id_2",
function = "local_fn_name",
args = [msg]
);
assert_eq!(actual_trace[1.into()], expected_state);
}

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
use air::no_error_last_error_object;
use air::no_error_object;
use air::unsupported_map_key_type;
use air::CatchableError;
use air::LambdaError;
@ -608,7 +608,55 @@ fn undefined_last_error_functor() {
.expect("invalid test AIR script");
let result = executor.execute_all(local_peer_id).unwrap();
let expected_error = CatchableError::LengthFunctorAppliedToNotArray(no_error_last_error_object());
let expected_error = CatchableError::LengthFunctorAppliedToNotArray(no_error_object());
assert!(check_error(&result.last().unwrap(), expected_error));
}
#[test]
fn undefined_error_functor() {
let local_peer_id = "local_peer_id";
let script = format!(
r#"
(xor
(match 1 2
(null)
)
(call "local_peer_id" ("test" "error_code") [:error:.length] scalar)
)
"#
);
let executor = AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(local_peer_id), &script)
.expect("invalid test AIR script");
let result = executor.execute_all(local_peer_id).unwrap();
let error_object = json!({"error_code":10001, "instruction":"match 1 2","message":"compared values do not match"});
let expected_error = CatchableError::LengthFunctorAppliedToNotArray(error_object);
assert!(check_error(&result.last().unwrap(), expected_error));
}
#[test]
fn undefined_error_peerid() {
let local_peer_id = "local_peer_id";
let script = format!(
r#"
(xor
(match 1 2
(null)
)
(call "local_peer_id" ("test" "error_code") [:error:.$.peerid] scalar)
)
"#
);
let executor = AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(local_peer_id), &script)
.expect("invalid test AIR script");
let result = executor.execute_all(local_peer_id).unwrap();
let value = json!({"error_code":10001, "instruction":"match 1 2", "message":"compared values do not match"});
let field_name = "peerid".into();
let expected_error =
air::CatchableError::LambdaApplierError(air::LambdaError::ValueNotContainSuchField { value, field_name });
assert!(check_error(&result.last().unwrap(), expected_error));
}
@ -631,7 +679,7 @@ fn undefined_last_error_instruction() {
let result = executor.execute_all(local_peer_id).unwrap();
let expected_error = CatchableError::LambdaApplierError(LambdaError::ValueNotContainSuchField {
value: no_error_last_error_object(),
value: no_error_object(),
field_name: "instruction".to_string(),
});
assert!(check_error(&&result.last().unwrap(), expected_error));
@ -655,7 +703,7 @@ fn undefined_last_error_peer_id() {
let result = executor.execute_all(local_peer_id).unwrap();
let expected_error = CatchableError::LambdaApplierError(LambdaError::ValueNotContainSuchField {
value: no_error_last_error_object(),
value: no_error_object(),
field_name: "peer_id".to_string(),
});
assert!(check_error(&&result.last().unwrap(), expected_error));

View File

@ -21,6 +21,7 @@ use super::CanonStream;
use super::CanonStreamWithLambda;
use super::ImmutableVariable;
use super::ImmutableVariableWithLambda;
use super::InstructionErrorAST;
use super::Scalar;
use super::ScalarWithLambda;
use super::Stream;
@ -68,6 +69,7 @@ pub struct Triplet<'i> {
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub enum ImmutableValue<'i> {
InitPeerId,
Error(InstructionErrorAST<'i>),
LastError(Option<LambdaAST<'i>>),
Timestamp,
TTL,
@ -93,6 +95,7 @@ pub enum ApArgument<'i> {
InitPeerId,
Timestamp,
TTL,
Error(InstructionErrorAST<'i>),
LastError(Option<LambdaAST<'i>>),
Literal(&'i str),
Number(Number),

View File

@ -35,6 +35,7 @@ impl fmt::Display for ImmutableValue<'_> {
match self {
InitPeerId => write!(f, "%init_peer_id%"),
Error(error_accessor) => display_error(f, error_accessor),
LastError(error_accessor) => display_last_error(f, error_accessor),
Literal(literal) => write!(f, r#""{literal}""#),
Timestamp => write!(f, "%timestamp%"),
@ -93,6 +94,7 @@ impl fmt::Display for ApArgument<'_> {
match self {
InitPeerId => write!(f, "%init_peer_id%"),
Error(error_accessor) => display_error(f, error_accessor),
LastError(error_accessor) => display_last_error(f, error_accessor),
Literal(str) => write!(f, r#""{str}""#),
Timestamp => write!(f, "%timestamp%"),
@ -183,8 +185,21 @@ impl From<&Number> for serde_json::Value {
}
fn display_last_error(f: &mut fmt::Formatter, lambda_ast: &Option<LambdaAST>) -> fmt::Result {
use crate::parser::LAST_ERROR;
match lambda_ast {
Some(lambda_ast) => write!(f, "%last_error%{lambda_ast}"),
None => write!(f, "%last_error%"),
Some(lambda_ast) => write!(f, "{LAST_ERROR}{lambda_ast}"),
None => write!(f, "{LAST_ERROR}"),
}
}
fn display_error(f: &mut fmt::Formatter, error: &InstructionErrorAST) -> fmt::Result {
use crate::parser::ERROR;
let InstructionErrorAST { lens } = error;
match lens {
Some(lens) => write!(f, "{ERROR}{lens}"),
None => write!(f, "{ERROR}"),
}
}

View File

@ -188,3 +188,7 @@ pub struct New<'i> {
/// (null)
#[derive(Serialize, Debug, PartialEq, Eq)]
pub struct Null;
pub trait PeerIDErrorLogable {
fn log_errors_with_peer_id(&self) -> bool;
}

View File

@ -175,3 +175,49 @@ impl fmt::Display for New<'_> {
write!(f, "new {}", self.argument)
}
}
macro_rules! peer_id_error_logable {
($($t:ty),+) => {
$(
impl PeerIDErrorLogable for $t {
#[inline]
fn log_errors_with_peer_id(&self) -> bool {
true
}
}
)+
};
}
macro_rules! no_peer_id_error_logable {
($($t:ty),+) => {
$(
impl PeerIDErrorLogable for $t {
#[inline]
fn log_errors_with_peer_id(&self) -> bool {
false
}
}
)+
};
}
peer_id_error_logable!(Call<'_>, Canon<'_>, CanonStreamMapScalar<'_>);
no_peer_id_error_logable!(
Ap<'_>,
ApMap<'_>,
Fail<'_>,
FoldScalar<'_>,
FoldStream<'_>,
FoldStreamMap<'_>,
Seq<'_>,
Par<'_>,
Xor<'_>,
Match<'_>,
MisMatch<'_>,
Never,
Next<'_>,
New<'_>,
Null
);

View File

@ -86,3 +86,10 @@ pub struct StreamMap<'i> {
pub name: &'i str,
pub position: AirPos,
}
/// An error wrapper with an optional lens.
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
pub struct InstructionErrorAST<'lens> {
#[serde(borrow)]
pub lens: Option<LambdaAST<'lens>>,
}

View File

@ -125,3 +125,9 @@ impl<'i> StreamMap<'i> {
Self { name, position }
}
}
impl<'lens> InstructionErrorAST<'lens> {
pub fn new(lens: Option<LambdaAST<'lens>>) -> Self {
Self { lens }
}
}

View File

@ -230,6 +230,8 @@ Value: ImmutableValue<'input> = {
InitPeerId => ImmutableValue::InitPeerId,
<LastError> => ImmutableValue::LastError(None),
<le:LastErrorWithLambda> => ImmutableValue::LastError(Some(le)),
<Error> => ImmutableValue::Error(InstructionErrorAST::new(None)),
<le:ErrorWithLambda> => ImmutableValue::Error(InstructionErrorAST::new(Some(le))),
<l:Literal> => ImmutableValue::Literal(l),
Timestamp => ImmutableValue::Timestamp,
TTL => ImmutableValue::TTL,
@ -246,6 +248,8 @@ ApArgument: ApArgument<'input> = {
InitPeerId => ApArgument::InitPeerId,
<LastError> => ApArgument::LastError(None),
<le:LastErrorWithLambda> => ApArgument::LastError(Some(le)),
<Error> => ApArgument::Error(InstructionErrorAST::new(None)),
<le:ErrorWithLambda> => ApArgument::Error(InstructionErrorAST::new(Some(le))),
Timestamp => ApArgument::Timestamp,
TTL => ApArgument::TTL,
<l:Literal> => ApArgument::Literal(l),
@ -295,6 +299,8 @@ extern {
InitPeerId => Token::InitPeerId,
LastError => Token::LastError,
LastErrorWithLambda => Token::LastErrorWithLambda(<LambdaAST<'input>>),
Error => Token::Error,
ErrorWithLambda => Token::ErrorWithLambda(<LambdaAST<'input>>),
Timestamp => Token::Timestamp,
TTL => Token::TTL,

File diff suppressed because it is too large Load Diff

View File

@ -195,7 +195,10 @@ fn string_to_token(input: &str, start_pos: AirPos) -> LexerResult<Token> {
MISMATCH_INSTR => Ok(Token::MisMatch),
INIT_PEER_ID => Ok(Token::InitPeerId),
_ if input.starts_with(LAST_ERROR) => parse_last_error(input, start_pos),
_ if input.starts_with(ERROR) => parse_error(input, start_pos, ERROR, Token::Error),
_ if input.starts_with(LAST_ERROR) => {
parse_error(input, start_pos, LAST_ERROR, Token::LastError)
}
TIMESTAMP => Ok(Token::Timestamp),
TTL => Ok(Token::TTL),
@ -206,28 +209,37 @@ fn string_to_token(input: &str, start_pos: AirPos) -> LexerResult<Token> {
}
}
fn parse_last_error(input: &str, start_pos: AirPos) -> LexerResult<Token<'_>> {
let last_error_size = LAST_ERROR.len();
if input.len() == last_error_size {
return Ok(Token::LastError);
fn parse_error<'input>(
input: &'input str,
start_pos: AirPos,
token_str: &str,
token_wo_lens: Token<'static>,
) -> LexerResult<Token<'input>> {
let token_wo_lens_len = token_str.len();
if input.len() == token_wo_lens_len {
return Ok(token_wo_lens);
}
if input.len() <= last_error_size {
if input.len() <= token_wo_lens_len {
return Err(LexerError::lambda_parser_error(
start_pos + last_error_size..start_pos + input.len(),
start_pos + token_wo_lens_len..start_pos + input.len(),
"lambda AST applied to last error has not enough size",
));
}
let last_error_accessor = crate::parse_lambda(&input[last_error_size..]).map_err(|e| {
let last_error_accessor = crate::parse_lambda(&input[token_wo_lens_len..]).map_err(|e| {
LexerError::lambda_parser_error(
start_pos + last_error_size..start_pos + input.len(),
start_pos + token_wo_lens_len..start_pos + input.len(),
e.to_string(),
)
})?;
let last_error_token = Token::LastErrorWithLambda(last_error_accessor);
Ok(last_error_token)
match token_wo_lens {
Token::Error => Ok(Token::ErrorWithLambda(last_error_accessor)),
Token::LastError => Ok(Token::LastErrorWithLambda(last_error_accessor)),
_ => unreachable!(),
}
}
const CALL_INSTR: &str = "call";
@ -246,7 +258,8 @@ const MATCH_INSTR: &str = "match";
const MISMATCH_INSTR: &str = "mismatch";
const INIT_PEER_ID: &str = "%init_peer_id%";
const LAST_ERROR: &str = "%last_error%";
pub(crate) const LAST_ERROR: &str = "%last_error%";
pub(crate) const ERROR: &str = ":error:";
const TIMESTAMP: &str = "%timestamp%";
const TTL: &str = "%ttl%";

View File

@ -25,6 +25,8 @@ mod tests;
pub mod text_pos;
pub use air_lexer::AIRLexer;
pub(crate) use air_lexer::ERROR;
pub(crate) use air_lexer::LAST_ERROR;
pub use errors::LexerError;
pub use text_pos::AirPos;
pub use token::Token;

View File

@ -72,7 +72,9 @@ pub enum Token<'input> {
InitPeerId,
LastError,
Error,
LastErrorWithLambda(LambdaAST<'input>),
ErrorWithLambda(LambdaAST<'input>),
Timestamp,
TTL,

View File

@ -33,6 +33,8 @@ pub mod tests;
pub use self::air_parser::parse;
pub use air::AIRParser;
pub use lexer::AIRLexer;
pub(crate) use lexer::ERROR;
pub(crate) use lexer::LAST_ERROR;
pub use span::Span;
pub use validator::VariableValidator;

View File

@ -155,6 +155,7 @@ impl<'i> VariableValidator<'i> {
| ApArgument::Literal(_)
| ApArgument::EmptyArray
| ApArgument::LastError(_) => {}
ApArgument::Error(_) => {}
ApArgument::Scalar(scalar) => self.met_scalar(scalar, span),
ApArgument::ScalarWithLambda(scalar) => self.met_scalar_wl(scalar, span),
ApArgument::CanonStream(canon_stream) => self.met_canon_stream(canon_stream, span),
@ -227,8 +228,8 @@ impl<'i> VariableValidator<'i> {
use ImmutableValue::*;
match instr_arg_value {
InitPeerId | LastError(_) | Timestamp | TTL | Literal(_) | Number(_) | Boolean(_)
| EmptyArray => {}
InitPeerId | Error(_) | LastError(_) | Timestamp | TTL | Literal(_) | Number(_)
| Boolean(_) | EmptyArray => {}
Variable(variable) => self.met_variable(variable, span),
VariableWithLambda(variable) => self.met_variable_wl(variable, span),
}
@ -323,6 +324,7 @@ impl<'i> VariableValidator<'i> {
| ImmutableValue::Number(_)
| ImmutableValue::Boolean(_)
| ImmutableValue::Literal(_)
| ImmutableValue::Error(_)
| ImmutableValue::LastError(_)
| ImmutableValue::EmptyArray => {}
ImmutableValue::Variable(variable) => self.met_variable(variable, span),

View File

@ -135,7 +135,10 @@ fn parse_sexp_string(inp: Input<'_>) -> IResult<Input<'_>, Sexp, ParseError<'_>>
fn parse_sexp_symbol(inp: Input<'_>) -> IResult<Input<'_>, Sexp, ParseError<'_>> {
map(
recognize(pair(
many1_count(alt((value((), alphanumeric1), value((), one_of("_-.$#%"))))),
many1_count(alt((
value((), alphanumeric1),
value((), one_of("_-.:$#%")),
))),
opt(terminated(
delimited(tag("["), parse_sexp_symbol, tag("]")),
opt(tag("!")),