Repository: ergoplatform/sigma-rust (language: rust)

Issue:
Unary "-" op



Discussion / hints:

Fix patch:
diff --git a/ergo-lib/CHANGELOG.md b/ergo-lib/CHANGELOG.md
index fd106d6ec..c3c4fb74d 100644
--- a/ergo-lib/CHANGELOG.md
+++ b/ergo-lib/CHANGELOG.md
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 ### Added 
 - `proveDlog`, `box.creationInfo`, `Box.id`, `Coll.exists`, `SigmaProp.propBytes`, `Option.isDefined`, `Option.getOrElse` IR nodes (evaluation, serialization) [#209](https://github.com/ergoplatform/sigma-rust/pull/209);
 - `Box.R0..R3` register access [#213](https://github.com/ergoplatform/sigma-rust/pull/213);
+- `-` negation for numeric types evaluation and serialization [#214](https://github.com/ergoplatform/sigma-rust/pull/214);
 
 ## [0.7.0] - 2021-03-03
 
diff --git a/ergotree-interpreter/src/eval.rs b/ergotree-interpreter/src/eval.rs
index f2e3884b4..e75ef236d 100644
--- a/ergotree-interpreter/src/eval.rs
+++ b/ergotree-interpreter/src/eval.rs
@@ -47,6 +47,7 @@ pub(crate) mod global_vars;
 pub(crate) mod if_op;
 pub(crate) mod logical_not;
 pub(crate) mod method_call;
+pub(crate) mod negation;
 pub(crate) mod option_get;
 pub(crate) mod option_get_or_else;
 pub(crate) mod option_is_defined;
diff --git a/ergotree-interpreter/src/eval/expr.rs b/ergotree-interpreter/src/eval/expr.rs
index e0b4ced95..7299e8539 100644
--- a/ergotree-interpreter/src/eval/expr.rs
+++ b/ergotree-interpreter/src/eval/expr.rs
@@ -47,6 +47,7 @@ impl Evaluable for Expr {
             Expr::SigmaPropBytes(op) => op.eval(env, ctx),
             Expr::OptionIsDefined(op) => op.eval(env, ctx),
             Expr::OptionGetOrElse(op) => op.eval(env, ctx),
+            Expr::Negation(op) => op.eval(env, ctx),
         }
     }
 }
diff --git a/ergotree-interpreter/src/eval/negation.rs b/ergotree-interpreter/src/eval/negation.rs
new file mode 100644
index 000000000..d0406ea24
--- /dev/null
+++ b/ergotree-interpreter/src/eval/negation.rs
@@ -0,0 +1,47 @@
+use ergotree_ir::mir::negation::Negation;
+use ergotree_ir::mir::value::Value;
+
+use crate::eval::env::Env;
+use crate::eval::EvalContext;
+use crate::eval::EvalError;
+use crate::eval::Evaluable;
+
+impl Evaluable for Negation {
+    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
+        let input_v = self.input.eval(env, ctx)?;
+        match input_v {
+            Value::Byte(v) => Ok((-v).into()),
+            Value::Short(v) => Ok((-v).into()),
+            Value::Int(v) => Ok((-v).into()),
+            Value::Long(v) => Ok((-v).into()),
+            _ => Err(EvalError::UnexpectedValue(format!(
+                "Expected Negation input to be numeric value, got {:?}",
+                input_v
+            ))),
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+
+    use super::*;
+    use crate::eval::tests::eval_out_wo_ctx;
+    use ergotree_ir::mir::constant::Constant;
+    use ergotree_ir::mir::constant::TryExtractFrom;
+    use ergotree_ir::mir::expr::Expr;
+    use num::Num;
+
+    fn run_eval<T: Num + Into<Constant> + TryExtractFrom<Value>>(input: T) -> T {
+        let expr: Expr = Negation::new(Expr::Const(input.into())).unwrap().into();
+        eval_out_wo_ctx::<T>(&expr)
+    }
+
+    #[test]
+    fn eval() {
+        assert_eq!(run_eval(1i8), -1i8);
+        assert_eq!(run_eval(1i16), -1i16);
+        assert_eq!(run_eval(1i32), -1i32);
+        assert_eq!(run_eval(1i64), -1i64);
+    }
+}
diff --git a/ergotree-ir/README.md b/ergotree-ir/README.md
index b51320128..25f3907f2 100644
--- a/ergotree-ir/README.md
+++ b/ergotree-ir/README.md
@@ -32,7 +32,7 @@ Descriptions for the operations can be found in [ErgoTree Specification](https:/
 - ^ (bitwise XOR);
 - `|` (byte-wise XOR of two collections of bytes);
 - :heavy_check_mark: unary `!`;
-- unary `-`;
+- :heavy_check_mark: unary `-`;
 - unary `~` (bit inversion);
 - `>>`, `<<`, `>>>` (bit shifts);
 
diff --git a/ergotree-ir/src/mir.rs b/ergotree-ir/src/mir.rs
index e3d8690fe..367200883 100644
--- a/ergotree-ir/src/mir.rs
+++ b/ergotree-ir/src/mir.rs
@@ -44,6 +44,8 @@ pub mod if_op;
 pub mod logical_not;
 /// Object method call
 pub mod method_call;
+/// Negation operation on numeric type.
+pub mod negation;
 /// Option.get() op
 pub mod option_get;
 /// Returns the Option's value or error if no value
diff --git a/ergotree-ir/src/mir/expr.rs b/ergotree-ir/src/mir/expr.rs
index 0f6d72647..f02b1c4de 100644
--- a/ergotree-ir/src/mir/expr.rs
+++ b/ergotree-ir/src/mir/expr.rs
@@ -34,6 +34,7 @@ use super::global_vars::GlobalVars;
 use super::if_op::If;
 use super::logical_not::LogicalNot;
 use super::method_call::MethodCall;
+use super::negation::Negation;
 use super::option_get::OptionGet;
 use super::option_get_or_else::OptionGetOrElse;
 use super::option_is_defined::OptionIsDefined;
@@ -90,6 +91,8 @@ pub enum Expr {
     Or(Or),
     /// LogicalNot
     LogicalNot(LogicalNot),
+    /// Negation on numeric type
+    Negation(Negation),
     /// Option.get method
     OptionGet(OptionGet),
     /// Option.isDefined method
@@ -172,6 +175,7 @@ impl Expr {
             Expr::SigmaPropBytes(op) => op.op_code(),
             Expr::OptionIsDefined(op) => op.op_code(),
             Expr::OptionGetOrElse(op) => op.op_code(),
+            Expr::Negation(op) => op.op_code(),
         }
     }
 
@@ -215,6 +219,7 @@ impl Expr {
             Expr::SigmaPropBytes(v) => v.tpe(),
             Expr::OptionIsDefined(v) => v.tpe(),
             Expr::OptionGetOrElse(v) => v.tpe(),
+            Expr::Negation(v) => v.tpe(),
         }
     }
 
@@ -367,6 +372,12 @@ pub(crate) mod arbitrary {
         prop_oneof![Just(GlobalVars::Height.into()),].boxed()
     }
 
+    fn constant(tpe: &SType) -> BoxedStrategy<Expr> {
+        any_with::<Constant>(tpe.clone().into())
+            .prop_map_into()
+            .boxed()
+    }
+
     fn bool_non_nested_expr() -> BoxedStrategy<Expr> {
         prop_oneof![any_with::<Constant>(SType::SBoolean.into()).prop_map_into()].boxed()
     }
@@ -393,7 +404,7 @@ pub(crate) mod arbitrary {
             SType::SInt => int_non_nested_expr(),
             SType::SBoolean => bool_non_nested_expr(),
             SType::SColl(elem_type) => coll_non_nested_expr(elem_type),
-            _ => todo!("{0:?} is not yet implemented", tpe),
+            t => constant(t),
         }
     }
 
diff --git a/ergotree-ir/src/mir/negation.rs b/ergotree-ir/src/mir/negation.rs
new file mode 100644
index 000000000..bb35a58f7
--- /dev/null
+++ b/ergotree-ir/src/mir/negation.rs
@@ -0,0 +1,105 @@
+use super::expr::Expr;
+use super::expr::InvalidArgumentError;
+use crate::serialization::op_code::OpCode;
+use crate::serialization::sigma_byte_reader::SigmaByteRead;
+use crate::serialization::sigma_byte_writer::SigmaByteWrite;
+use crate::serialization::SerializationError;
+use crate::serialization::SigmaSerializable;
+use crate::types::stype::SType;
+
+/// Negation operation on numeric type.
+#[derive(PartialEq, Eq, Debug, Clone)]
+pub struct Negation {
+    /// Input expr of numeric type
+    pub input: Box<Expr>,
+}
+
+impl Negation {
+    pub(crate) const OP_CODE: OpCode = OpCode::NEGATION;
+
+    /// Create new object, returns an error if any of the requirements failed
+    pub fn new(input: Expr) -> Result<Self, InvalidArgumentError> {
+        match input.post_eval_tpe() {
+            SType::SByte | SType::SShort | SType::SInt | SType::SLong => Ok(Self {
+                input: input.into(),
+            }),
+            tpe => Err(InvalidArgumentError(format!(
+                "Negation: expected input type to be numeric, got {:?}",
+                tpe
+            ))),
+        }
+    }
+
+    /// Type
+    pub fn tpe(&self) -> SType {
+        self.input.post_eval_tpe()
+    }
+
+    pub(crate) fn op_code(&self) -> OpCode {
+        Self::OP_CODE
+    }
+}
+
+impl SigmaSerializable for Negation {
+    fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), std::io::Error> {
+        self.input.sigma_serialize(w)
+    }
+
+    fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SerializationError> {
+        Ok(Self::new(Expr::sigma_parse(r)?)?)
+    }
+}
+
+#[cfg(feature = "arbitrary")]
+mod arbitrary {
+    use crate::mir::expr::arbitrary::ArbExprParams;
+
+    use super::*;
+    use proptest::prelude::*;
+
+    impl Arbitrary for Negation {
+        type Strategy = BoxedStrategy<Self>;
+        type Parameters = ();
+
+        fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
+            prop_oneof![
+                any_with::<Expr>(ArbExprParams {
+                    tpe: SType::SByte,
+                    depth: 0,
+                }),
+                any_with::<Expr>(ArbExprParams {
+                    tpe: SType::SShort,
+                    depth: 0,
+                }),
+                any_with::<Expr>(ArbExprParams {
+                    tpe: SType::SInt,
+                    depth: 0,
+                }),
+                any_with::<Expr>(ArbExprParams {
+                    tpe: SType::SLong,
+                    depth: 0,
+                }),
+            ]
+            .prop_map(|input| Self::new(input).unwrap())
+            .boxed()
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::mir::expr::Expr;
+    use crate::serialization::sigma_serialize_roundtrip;
+    use proptest::prelude::*;
+
+    proptest! {
+
+        #[test]
+        fn ser_roundtrip(v in any::<Negation>()) {
+            let expr: Expr = v.into();
+            prop_assert_eq![sigma_serialize_roundtrip(&expr), expr];
+        }
+
+    }
+}
diff --git a/ergotree-ir/src/serialization/expr.rs b/ergotree-ir/src/serialization/expr.rs
index 9c846922b..cc7b72f56 100644
--- a/ergotree-ir/src/serialization/expr.rs
+++ b/ergotree-ir/src/serialization/expr.rs
@@ -31,6 +31,7 @@ use crate::mir::global_vars::GlobalVars;
 use crate::mir::if_op::If;
 use crate::mir::logical_not::LogicalNot;
 use crate::mir::method_call::MethodCall;
+use crate::mir::negation::Negation;
 use crate::mir::option_get::OptionGet;
 use crate::mir::option_get_or_else::OptionGetOrElse;
 use crate::mir::option_is_defined::OptionIsDefined;
@@ -99,6 +100,7 @@ impl SigmaSerializable for Expr {
                     Expr::SigmaPropBytes(op) => op.sigma_serialize(w),
                     Expr::OptionIsDefined(op) => op.sigma_serialize(w),
                     Expr::OptionGetOrElse(op) => op.sigma_serialize(w),
+                    Expr::Negation(op) => op.sigma_serialize(w),
                 }
             }
         }
@@ -148,6 +150,7 @@ impl SigmaSerializable for Expr {
                 ExtractId::OP_CODE => Ok(ExtractId::sigma_parse(r)?.into()),
                 OpCode::EQ => Ok(bin_op_sigma_parse(RelationOp::Eq.into(), r)?),
                 OpCode::NEQ => Ok(bin_op_sigma_parse(RelationOp::NEq.into(), r)?),
+                Negation::OP_CODE => Ok(Negation::sigma_parse(r)?.into()),
                 OpCode::LOGICAL_NOT => Ok(LogicalNot::sigma_parse(r)?.into()),
                 OpCode::BIN_AND => Ok(bin_op_sigma_parse(RelationOp::And.into(), r)?),
                 OpCode::BIN_OR => Ok(bin_op_sigma_parse(RelationOp::Or.into(), r)?),


Test patch: