mirror of
https://github.com/fluencelabs/aquavm
synced 2024-12-04 15:20:16 +00:00
Fix last_error behaviour (#66)
This commit is contained in:
parent
c6d8a458da
commit
271156b5fc
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -17,6 +17,7 @@ dependencies = [
|
||||
"codespan-reporting",
|
||||
"criterion",
|
||||
"fstrings",
|
||||
"itertools 0.10.0",
|
||||
"lalrpop",
|
||||
"lalrpop-util",
|
||||
"regex",
|
||||
|
@ -18,6 +18,8 @@ codespan-reporting = "0.9.5"
|
||||
serde = { version = "=1.0.118", features = ["rc"] }
|
||||
serde_json = "=1.0.61"
|
||||
|
||||
itertools = "0.10.0"
|
||||
|
||||
thiserror = "1.0.23"
|
||||
|
||||
[dev-dependencies]
|
||||
|
@ -1,3 +1,21 @@
|
||||
/*
|
||||
* 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 traits;
|
||||
|
||||
pub use crate::parser::lexer::Number;
|
||||
|
||||
use serde::Deserialize;
|
||||
|
189
crates/air-parser/src/parser/ast/traits.rs
Normal file
189
crates/air-parser/src/parser/ast/traits.rs
Normal file
@ -0,0 +1,189 @@
|
||||
/*
|
||||
* Copyright 2020 Fluence Labs Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use super::*;
|
||||
|
||||
use std::fmt;
|
||||
|
||||
impl fmt::Display for CallInstrArgValue<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
use CallInstrArgValue::*;
|
||||
|
||||
match self {
|
||||
InitPeerId => write!(f, "%init_peer_id%"),
|
||||
LastError => write!(f, "%last_error%"),
|
||||
Literal(str) => write!(f, r#""{}""#, str),
|
||||
Number(number) => write!(f, "{}", number),
|
||||
Boolean(bool) => write!(f, "{}", bool),
|
||||
Variable(str) => write!(f, "{}", str),
|
||||
JsonPath { variable, path } => write!(f, "{}.{}", variable, path),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for CallInstrValue<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
use CallInstrValue::*;
|
||||
|
||||
match self {
|
||||
InitPeerId => write!(f, "%init_peer_id%"),
|
||||
Literal(str) => write!(f, r#""{}""#, str),
|
||||
Variable(str) => write!(f, "{}", str),
|
||||
JsonPath { variable, path } => write!(f, "{}.{}", variable, path),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for IterableValue<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
use IterableValue::*;
|
||||
|
||||
match self {
|
||||
Variable(str) => write!(f, "{}", str),
|
||||
JsonPath { variable, path } => write!(f, "{}.{}", variable, path),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for MatchableValue<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
use MatchableValue::*;
|
||||
|
||||
match self {
|
||||
Literal(str) => write!(f, r#""{}""#, str),
|
||||
Number(number) => write!(f, "{}", number),
|
||||
Boolean(bool) => write!(f, "{}", bool),
|
||||
Variable(str) => write!(f, "{}", str),
|
||||
JsonPath { variable, path } => write!(f, "{}.{}", variable, path),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for CallOutputValue<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
use CallOutputValue::*;
|
||||
|
||||
match self {
|
||||
Scalar(str) => write!(f, "{}", str),
|
||||
Accumulator(str) => write!(f, "{}[]", str),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for PeerPart<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
use PeerPart::*;
|
||||
|
||||
match self {
|
||||
PeerPk(peer_pk) => write!(f, "{}", peer_pk),
|
||||
PeerPkWithServiceId(peer_pk, service_id) => write!(f, "({} {})", peer_pk, service_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for FunctionPart<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
use FunctionPart::*;
|
||||
|
||||
match self {
|
||||
FuncName(func_name) => write!(f, "{}", func_name),
|
||||
ServiceIdWithFuncName(service_id, func_name) => {
|
||||
write!(f, "({} {})", service_id, func_name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Instruction<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
use Instruction::*;
|
||||
|
||||
match self {
|
||||
Null(null) => write!(f, "{}", null),
|
||||
Call(call) => write!(f, "{}", call),
|
||||
Seq(seq) => write!(f, "{}", seq),
|
||||
Par(par) => write!(f, "{}", par),
|
||||
Xor(xor) => write!(f, "{}", xor),
|
||||
Match(match_) => write!(f, "{}", match_),
|
||||
MisMatch(mismatch) => write!(f, "{}", mismatch),
|
||||
Fold(fold) => write!(f, "{}", fold),
|
||||
Next(next) => write!(f, "{}", next),
|
||||
Error => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Call<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
use itertools::Itertools;
|
||||
|
||||
write!(f, "call {} {}", self.peer_part, self.function_part)?;
|
||||
|
||||
let args = self.args.iter().map(|arg| format!("{}", arg)).join(" ");
|
||||
write!(f, " [{}]", args)?;
|
||||
|
||||
write!(f, " {}", self.output)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Fold<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "fold {} {}", self.iterable, self.iterator)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Seq<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "seq")
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Par<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "par")
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Null {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "null")
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Xor<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "xor")
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Match<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "match {} {}", self.left_value, self.right_value)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for MisMatch<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "mismatch {} {}", self.left_value, self.right_value)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Next<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "next")
|
||||
}
|
||||
}
|
@ -51,6 +51,19 @@ pub enum Number {
|
||||
Float(f64),
|
||||
}
|
||||
|
||||
use std::fmt;
|
||||
|
||||
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)
|
||||
|
@ -136,6 +136,33 @@ pub fn set_variables_call_service(ret_mapping: HashMap<String, String>) -> CallS
|
||||
})
|
||||
}
|
||||
|
||||
pub fn fallible_call_service(fallible_service_id: impl Into<String>) -> CallServiceClosure {
|
||||
let fallible_service_id = fallible_service_id.into();
|
||||
|
||||
Box::new(move |_, args| -> Option<IValue> {
|
||||
let builtin_service = match &args[0] {
|
||||
IValue::String(str) => str,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
// return a error for service with such id
|
||||
if builtin_service == &fallible_service_id {
|
||||
Some(IValue::Record(
|
||||
NEVec::new(vec![IValue::S32(1), IValue::String(String::from("error"))]).unwrap(),
|
||||
))
|
||||
} else {
|
||||
// return success for services with other ids
|
||||
Some(IValue::Record(
|
||||
NEVec::new(vec![
|
||||
IValue::S32(0),
|
||||
IValue::String(String::from(r#""res""#)),
|
||||
])
|
||||
.unwrap(),
|
||||
))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! call_vm {
|
||||
($vm:expr, $init_peer_id:expr, $script:expr, $prev_data:expr, $data:expr) => {
|
||||
|
@ -50,6 +50,10 @@ pub(crate) struct ExecutionCtx<'i> {
|
||||
/// None means that there weren't any error.
|
||||
pub last_error: Option<LastErrorDescriptor>,
|
||||
|
||||
/// 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.
|
||||
pub last_error_could_be_set: bool,
|
||||
|
||||
/// Indicates that previous executed subtree is complete.
|
||||
/// A subtree treats as a complete if all subtree elements satisfy the following rules:
|
||||
/// - at least one of par subtrees is completed
|
||||
@ -101,6 +105,7 @@ impl<'i> ExecutionCtx<'i> {
|
||||
current_peer_id,
|
||||
init_peer_id,
|
||||
subtree_complete: true,
|
||||
last_error_could_be_set: true,
|
||||
..<_>::default()
|
||||
}
|
||||
}
|
||||
|
@ -30,6 +30,8 @@ use crate::SecurityTetraplet;
|
||||
|
||||
use air_parser::ast::Call;
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
/// This macro converts joinable errors to Ok and sets subtree complete to true.
|
||||
macro_rules! joinable {
|
||||
($cmd:expr, $exec_ctx:expr) => {
|
||||
@ -48,25 +50,32 @@ impl<'i> super::ExecutableInstruction<'i> for Call<'i> {
|
||||
log_instruction!(call, exec_ctx, trace_ctx);
|
||||
|
||||
let resolved_call = joinable!(ResolvedCall::new(self, exec_ctx), exec_ctx).map_err(|e| {
|
||||
let instruction = format!("{:?}", self);
|
||||
let last_error = LastErrorDescriptor::new(e.clone(), instruction, None);
|
||||
exec_ctx.last_error = Some(last_error);
|
||||
|
||||
set_last_error(self, exec_ctx, e.clone(), None);
|
||||
e
|
||||
})?;
|
||||
|
||||
let triplet = resolved_call.as_triplet();
|
||||
joinable!(resolved_call.execute(exec_ctx, trace_ctx), exec_ctx).map_err(|e| {
|
||||
let tetraplet = SecurityTetraplet::from_triplet(triplet);
|
||||
let instruction = format!("{:?}", self);
|
||||
let last_error = LastErrorDescriptor::new(e.clone(), instruction, Some(tetraplet));
|
||||
exec_ctx.last_error = Some(last_error);
|
||||
set_last_error(self, exec_ctx, e.clone(), Some(tetraplet));
|
||||
|
||||
e
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn set_last_error<'i>(
|
||||
call: &Call<'i>,
|
||||
exec_ctx: &mut ExecutionCtx<'i>,
|
||||
e: Rc<ExecutionError>,
|
||||
tetraplet: Option<SecurityTetraplet>,
|
||||
) {
|
||||
let instruction = format!("{}", call);
|
||||
let last_error = LastErrorDescriptor::new(e, instruction, tetraplet);
|
||||
exec_ctx.last_error = Some(last_error);
|
||||
exec_ctx.last_error_could_be_set = false;
|
||||
}
|
||||
|
||||
macro_rules! log_join {
|
||||
($($args:tt)*) => {
|
||||
log::trace!(target: crate::log_targets::JOIN_BEHAVIOUR, $($args)*)
|
||||
|
@ -39,7 +39,12 @@ macro_rules! execute {
|
||||
($self:expr, $instr:expr, $exec_ctx:ident, $trace_ctx:ident) => {
|
||||
match $instr.execute($exec_ctx, $trace_ctx) {
|
||||
Err(e) => {
|
||||
let instruction = format!("{:?}", $self);
|
||||
if !$exec_ctx.last_error_could_be_set {
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
let instruction = format!("{}", $self);
|
||||
println!("set error on {}", instruction);
|
||||
let last_error = LastErrorDescriptor::new(e.clone(), instruction, None);
|
||||
$exec_ctx.last_error = Some(last_error);
|
||||
Err(e)
|
||||
|
@ -30,6 +30,8 @@ impl<'i> super::ExecutableInstruction<'i> for Xor<'i> {
|
||||
match self.0.execute(exec_ctx, trace_ctx) {
|
||||
Err(e) if is_catchable_by_xor(&e) => {
|
||||
exec_ctx.subtree_complete = true;
|
||||
exec_ctx.last_error_could_be_set = true;
|
||||
|
||||
self.1.execute(exec_ctx, trace_ctx)
|
||||
}
|
||||
res => res,
|
||||
@ -55,35 +57,10 @@ mod tests {
|
||||
|
||||
use aqua_test_utils::call_vm;
|
||||
use aqua_test_utils::create_aqua_vm;
|
||||
use aqua_test_utils::CallServiceClosure;
|
||||
use aqua_test_utils::IValue;
|
||||
use aqua_test_utils::NEVec;
|
||||
use aqua_test_utils::fallible_call_service;
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
fn fallible_call_service(fallible_service_id: impl Into<String>) -> CallServiceClosure {
|
||||
let fallible_service_id = fallible_service_id.into();
|
||||
|
||||
Box::new(move |_, args| -> Option<IValue> {
|
||||
let builtin_service = match &args[0] {
|
||||
IValue::String(str) => str,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
// return a error for service with such id
|
||||
if builtin_service == &fallible_service_id {
|
||||
Some(IValue::Record(
|
||||
NEVec::new(vec![IValue::S32(1), IValue::String(String::from("error"))]).unwrap(),
|
||||
))
|
||||
} else {
|
||||
// return success for services with other ids
|
||||
Some(IValue::Record(
|
||||
NEVec::new(vec![IValue::S32(0), IValue::String(String::from(r#""res""#))]).unwrap(),
|
||||
))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xor() {
|
||||
use crate::contexts::execution_trace::CallResult::*;
|
||||
@ -265,7 +242,7 @@ mod tests {
|
||||
let actual_trace: ExecutionTrace = serde_json::from_slice(&res.data).expect("should be valid json");
|
||||
|
||||
let expected_state = Call(Executed(Rc::new(JValue::String(String::from(
|
||||
"{\"error\":\"Local service error: ret_code is 1, error message is \'error\'\",\"instruction\":\"Call { peer_part: PeerPk(Literal(\\\"failible_peer_id\\\")), function_part: ServiceIdWithFuncName(Literal(\\\"service_id_1\\\"), Literal(\\\"local_fn_name\\\")), args: [], output: Scalar(\\\"result\\\") }\"}"
|
||||
"{\"error\":\"Local service error: ret_code is 1, error message is \'error\'\",\"instruction\":\"call \\\"failible_peer_id\\\" (\\\"service_id_1\\\" \\\"local_fn_name\\\") [] result\"}"
|
||||
)))));
|
||||
|
||||
assert_eq!(actual_trace[1], expected_state);
|
||||
|
81
stepper-lib/tests/last_error.rs
Normal file
81
stepper-lib/tests/last_error.rs
Normal file
@ -0,0 +1,81 @@
|
||||
/*
|
||||
* 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 aqua_test_utils::call_vm;
|
||||
use aqua_test_utils::create_aqua_vm;
|
||||
use aqua_test_utils::fallible_call_service;
|
||||
use aqua_test_utils::unit_call_service;
|
||||
use aqua_test_utils::CallServiceClosure;
|
||||
use aqua_test_utils::IValue;
|
||||
use aqua_test_utils::NEVec;
|
||||
use stepper_lib::SecurityTetraplet;
|
||||
|
||||
fn create_check_service_closure() -> CallServiceClosure {
|
||||
Box::new(move |_, args| -> Option<IValue> {
|
||||
let call_args = match &args[2] {
|
||||
IValue::String(str) => str,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let call_args: Vec<String> = serde_json::from_str(call_args).expect("json deserialization shouldn't fail");
|
||||
|
||||
let tetraplets = match &args[3] {
|
||||
IValue::String(str) => str,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let de_tetraplets: Vec<Vec<SecurityTetraplet>> =
|
||||
serde_json::from_str(tetraplets).expect("json deserialization shouldn't fail");
|
||||
|
||||
assert_eq!(
|
||||
call_args[0],
|
||||
r#"{"error":"Local service error: ret_code is 1, error message is 'error'","instruction":"call \"failible_peer_id\" ("falliable_call_service" "") [service_id] client_result"}"#
|
||||
);
|
||||
|
||||
let triplet = &de_tetraplets[0][0].triplet;
|
||||
assert_eq!(triplet.peer_pk, "failible_peer_id");
|
||||
assert_eq!(triplet.service_id, "failiable_call_service");
|
||||
assert_eq!(triplet.function_name, "");
|
||||
assert_eq!(de_tetraplets[0][0].json_path, "");
|
||||
|
||||
Some(IValue::Record(
|
||||
NEVec::new(vec![IValue::S32(0), IValue::String(tetraplets.clone())]).unwrap(),
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn last_error_tetraplets() {
|
||||
let set_variable_peer_id = "set_variable";
|
||||
let mut set_variable_vm = create_aqua_vm(unit_call_service(), set_variable_peer_id);
|
||||
|
||||
let faillible_peer_id = "failible_peer_id";
|
||||
let mut faillible_vm = create_aqua_vm(fallible_call_service("falliable_call_service"), faillible_peer_id);
|
||||
|
||||
let local_peer_id = "local_peer_id";
|
||||
let mut local_vm = create_aqua_vm(create_check_service_closure(), local_peer_id);
|
||||
|
||||
let script = format!(
|
||||
include_str!("scripts/create_service_with_xor.clj"),
|
||||
set_variable_peer_id, faillible_peer_id, local_peer_id
|
||||
);
|
||||
|
||||
let res = call_vm!(set_variable_vm, "asd", script.clone(), "", "");
|
||||
let res = call_vm!(faillible_vm, "asd", script.clone(), "", res.data);
|
||||
|
||||
// assert is done on the 'create_check_service_closure' call service closure
|
||||
let _ = call_vm!(local_vm, "asd", script, "", res.data);
|
||||
}
|
25
stepper-lib/tests/scripts/create_service_with_xor.clj
Normal file
25
stepper-lib/tests/scripts/create_service_with_xor.clj
Normal file
@ -0,0 +1,25 @@
|
||||
(seq
|
||||
(seq
|
||||
(seq
|
||||
(call "{0}" ("add_module" "") ["module_bytes"] module_bytes)
|
||||
(call "{0}" ("add_module" "") ["module_config"] module_config)
|
||||
)
|
||||
(call "{0}" ("add_module" "") ["blueprint"] blueprint)
|
||||
)
|
||||
(xor
|
||||
(seq
|
||||
(call "{0}" ("dist" "add_module") [module_bytes module_config] module)
|
||||
(seq
|
||||
(call "{0}" ("dist" "add_blueprint") [blueprint] blueprint_id)
|
||||
(seq
|
||||
(call "{0}" ("srv" "create") [blueprint_id] service_id)
|
||||
(call "{1}" ("failiable_call_service" "") [service_id] client_result)
|
||||
)
|
||||
)
|
||||
)
|
||||
(seq
|
||||
(call "{1}" ("op" "identity") ["XOR: create_greeting_service failed"] fail[])
|
||||
(call "{2}" ("return" "") [%last_error%])
|
||||
)
|
||||
)
|
||||
)
|
@ -11,10 +11,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "air-parser"
|
||||
version = "0.3.0"
|
||||
version = "0.4.0"
|
||||
dependencies = [
|
||||
"codespan",
|
||||
"codespan-reporting",
|
||||
"itertools 0.10.0",
|
||||
"lalrpop",
|
||||
"lalrpop-util",
|
||||
"regex",
|
||||
@ -412,6 +413,15 @@ dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37d572918e350e82412fe766d24b15e6682fb2ed2bbe018280caa810397cb319"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "0.4.7"
|
||||
@ -442,7 +452,7 @@ dependencies = [
|
||||
"diff",
|
||||
"docopt",
|
||||
"ena",
|
||||
"itertools",
|
||||
"itertools 0.9.0",
|
||||
"lalrpop-util",
|
||||
"petgraph",
|
||||
"regex",
|
||||
@ -673,7 +683,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "stepper-lib"
|
||||
version = "0.4.1"
|
||||
version = "0.5.0"
|
||||
dependencies = [
|
||||
"air-parser",
|
||||
"boolinator",
|
||||
|
@ -11,10 +11,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "air-parser"
|
||||
version = "0.3.0"
|
||||
version = "0.4.0"
|
||||
dependencies = [
|
||||
"codespan",
|
||||
"codespan-reporting",
|
||||
"itertools 0.10.0",
|
||||
"lalrpop",
|
||||
"lalrpop-util",
|
||||
"regex",
|
||||
@ -404,6 +405,15 @@ dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37d572918e350e82412fe766d24b15e6682fb2ed2bbe018280caa810397cb319"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "0.4.7"
|
||||
@ -434,7 +444,7 @@ dependencies = [
|
||||
"diff",
|
||||
"docopt",
|
||||
"ena",
|
||||
"itertools",
|
||||
"itertools 0.9.0",
|
||||
"lalrpop-util",
|
||||
"petgraph",
|
||||
"regex",
|
||||
@ -673,7 +683,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "stepper-lib"
|
||||
version = "0.4.1"
|
||||
version = "0.5.0"
|
||||
dependencies = [
|
||||
"air-parser",
|
||||
"boolinator",
|
||||
|
Loading…
Reference in New Issue
Block a user