Introduce shadowing variables in fold (#33)

This commit is contained in:
vms 2020-11-24 16:44:15 +03:00 committed by GitHub
parent dde12e2a40
commit 48519be208
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 237 additions and 30 deletions

37
Cargo.lock generated
View File

@ -121,9 +121,9 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]] [[package]]
name = "base64" name = "base64"
version = "0.12.3" version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
[[package]] [[package]]
name = "bincode" name = "bincode"
@ -426,7 +426,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775" checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"crossbeam-utils 0.8.0", "crossbeam-utils",
] ]
[[package]] [[package]]
@ -437,7 +437,7 @@ checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"crossbeam-epoch", "crossbeam-epoch",
"crossbeam-utils 0.8.0", "crossbeam-utils",
] ]
[[package]] [[package]]
@ -448,23 +448,12 @@ checksum = "ec0f606a85340376eef0d6d8fec399e6d4a544d648386c6645eb6d0653b27d9f"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"const_fn", "const_fn",
"crossbeam-utils 0.8.0", "crossbeam-utils",
"lazy_static", "lazy_static",
"memoffset", "memoffset",
"scopeguard", "scopeguard",
] ]
[[package]]
name = "crossbeam-utils"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
dependencies = [
"autocfg",
"cfg-if 0.1.10",
"lazy_static",
]
[[package]] [[package]]
name = "crossbeam-utils" name = "crossbeam-utils"
version = "0.8.0" version = "0.8.0"
@ -1180,9 +1169,9 @@ checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0"
[[package]] [[package]]
name = "oorandom" name = "oorandom"
version = "11.1.2" version = "11.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a170cebd8021a008ea92e4db85a72f80b35df514ec664b296fdcbb654eac0b2c" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
[[package]] [[package]]
name = "opaque-debug" name = "opaque-debug"
@ -1407,7 +1396,7 @@ checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a"
dependencies = [ dependencies = [
"crossbeam-channel", "crossbeam-channel",
"crossbeam-deque", "crossbeam-deque",
"crossbeam-utils 0.8.0", "crossbeam-utils",
"lazy_static", "lazy_static",
"num_cpus", "num_cpus",
] ]
@ -1458,14 +1447,14 @@ checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189"
[[package]] [[package]]
name = "rust-argon2" name = "rust-argon2"
version = "0.8.2" version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dab61250775933275e84053ac235621dfb739556d5c54a2f2e9313b7cf43a19" checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb"
dependencies = [ dependencies = [
"base64", "base64",
"blake2b_simd", "blake2b_simd",
"constant_time_eq", "constant_time_eq",
"crossbeam-utils 0.7.2", "crossbeam-utils",
] ]
[[package]] [[package]]
@ -1659,9 +1648,9 @@ checksum = "343f3f510c2915908f155e94f17220b19ccfacf2a64a2a5d8004f2c3e311e7fd"
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.48" version = "1.0.51"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc371affeffc477f42a221a1e4297aedcea33d47d19b61455588bd9d8f6b19ac" checksum = "3b4f34193997d92804d359ed09953e25d5138df6bcc055a71bf68ee89fdf9223"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View File

@ -39,9 +39,34 @@ pub(super) fn set_local_call_result<'i>(
match output { match output {
CallOutput::Scalar(name) => { CallOutput::Scalar(name) => {
if let Some(fold_block_name) = exec_ctx.met_folds.back() {
let fold_state = match exec_ctx.data_cache.get_mut(*fold_block_name) {
Some(AValue::JValueFoldCursor(fold_state)) => fold_state,
_ => unreachable!("fold block data must be represented as fold cursor"),
};
fold_state.met_variables.insert(name, result.clone());
}
match exec_ctx.data_cache.entry(name.to_string()) { match exec_ctx.data_cache.entry(name.to_string()) {
Vacant(entry) => entry.insert(AValue::JValueRef(result)), Vacant(entry) => {
Occupied(entry) => return Err(MultipleVariablesFound(entry.key().clone())), entry.insert(AValue::JValueRef(result));
}
Occupied(mut entry) => {
// check that current execution flow is inside a fold block
if exec_ctx.met_folds.is_empty() {
// shadowing is allowed only inside fold blocks
return Err(MultipleVariablesFound(entry.key().clone()));
}
match entry.get() {
AValue::JValueRef(_) => {}
// shadowing is allowed only for scalar values
_ => return Err(ShadowingError(entry.key().clone())),
};
entry.insert(AValue::JValueRef(result));
}
}; };
} }
CallOutput::Accumulator(name) => { CallOutput::Accumulator(name) => {

View File

@ -17,6 +17,7 @@
use crate::AValue; use crate::AValue;
use std::collections::HashMap; use std::collections::HashMap;
use std::collections::VecDeque;
use std::fmt::Display; use std::fmt::Display;
use std::fmt::Formatter; use std::fmt::Formatter;
@ -42,6 +43,9 @@ pub(crate) struct ExecutionCtx<'i> {
/// - all of seq subtrees are complete /// - all of seq subtrees are complete
/// - call executes successfully (call evidence equals to Executed) /// - call executes successfully (call evidence equals to Executed)
pub subtree_complete: bool, pub subtree_complete: bool,
/// List of met folds used to determine whether a variable can be shadowed.
pub met_folds: VecDeque<&'i str>,
} }
impl<'i> ExecutionCtx<'i> { impl<'i> ExecutionCtx<'i> {
@ -52,6 +56,7 @@ impl<'i> ExecutionCtx<'i> {
current_peer_id, current_peer_id,
init_peer_id, init_peer_id,
subtree_complete: true, subtree_complete: true,
met_folds: VecDeque::new(),
} }
} }
} }

View File

@ -26,6 +26,7 @@ use crate::Result;
use air_parser::ast::{Fold, Next}; use air_parser::ast::{Fold, Next};
use std::collections::HashMap;
use std::rc::Rc; use std::rc::Rc;
/* /*
@ -43,6 +44,8 @@ pub(crate) struct FoldState<'i> {
pub(crate) cursor: usize, pub(crate) cursor: usize,
pub(crate) iterable: Rc<JValue>, pub(crate) iterable: Rc<JValue>,
pub(crate) instr_head: Rc<Instruction<'i>>, pub(crate) instr_head: Rc<Instruction<'i>>,
// map of met variables inside this (not any inner) fold block with their initial values
pub(crate) met_variables: HashMap<&'i str, Rc<JValue>>,
} }
impl<'i> super::ExecutableInstruction<'i> for Fold<'i> { impl<'i> super::ExecutableInstruction<'i> for Fold<'i> {
@ -71,6 +74,7 @@ impl<'i> super::ExecutableInstruction<'i> for Fold<'i> {
// TODO: reuse existing Rc from JValueRef, if there was some // TODO: reuse existing Rc from JValueRef, if there was some
iterable: Rc::new(iterable), iterable: Rc::new(iterable),
instr_head: self.instruction.clone(), instr_head: self.instruction.clone(),
met_variables: HashMap::new(),
}; };
let previous_value = exec_ctx let previous_value = exec_ctx
@ -80,9 +84,33 @@ impl<'i> super::ExecutableInstruction<'i> for Fold<'i> {
if previous_value.is_some() { if previous_value.is_some() {
return Err(MultipleFoldStates(self.iterator.to_string())); return Err(MultipleFoldStates(self.iterator.to_string()));
} }
exec_ctx.met_folds.push_back(self.iterator);
self.instruction.execute(exec_ctx, call_ctx)?; self.instruction.execute(exec_ctx, call_ctx)?;
exec_ctx.data_cache.remove(self.iterator);
let fold_state = match exec_ctx.data_cache.remove(self.iterator) {
Some(AValue::JValueFoldCursor(fold_state)) => fold_state,
_ => unreachable!("fold cursor is changed only inside fold block"),
};
for (variable_name, _) in fold_state.met_variables {
exec_ctx.data_cache.remove(variable_name);
}
exec_ctx.met_folds.pop_back();
if let Some(fold_block_name) = exec_ctx.met_folds.back() {
let fold_state = match exec_ctx.data_cache.get(*fold_block_name) {
Some(AValue::JValueFoldCursor(fold_state)) => fold_state,
_ => unreachable!("fold block data must be represented as fold cursor"),
};
let mut upper_fold_values = HashMap::new();
for (variable_name, variable) in fold_state.met_variables.iter() {
upper_fold_values.insert(variable_name.to_string(), AValue::JValueRef(variable.clone()));
}
exec_ctx.data_cache.extend(upper_fold_values);
}
Ok(()) Ok(())
} }
@ -138,12 +166,13 @@ mod tests {
use crate::call_evidence::CallEvidencePath; use crate::call_evidence::CallEvidencePath;
use crate::JValue; use crate::JValue;
use aqua_test_utils::call_vm;
use aqua_test_utils::create_aqua_vm; use aqua_test_utils::create_aqua_vm;
use aqua_test_utils::echo_number_call_service; use aqua_test_utils::echo_number_call_service;
use aqua_test_utils::set_variable_call_service; use aqua_test_utils::set_variable_call_service;
use aqua_test_utils::{call_vm, echo_string_call_service};
use aquamarine_vm::AquamarineVMError; use aquamarine_vm::AquamarineVMError;
use aquamarine_vm::StepperError; use aquamarine_vm::StepperError;
use aquamarine_vm::StepperOutcome;
use serde_json::json; use serde_json::json;
use std::rc::Rc; use std::rc::Rc;
@ -370,4 +399,154 @@ mod tests {
assert_eq!(res[i], Call(Executed(Rc::new(JValue::Number(i.into()))))); assert_eq!(res[i], Call(Executed(Rc::new(JValue::Number(i.into())))));
} }
} }
#[test]
fn shadowing() {
use crate::call_evidence::CallResult::*;
use crate::call_evidence::EvidenceState::*;
let mut set_variables_vm = create_aqua_vm(set_variable_call_service(r#"["1","2"]"#), "set_variable");
let mut vm_a = create_aqua_vm(echo_string_call_service(), "A");
let mut vm_b = create_aqua_vm(echo_string_call_service(), "B");
let script = String::from(
r#"
(seq
(seq
(call "set_variable" ("" "") [] Iterable1)
(call "set_variable" ("" "") [] Iterable2)
)
(fold Iterable1 i
(seq
(seq
(fold Iterable2 j
(seq
(seq
(call "A" ("" "") [i] local_j)
(call "B" ("" "") [local_j])
)
(next j)
)
)
(par
(call "A" ("" "") [i] local_i)
(call "B" ("" "") [i])
)
)
(next i)
)
)
)"#,
);
let res = call_vm!(set_variables_vm, "", script.clone(), "[]", "[]");
let res = call_vm!(vm_a, "", script.clone(), "[]", res.data);
let res = call_vm!(vm_b, "", script.clone(), "[]", res.data);
let res = call_vm!(vm_a, "", script.clone(), "[]", res.data);
let res = call_vm!(vm_b, "", script.clone(), "[]", res.data);
let res = call_vm!(vm_a, "", script.clone(), "[]", res.data);
let res = call_vm!(vm_b, "", script, "[]", res.data);
let res: CallEvidencePath = serde_json::from_str(&res.data).expect("should be valid call evidence path");
assert_eq!(res.len(), 12);
for i in 2..11 {
assert!(matches!(res[i], Call(Executed(_))) || matches!(res[i], Par(..)));
}
}
#[test]
fn shadowing_scope() {
use crate::call_evidence::CallResult::*;
use crate::call_evidence::EvidenceState::*;
fn execute_script(script: String) -> Result<StepperOutcome, AquamarineVMError> {
let mut set_variables_vm = create_aqua_vm(set_variable_call_service(r#"["1","2"]"#), "set_variable");
let mut vm_a = create_aqua_vm(echo_string_call_service(), "A");
let mut vm_b = create_aqua_vm(echo_string_call_service(), "B");
let res = call_vm!(set_variables_vm, "", script.clone(), "[]", "[]");
let res = call_vm!(vm_a, "", script.clone(), "[]", res.data);
let res = call_vm!(vm_b, "", script.clone(), "[]", res.data);
let res = call_vm!(vm_a, "", script.clone(), "[]", res.data);
let res = call_vm!(vm_b, "", script.clone(), "[]", res.data);
vm_a.call_with_prev_data("", script, "[]", res.data)
}
let use_non_exist_variable_script = String::from(
r#"
(seq
(seq
(call "set_variable" ("" "") [] Iterable1)
(call "set_variable" ("" "") [] Iterable2)
)
(fold Iterable1 i
(seq
(seq
(fold Iterable2 j
(seq
(seq
(call "A" ("" "") [i] local_j)
(call "B" ("" "") [local_j])
)
(next j)
)
)
(call "A" ("" "") [local_j])
)
(next i)
)
)
)"#,
);
let res = execute_script(use_non_exist_variable_script);
assert!(res.is_err());
let error = res.err().unwrap();
let error = match error {
AquamarineVMError::StepperError(error) => error,
_ => unreachable!(),
};
assert!(matches!(error, StepperError::VariableNotFound(_)));
let variable_shadowing_script = String::from(
r#"
(seq
(seq
(call "set_variable" ("" "") [] Iterable1)
(call "set_variable" ("" "") [] Iterable2)
)
(fold Iterable1 i
(seq
(seq
(call "A" ("" "") ["value"] local_j)
(seq
(fold Iterable2 j
(seq
(seq
(call "A" ("" "") [i] local_j)
(call "B" ("" "") [local_j])
)
(next j)
)
)
(call "A" ("" "") [local_j])
)
)
(next i)
)
)
)"#,
);
let res = execute_script(variable_shadowing_script).unwrap();
let res: CallEvidencePath = serde_json::from_str(&res.data).expect("should be valid call evidence path");
assert_eq!(res.len(), 11);
for i in 0..10 {
assert!(matches!(res[i], Call(Executed(_))));
}
}
} }

View File

@ -96,7 +96,7 @@ pub(crate) fn require_string(value: JValue) -> Result<String> {
} }
} }
pub(crate) fn apply_json_path<'i>(jvalue: JValue, json_path: &'i str) -> Result<JValue> { pub(crate) fn apply_json_path(jvalue: JValue, json_path: &str) -> Result<JValue> {
let values = find_by_json_path(&jvalue, json_path)?; let values = find_by_json_path(&jvalue, json_path)?;
if values.is_empty() { if values.is_empty() {
return Err(AquamarineError::VariableNotFound(json_path.to_string())); return Err(AquamarineError::VariableNotFound(json_path.to_string()));

View File

@ -88,6 +88,9 @@ pub enum AquamarineError {
/// Errors occurred when evidence path contains less elements then corresponding Par has. /// Errors occurred when evidence path contains less elements then corresponding Par has.
EvidencePathTooSmall(usize, usize), EvidencePathTooSmall(usize, usize),
/// Errors occurred when evidence path contains less elements then corresponding Par has.
ShadowingError(String),
} }
impl Error for AquamarineError {} impl Error for AquamarineError {}
@ -167,6 +170,11 @@ impl std::fmt::Display for AquamarineError {
"evidence path remains {} elements, but {} requires by Par", "evidence path remains {} elements, but {} requires by Par",
actual_count, desired_count actual_count, desired_count
), ),
AquamarineError::ShadowingError(variable_name) => write!(
f,
"vairable with name = '{}' can't be shadowed, shadowing is supported only for scalar values",
variable_name
),
} }
} }
} }
@ -200,6 +208,7 @@ impl Into<StepperOutcome> for AquamarineError {
AquamarineError::IncompatibleEvidenceStates(..) => 18, AquamarineError::IncompatibleEvidenceStates(..) => 18,
AquamarineError::IncompatibleCallResults(..) => 19, AquamarineError::IncompatibleCallResults(..) => 19,
AquamarineError::EvidencePathTooSmall(..) => 20, AquamarineError::EvidencePathTooSmall(..) => 20,
AquamarineError::ShadowingError(_) => 21,
}; };
StepperOutcome { StepperOutcome {

View File

@ -46,7 +46,7 @@ pub(super) fn prepare<'i>(
let prev_path = to_evidence_path(raw_prev_path)?; let prev_path = to_evidence_path(raw_prev_path)?;
let path = to_evidence_path(raw_path)?; let path = to_evidence_path(raw_path)?;
let aqua: Instruction<'i> = *air_parser::parse(raw_aqua).map_err(|msg| AquamarineError::AIRParseError(msg))?; let aqua: Instruction<'i> = *air_parser::parse(raw_aqua).map_err(AquamarineError::AIRParseError)?;
log::trace!( log::trace!(
target: RUN_PARAMS, target: RUN_PARAMS,