From 48519be20871861029be8f7fabc4506065122937 Mon Sep 17 00:00:00 2001 From: vms Date: Tue, 24 Nov 2020 16:44:15 +0300 Subject: [PATCH] Introduce shadowing variables in fold (#33) --- Cargo.lock | 37 ++--- stepper-lib/src/air/call/utils.rs | 29 +++- stepper-lib/src/air/execution_context.rs | 5 + stepper-lib/src/air/fold.rs | 183 ++++++++++++++++++++++- stepper-lib/src/air/resolve.rs | 2 +- stepper-lib/src/errors.rs | 9 ++ stepper-lib/src/execution/prolog.rs | 2 +- 7 files changed, 237 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ef5eff32..12c765b0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -121,9 +121,9 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "base64" -version = "0.12.3" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" [[package]] name = "bincode" @@ -426,7 +426,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775" dependencies = [ "cfg-if 1.0.0", - "crossbeam-utils 0.8.0", + "crossbeam-utils", ] [[package]] @@ -437,7 +437,7 @@ checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9" dependencies = [ "cfg-if 1.0.0", "crossbeam-epoch", - "crossbeam-utils 0.8.0", + "crossbeam-utils", ] [[package]] @@ -448,23 +448,12 @@ checksum = "ec0f606a85340376eef0d6d8fec399e6d4a544d648386c6645eb6d0653b27d9f" dependencies = [ "cfg-if 1.0.0", "const_fn", - "crossbeam-utils 0.8.0", + "crossbeam-utils", "lazy_static", "memoffset", "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]] name = "crossbeam-utils" version = "0.8.0" @@ -1180,9 +1169,9 @@ checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0" [[package]] name = "oorandom" -version = "11.1.2" +version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a170cebd8021a008ea92e4db85a72f80b35df514ec664b296fdcbb654eac0b2c" +checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" [[package]] name = "opaque-debug" @@ -1407,7 +1396,7 @@ checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a" dependencies = [ "crossbeam-channel", "crossbeam-deque", - "crossbeam-utils 0.8.0", + "crossbeam-utils", "lazy_static", "num_cpus", ] @@ -1458,14 +1447,14 @@ checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189" [[package]] name = "rust-argon2" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dab61250775933275e84053ac235621dfb739556d5c54a2f2e9313b7cf43a19" +checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb" dependencies = [ "base64", "blake2b_simd", "constant_time_eq", - "crossbeam-utils 0.7.2", + "crossbeam-utils", ] [[package]] @@ -1659,9 +1648,9 @@ checksum = "343f3f510c2915908f155e94f17220b19ccfacf2a64a2a5d8004f2c3e311e7fd" [[package]] name = "syn" -version = "1.0.48" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc371affeffc477f42a221a1e4297aedcea33d47d19b61455588bd9d8f6b19ac" +checksum = "3b4f34193997d92804d359ed09953e25d5138df6bcc055a71bf68ee89fdf9223" dependencies = [ "proc-macro2", "quote", diff --git a/stepper-lib/src/air/call/utils.rs b/stepper-lib/src/air/call/utils.rs index 05cb5de9..7f8253c2 100644 --- a/stepper-lib/src/air/call/utils.rs +++ b/stepper-lib/src/air/call/utils.rs @@ -39,9 +39,34 @@ pub(super) fn set_local_call_result<'i>( match output { 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()) { - Vacant(entry) => entry.insert(AValue::JValueRef(result)), - Occupied(entry) => return Err(MultipleVariablesFound(entry.key().clone())), + Vacant(entry) => { + 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) => { diff --git a/stepper-lib/src/air/execution_context.rs b/stepper-lib/src/air/execution_context.rs index ad46e3a5..c06fa58e 100644 --- a/stepper-lib/src/air/execution_context.rs +++ b/stepper-lib/src/air/execution_context.rs @@ -17,6 +17,7 @@ use crate::AValue; use std::collections::HashMap; +use std::collections::VecDeque; use std::fmt::Display; use std::fmt::Formatter; @@ -42,6 +43,9 @@ pub(crate) struct ExecutionCtx<'i> { /// - all of seq subtrees are complete /// - call executes successfully (call evidence equals to Executed) 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> { @@ -52,6 +56,7 @@ impl<'i> ExecutionCtx<'i> { current_peer_id, init_peer_id, subtree_complete: true, + met_folds: VecDeque::new(), } } } diff --git a/stepper-lib/src/air/fold.rs b/stepper-lib/src/air/fold.rs index 3c077825..5e77c39c 100644 --- a/stepper-lib/src/air/fold.rs +++ b/stepper-lib/src/air/fold.rs @@ -26,6 +26,7 @@ use crate::Result; use air_parser::ast::{Fold, Next}; +use std::collections::HashMap; use std::rc::Rc; /* @@ -43,6 +44,8 @@ pub(crate) struct FoldState<'i> { pub(crate) cursor: usize, pub(crate) iterable: Rc, pub(crate) instr_head: Rc>, + // map of met variables inside this (not any inner) fold block with their initial values + pub(crate) met_variables: HashMap<&'i str, Rc>, } 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 iterable: Rc::new(iterable), instr_head: self.instruction.clone(), + met_variables: HashMap::new(), }; let previous_value = exec_ctx @@ -80,9 +84,33 @@ impl<'i> super::ExecutableInstruction<'i> for Fold<'i> { if previous_value.is_some() { return Err(MultipleFoldStates(self.iterator.to_string())); } + exec_ctx.met_folds.push_back(self.iterator); 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(()) } @@ -138,12 +166,13 @@ mod tests { use crate::call_evidence::CallEvidencePath; use crate::JValue; - use aqua_test_utils::call_vm; use aqua_test_utils::create_aqua_vm; use aqua_test_utils::echo_number_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::StepperError; + use aquamarine_vm::StepperOutcome; use serde_json::json; use std::rc::Rc; @@ -370,4 +399,154 @@ mod tests { 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 { + 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(_)))); + } + } } diff --git a/stepper-lib/src/air/resolve.rs b/stepper-lib/src/air/resolve.rs index a45778a7..c11be911 100644 --- a/stepper-lib/src/air/resolve.rs +++ b/stepper-lib/src/air/resolve.rs @@ -96,7 +96,7 @@ pub(crate) fn require_string(value: JValue) -> Result { } } -pub(crate) fn apply_json_path<'i>(jvalue: JValue, json_path: &'i str) -> Result { +pub(crate) fn apply_json_path(jvalue: JValue, json_path: &str) -> Result { let values = find_by_json_path(&jvalue, json_path)?; if values.is_empty() { return Err(AquamarineError::VariableNotFound(json_path.to_string())); diff --git a/stepper-lib/src/errors.rs b/stepper-lib/src/errors.rs index dd4bab09..e23f7195 100644 --- a/stepper-lib/src/errors.rs +++ b/stepper-lib/src/errors.rs @@ -88,6 +88,9 @@ pub enum AquamarineError { /// Errors occurred when evidence path contains less elements then corresponding Par has. EvidencePathTooSmall(usize, usize), + + /// Errors occurred when evidence path contains less elements then corresponding Par has. + ShadowingError(String), } impl Error for AquamarineError {} @@ -167,6 +170,11 @@ impl std::fmt::Display for AquamarineError { "evidence path remains {} elements, but {} requires by Par", 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 for AquamarineError { AquamarineError::IncompatibleEvidenceStates(..) => 18, AquamarineError::IncompatibleCallResults(..) => 19, AquamarineError::EvidencePathTooSmall(..) => 20, + AquamarineError::ShadowingError(_) => 21, }; StepperOutcome { diff --git a/stepper-lib/src/execution/prolog.rs b/stepper-lib/src/execution/prolog.rs index c5ec1285..69d79b75 100644 --- a/stepper-lib/src/execution/prolog.rs +++ b/stepper-lib/src/execution/prolog.rs @@ -46,7 +46,7 @@ pub(super) fn prepare<'i>( let prev_path = to_evidence_path(raw_prev_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!( target: RUN_PARAMS,