Introduce mismatch (#62)

This commit is contained in:
vms 2021-02-01 18:53:00 +03:00 committed by GitHub
parent 611bee0836
commit 709b5e0a52
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 810 additions and 391 deletions

4
Cargo.lock generated
View File

@ -57,7 +57,7 @@ dependencies = [
[[package]]
name = "aquamarine"
version = "0.3.0"
version = "0.4.1"
dependencies = [
"fluence",
"log",
@ -1693,7 +1693,7 @@ dependencies = [
[[package]]
name = "stepper-lib"
version = "0.4.0"
version = "0.4.1"
dependencies = [
"air-parser",
"aqua-test-utils",

View File

@ -31,7 +31,15 @@ Instr: Box<Instruction<'input>> = {
"(" xor <l:Instr> <r:Instr> ")" => Box::new(Instruction::Xor(Xor(l, r))),
"(" match_ <l:Matchable> <r:Matchable> <i:Instr> ")" => Box::new(Instruction::Match(Match(l, r, i))),
"(" match_ <l:Matchable> <r:Matchable> <i:Instr> ")" => {
let match_ = Match { left_value: l, right_value: r, instruction: i};
Box::new(Instruction::Match(match_))
},
"(" mismatch <l:Matchable> <r:Matchable> <i:Instr> ")" => {
let mismatch = MisMatch { left_value: l, right_value: r, instruction: i};
Box::new(Instruction::MisMatch(mismatch))
},
! => { errors.push(<>); Box::new(Instruction::Error) },
}
@ -112,5 +120,6 @@ extern {
xor => Token::Xor,
next => Token::Next,
match_ => Token::Match,
mismatch => Token::MisMatch,
}
}

File diff suppressed because it is too large Load Diff

View File

@ -28,6 +28,7 @@ pub enum Instruction<'i> {
Par(Par<'i>),
Xor(Xor<'i>),
Match(Match<'i>),
MisMatch(MisMatch<'i>),
Fold(Fold<'i>),
Next(Next<'i>),
Error,
@ -91,11 +92,18 @@ pub struct Par<'i>(pub Box<Instruction<'i>>, pub Box<Instruction<'i>>);
pub struct Xor<'i>(pub Box<Instruction<'i>>, pub Box<Instruction<'i>>);
#[derive(Serialize, Debug, PartialEq, Eq)]
pub struct Match<'i>(
pub MatchableValue<'i>,
pub MatchableValue<'i>,
pub Box<Instruction<'i>>,
);
pub struct Match<'i> {
pub left_value: MatchableValue<'i>,
pub right_value: MatchableValue<'i>,
pub instruction: Box<Instruction<'i>>,
}
#[derive(Serialize, Debug, PartialEq, Eq)]
pub struct MisMatch<'i> {
pub left_value: MatchableValue<'i>,
pub right_value: MatchableValue<'i>,
pub instruction: Box<Instruction<'i>>,
}
#[derive(Serialize, Debug, PartialEq, Eq)]
pub struct Fold<'i> {

View File

@ -181,6 +181,7 @@ fn string_to_token(input: &str, start_pos: usize) -> Result<Token, LexerError> {
XOR_INSTR => Ok(Token::Xor),
NEXT_INSTR => Ok(Token::Next),
MATCH_INSTR => Ok(Token::Match),
MISMATCH_INSTR => Ok(Token::MisMatch),
INIT_PEER_ID => Ok(Token::InitPeerId),
@ -236,6 +237,7 @@ const FOLD_INSTR: &str = "fold";
const XOR_INSTR: &str = "xor";
const NEXT_INSTR: &str = "next";
const MATCH_INSTR: &str = "match";
const MISMATCH_INSTR: &str = "mismatch";
const INIT_PEER_ID: &str = "%init_peer_id%";

View File

@ -103,6 +103,32 @@ fn air_instructions() {
Ok((5, Token::CloseRoundBracket, 6))
]
);
let match_tokens = run_lexer("match");
assert_eq!(match_tokens, vec![Ok((0, Token::Match, 5))]);
let match_tokens = run_lexer("(match)");
assert_eq!(
match_tokens,
vec![
Ok((0, Token::OpenRoundBracket, 1)),
Ok((1, Token::Match, 6)),
Ok((6, Token::CloseRoundBracket, 7))
]
);
let mismatch_tokens = run_lexer("mismatch");
assert_eq!(mismatch_tokens, vec![Ok((0, Token::MisMatch, 8))]);
let mismatch_tokens = run_lexer("(mismatch)");
assert_eq!(
mismatch_tokens,
vec![
Ok((0, Token::OpenRoundBracket, 1)),
Ok((1, Token::MisMatch, 9)),
Ok((9, Token::CloseRoundBracket, 10))
]
);
}
#[test]

View File

@ -36,4 +36,5 @@ pub enum Token<'input> {
Xor,
Next,
Match,
MisMatch,
}

View File

@ -244,6 +244,34 @@ fn parse_fold() {
assert_eq!(instruction, expected);
}
#[test]
fn parse_match() {
use ast::MatchableValue::Variable;
let source_code = r#"
(match v1 v2
(null)
)
"#;
let instruction = parse(&source_code.as_ref());
let expected = match_(Variable("v1"), Variable("v2"), null());
assert_eq!(instruction, expected);
}
#[test]
fn parse_mismatch() {
use ast::MatchableValue::Variable;
let source_code = r#"
(mismatch v1 v2
(null)
)
"#;
let instruction = parse(&source_code.as_ref());
let expected = mismatch(Variable("v1"), Variable("v2"), null());
assert_eq!(instruction, expected);
}
fn source_fold_with(name: &str) -> String {
f!(r#"(fold iterable i
({name} (null) (null))
@ -516,18 +544,23 @@ fn comments() {
fn seq<'a>(l: Instruction<'a>, r: Instruction<'a>) -> Instruction<'a> {
Instruction::Seq(ast::Seq(Box::new(l), Box::new(r)))
}
fn par<'a>(l: Instruction<'a>, r: Instruction<'a>) -> Instruction<'a> {
Instruction::Par(ast::Par(Box::new(l), Box::new(r)))
}
fn xor<'a>(l: Instruction<'a>, r: Instruction<'a>) -> Instruction<'a> {
Instruction::Xor(ast::Xor(Box::new(l), Box::new(r)))
}
fn seqnn() -> Instruction<'static> {
seq(null(), null())
}
fn null() -> Instruction<'static> {
Instruction::Null(ast::Null)
}
fn fold<'a>(
iterable: ast::IterableValue<'a>,
iterator: &'a str,
@ -539,6 +572,31 @@ fn fold<'a>(
instruction: std::rc::Rc::new(instruction),
})
}
fn match_<'a>(
left_value: ast::MatchableValue<'a>,
right_value: ast::MatchableValue<'a>,
instruction: Instruction<'a>,
) -> Instruction<'a> {
Instruction::Match(ast::Match {
left_value,
right_value,
instruction: Box::new(instruction),
})
}
fn mismatch<'a>(
left_value: ast::MatchableValue<'a>,
right_value: ast::MatchableValue<'a>,
instruction: Instruction<'a>,
) -> Instruction<'a> {
Instruction::MisMatch(ast::MisMatch {
left_value,
right_value,
instruction: Box::new(instruction),
})
}
fn binary_instruction<'a, 'b>(
name: &'a str,
) -> impl Fn(Instruction<'b>, Instruction<'b>) -> Instruction<'b> {

View File

@ -0,0 +1,90 @@
/*
* Copyright 2020 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use crate::contexts::execution::ExecutionCtx;
use crate::execution::air::ExecutionResult;
use crate::execution::utils::resolve_to_jvaluable;
use crate::JValue;
use air_parser::ast::MatchableValue;
pub(crate) fn are_matchable_eq<'ctx>(
left: &MatchableValue<'_>,
right: &MatchableValue<'_>,
exec_ctx: &'ctx ExecutionCtx<'_>,
) -> ExecutionResult<bool> {
use MatchableValue::*;
match (left, right) {
(Literal(name), matchable) => compare_matchable_and_literal(matchable, name, exec_ctx),
(matchable, Literal(name)) => compare_matchable_and_literal(matchable, name, exec_ctx),
(Variable(left_name), Variable(right_name)) => {
let left_jvaluable = resolve_to_jvaluable(left_name, exec_ctx)?;
let left_value = left_jvaluable.as_jvalue();
let right_jvaluable = resolve_to_jvaluable(right_name, exec_ctx)?;
let right_value = right_jvaluable.as_jvalue();
Ok(left_value == right_value)
}
(JsonPath { variable: lv, path: lp }, JsonPath { variable: rv, path: rp }) => {
let left_jvaluable = resolve_to_jvaluable(lv, exec_ctx)?;
let left_value = left_jvaluable.apply_json_path(lp)?;
let right_jvaluable = resolve_to_jvaluable(rv, exec_ctx)?;
let right_value = right_jvaluable.apply_json_path(rp)?;
Ok(left_value == right_value)
}
_ => Ok(false),
}
}
fn compare_matchable_and_literal<'ctx>(
matchable: &MatchableValue<'_>,
string_literal: &str,
exec_ctx: &'ctx ExecutionCtx<'_>,
) -> ExecutionResult<bool> {
use std::borrow::Cow;
use MatchableValue::*;
fn compare_jvalue_and_literal(jvalue: Cow<'_, JValue>, string_literal: &str) -> bool {
use std::ops::Deref;
match jvalue.deref() {
JValue::String(value) => value == string_literal,
_ => false,
}
}
match matchable {
Literal(name) => Ok(name == &string_literal),
Variable(name) => {
let jvaluable = resolve_to_jvaluable(name, exec_ctx)?;
let jvalue = jvaluable.as_jvalue();
Ok(compare_jvalue_and_literal(jvalue, string_literal))
}
JsonPath { variable, path } => {
let jvaluable = resolve_to_jvaluable(variable, exec_ctx)?;
let jvalues = jvaluable.apply_json_path(path)?;
if jvalues.len() != 1 {
return Ok(false);
}
Ok(compare_jvalue_and_literal(Cow::Borrowed(jvalues[0]), string_literal))
}
}
}

View File

@ -0,0 +1,19 @@
/*
* 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 compare_matchable;
pub(super) use compare_matchable::are_matchable_eq;

View File

@ -14,6 +14,7 @@
* limitations under the License.
*/
use super::compare_matchable::are_matchable_eq;
use super::ExecutionCtx;
use super::ExecutionError;
use super::ExecutionResult;
@ -21,92 +22,18 @@ use super::ExecutionTraceCtx;
use crate::log_instruction;
use air_parser::ast::Match;
use air_parser::ast::MatchableValue;
impl<'i> super::ExecutableInstruction<'i> for Match<'i> {
fn execute(&self, exec_ctx: &mut ExecutionCtx<'i>, trace_ctx: &mut ExecutionTraceCtx) -> ExecutionResult<()> {
log_instruction!(match_, exec_ctx, trace_ctx);
let left_value = &self.0;
let right_value = &self.1;
let is_equal_values = compare_matchable(left_value, right_value, exec_ctx)?;
let are_values_equal = are_matchable_eq(&self.left_value, &self.right_value, exec_ctx)?;
if !is_equal_values {
if !are_values_equal {
return Err(ExecutionError::MatchWithoutXorError);
}
self.2.execute(exec_ctx, trace_ctx)
}
}
use crate::execution::utils::resolve_to_jvaluable;
use crate::JValue;
fn compare_matchable<'ctx>(
left: &MatchableValue<'_>,
right: &MatchableValue<'_>,
exec_ctx: &'ctx ExecutionCtx<'_>,
) -> ExecutionResult<bool> {
use MatchableValue::*;
match (left, right) {
(Literal(name), matchable) => compare_matchable_and_literal(matchable, name, exec_ctx),
(matchable, Literal(name)) => compare_matchable_and_literal(matchable, name, exec_ctx),
(Variable(left_name), Variable(right_name)) => {
let left_jvaluable = resolve_to_jvaluable(left_name, exec_ctx)?;
let left_value = left_jvaluable.as_jvalue();
let right_jvaluable = resolve_to_jvaluable(right_name, exec_ctx)?;
let right_value = right_jvaluable.as_jvalue();
Ok(left_value == right_value)
}
(JsonPath { variable: lv, path: lp }, JsonPath { variable: rv, path: rp }) => {
let left_jvaluable = resolve_to_jvaluable(lv, exec_ctx)?;
let left_value = left_jvaluable.apply_json_path(lp)?;
let right_jvaluable = resolve_to_jvaluable(rv, exec_ctx)?;
let right_value = right_jvaluable.apply_json_path(rp)?;
Ok(left_value == right_value)
}
_ => Ok(false),
}
}
fn compare_matchable_and_literal<'ctx>(
matchable: &MatchableValue<'_>,
string_literal: &str,
exec_ctx: &'ctx ExecutionCtx<'_>,
) -> ExecutionResult<bool> {
use std::borrow::Cow;
use MatchableValue::*;
fn compare_jvalue_and_literal(jvalue: Cow<'_, JValue>, string_literal: &str) -> bool {
use std::ops::Deref;
match jvalue.deref() {
JValue::String(value) => value == string_literal,
_ => false,
}
}
match matchable {
Literal(name) => Ok(name == &string_literal),
Variable(name) => {
let jvaluable = resolve_to_jvaluable(name, exec_ctx)?;
let jvalue = jvaluable.as_jvalue();
Ok(compare_jvalue_and_literal(jvalue, string_literal))
}
JsonPath { variable, path } => {
let jvaluable = resolve_to_jvaluable(variable, exec_ctx)?;
let jvalues = jvaluable.apply_json_path(path)?;
if jvalues.len() != 1 {
return Ok(false);
}
Ok(compare_jvalue_and_literal(Cow::Borrowed(jvalues[0]), string_literal))
}
self.instruction.execute(exec_ctx, trace_ctx)
}
}

View File

@ -0,0 +1,193 @@
/*
* 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::compare_matchable::are_matchable_eq;
use super::ExecutionCtx;
use super::ExecutionError;
use super::ExecutionResult;
use super::ExecutionTraceCtx;
use crate::log_instruction;
use air_parser::ast::MisMatch;
impl<'i> super::ExecutableInstruction<'i> for MisMatch<'i> {
fn execute(&self, exec_ctx: &mut ExecutionCtx<'i>, trace_ctx: &mut ExecutionTraceCtx) -> ExecutionResult<()> {
log_instruction!(match_, exec_ctx, trace_ctx);
let are_values_equal = are_matchable_eq(&self.left_value, &self.right_value, exec_ctx)?;
if are_values_equal {
return Err(ExecutionError::MatchWithoutXorError);
}
self.instruction.execute(exec_ctx, trace_ctx)
}
}
#[cfg(test)]
mod tests {
use crate::contexts::execution_trace::ExecutionTrace;
use crate::JValue;
use aqua_test_utils::call_vm;
use aqua_test_utils::create_aqua_vm;
use aqua_test_utils::echo_string_call_service;
use std::rc::Rc;
#[test]
fn mismatch_equal() {
use crate::contexts::execution_trace::CallResult::*;
use crate::contexts::execution_trace::ExecutedState::*;
let set_variable_peer_id = "set_variable_peer_id";
let mut set_variable_vm = create_aqua_vm(echo_string_call_service(), set_variable_peer_id);
let local_peer_id = "local_peer_id";
let mut vm = create_aqua_vm(echo_string_call_service(), local_peer_id);
let script = format!(
r#"
(seq
(seq
(call "{0}" ("" "") ["value_1"] value_1)
(call "{0}" ("" "") ["value_1"] value_2)
)
(xor
(mismatch value_1 value_2
(call "{1}" ("service_id_2" "local_fn_name") ["result_1"] result_1)
)
(call "{1}" ("service_id_2" "local_fn_name") ["result_2"] result_2)
)
)"#,
set_variable_peer_id, local_peer_id
);
let res = call_vm!(set_variable_vm, "asd", script.clone(), "", "");
let res = call_vm!(vm, "asd", script, "", res.data);
let actual_trace: ExecutionTrace = serde_json::from_slice(&res.data).expect("should be valid json");
let expected_executed_call_result = Call(Executed(Rc::new(JValue::String(String::from("result_2")))));
assert_eq!(actual_trace.len(), 3);
assert_eq!(actual_trace[2], expected_executed_call_result);
}
#[test]
fn mismatch_not_equal() {
use crate::contexts::execution_trace::CallResult::*;
use crate::contexts::execution_trace::ExecutedState::*;
let set_variable_peer_id = "set_variable_peer_id";
let mut set_variable_vm = create_aqua_vm(echo_string_call_service(), set_variable_peer_id);
let local_peer_id = "local_peer_id";
let mut vm = create_aqua_vm(echo_string_call_service(), local_peer_id);
let script = format!(
r#"
(seq
(seq
(call "{0}" ("" "") ["value_1"] value_1)
(call "{0}" ("" "") ["value_2"] value_2)
)
(xor
(mismatch value_1 value_2
(call "{1}" ("service_id_2" "local_fn_name") ["result_1"] result_1)
)
(call "{1}" ("service_id_2" "local_fn_name") ["result_2"] result_2)
)
)"#,
set_variable_peer_id, local_peer_id
);
let res = call_vm!(set_variable_vm, "asd", script.clone(), "", "");
let res = call_vm!(vm, "asd", script, "", res.data);
let actual_trace: ExecutionTrace = serde_json::from_slice(&res.data).expect("should be valid json");
let expected_executed_call_result = Call(Executed(Rc::new(JValue::String(String::from("result_1")))));
assert_eq!(actual_trace.len(), 3);
assert_eq!(actual_trace[2], expected_executed_call_result);
}
#[test]
fn mismatch_with_string() {
use crate::contexts::execution_trace::CallResult::*;
use crate::contexts::execution_trace::ExecutedState::*;
let set_variable_peer_id = "set_variable_peer_id";
let mut set_variable_vm = create_aqua_vm(echo_string_call_service(), set_variable_peer_id);
let local_peer_id = "local_peer_id";
let mut vm = create_aqua_vm(echo_string_call_service(), local_peer_id);
let script = format!(
r#"
(seq
(call "{0}" ("" "") ["value_1"] value_1)
(xor
(mismatch value_1 "value_1"
(call "{1}" ("service_id_2" "local_fn_name") ["result_1"] result_1)
)
(call "{1}" ("service_id_2" "local_fn_name") ["result_2"] result_2)
)
)"#,
set_variable_peer_id, local_peer_id
);
let res = call_vm!(set_variable_vm, "asd", script.clone(), "", "");
let res = call_vm!(vm, "asd", script, "", res.data);
let actual_trace: ExecutionTrace = serde_json::from_slice(&res.data).expect("should be valid json");
let expected_executed_call_result = Call(Executed(Rc::new(JValue::String(String::from("result_2")))));
assert_eq!(actual_trace.len(), 2);
assert_eq!(actual_trace[1], expected_executed_call_result);
}
#[test]
fn mismatch_without_xor() {
let set_variable_peer_id = "set_variable_peer_id";
let mut set_variable_vm = create_aqua_vm(echo_string_call_service(), set_variable_peer_id);
let local_peer_id = "local_peer_id";
let mut vm = create_aqua_vm(echo_string_call_service(), local_peer_id);
let script = format!(
r#"
(seq
(seq
(call "{0}" ("" "") ["value_1"] value_1)
(call "{0}" ("" "") ["value_1"] value_2)
)
(mismatch value_1 value_2
(call "{1}" ("service_id_2" "local_fn_name") ["result_1"] result_1)
)
)"#,
set_variable_peer_id, local_peer_id
);
let res = call_vm!(set_variable_vm, "asd", script.clone(), "", "");
let res = call_vm!(vm, "asd", script.clone(), "", res.data);
assert_eq!(res.ret_code, 1015);
let res = call_vm!(vm, "asd", script, "", res.data);
assert_eq!(res.ret_code, 1015);
}
}

View File

@ -15,8 +15,10 @@
*/
mod call;
mod compare_matchable;
mod fold;
mod match_;
mod mismatch;
mod null;
mod par;
mod seq;
@ -46,6 +48,7 @@ impl<'i> ExecutableInstruction<'i> for Instruction<'i> {
Instruction::Seq(seq) => seq.execute(exec_ctx, trace_ctx),
Instruction::Xor(xor) => xor.execute(exec_ctx, trace_ctx),
Instruction::Match(match_) => match_.execute(exec_ctx, trace_ctx),
Instruction::MisMatch(mismatch) => mismatch.execute(exec_ctx, trace_ctx),
Instruction::Error => unreachable!("should not execute if parsing succeeded. QED."),
}
}