use crate::{FinalizeRegisters, Opcode, Operand, ProgramStorage, ProgramStore, Stack};
use console::{
network::prelude::*,
program::{Identifier, Literal, Plaintext, Value},
};
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct Increment<N: Network> {
mapping: Identifier<N>,
first: Operand<N>,
second: Operand<N>,
}
impl<N: Network> Increment<N> {
#[inline]
pub const fn opcode() -> Opcode {
Opcode::Command("increment")
}
#[inline]
pub fn operands(&self) -> Vec<Operand<N>> {
vec![self.first.clone(), self.second.clone()]
}
#[inline]
pub const fn mapping_name(&self) -> &Identifier<N> {
&self.mapping
}
#[inline]
pub const fn key(&self) -> &Operand<N> {
&self.first
}
#[inline]
pub const fn value(&self) -> &Operand<N> {
&self.second
}
}
impl<N: Network> Increment<N> {
#[inline]
pub fn evaluate_finalize<P: ProgramStorage<N>>(
&self,
stack: &Stack<N>,
store: &ProgramStore<N, P>,
registers: &mut FinalizeRegisters<N>,
) -> Result<()> {
if !store.contains_mapping(stack.program_id(), &self.mapping)? {
bail!("Mapping '{}/{}' does not exist in storage", stack.program_id(), self.mapping);
}
let key = registers.load_plaintext(stack, &self.first)?;
let increment = registers.load_literal(stack, &self.second)?;
let start = match store.get_value(stack.program_id(), &self.mapping, &key)? {
Some(Value::Plaintext(Plaintext::Literal(literal, _))) => literal,
Some(Value::Plaintext(Plaintext::Interface(..))) => bail!("Cannot 'increment' by an 'interface'"),
Some(Value::Record(..)) => bail!("Cannot 'increment' by a 'record'"),
None => match increment {
Literal::Address(..) => bail!("Cannot 'increment' by an 'address'"),
Literal::Boolean(..) => bail!("Cannot 'increment' by a 'boolean'"),
Literal::Field(..) => Literal::Field(Zero::zero()),
Literal::Group(..) => Literal::Group(Zero::zero()),
Literal::I8(..) => Literal::I8(Zero::zero()),
Literal::I16(..) => Literal::I16(Zero::zero()),
Literal::I32(..) => Literal::I32(Zero::zero()),
Literal::I64(..) => Literal::I64(Zero::zero()),
Literal::I128(..) => Literal::I128(Zero::zero()),
Literal::U8(..) => Literal::U8(Zero::zero()),
Literal::U16(..) => Literal::U16(Zero::zero()),
Literal::U32(..) => Literal::U32(Zero::zero()),
Literal::U64(..) => Literal::U64(Zero::zero()),
Literal::U128(..) => Literal::U128(Zero::zero()),
Literal::Scalar(..) => Literal::Scalar(Zero::zero()),
Literal::String(..) => bail!("Cannot 'increment' by a 'string'"),
},
};
let outcome = match (start, increment) {
(Literal::Field(a), Literal::Field(b)) => Literal::Field(a.add(b)),
(Literal::Group(a), Literal::Group(b)) => Literal::Group(a.add(b)),
(Literal::I8(a), Literal::I8(b)) => Literal::I8(a.add(b)),
(Literal::I16(a), Literal::I16(b)) => Literal::I16(a.add(b)),
(Literal::I32(a), Literal::I32(b)) => Literal::I32(a.add(b)),
(Literal::I64(a), Literal::I64(b)) => Literal::I64(a.add(b)),
(Literal::I128(a), Literal::I128(b)) => Literal::I128(a.add(b)),
(Literal::U8(a), Literal::U8(b)) => Literal::U8(a.add(b)),
(Literal::U16(a), Literal::U16(b)) => Literal::U16(a.add(b)),
(Literal::U32(a), Literal::U32(b)) => Literal::U32(a.add(b)),
(Literal::U64(a), Literal::U64(b)) => Literal::U64(a.add(b)),
(Literal::U128(a), Literal::U128(b)) => Literal::U128(a.add(b)),
(Literal::Scalar(a), Literal::Scalar(b)) => Literal::Scalar(a.add(b)),
(a, b) => bail!("Cannot 'increment' '{a}' by '{b}'"),
};
let value = Value::Plaintext(Plaintext::from(outcome));
store.update_key_value(stack.program_id(), &self.mapping, key, value)?;
Ok(())
}
}
impl<N: Network> Parser for Increment<N> {
#[inline]
fn parse(string: &str) -> ParserResult<Self> {
let (string, _) = Sanitizer::parse(string)?;
let (string, _) = tag(*Self::opcode())(string)?;
let (string, _) = Sanitizer::parse_whitespaces(string)?;
let (string, mapping) = Identifier::parse(string)?;
let (string, _) = tag("[")(string)?;
let (string, _) = Sanitizer::parse_whitespaces(string)?;
let (string, first) = Operand::parse(string)?;
let (string, _) = Sanitizer::parse_whitespaces(string)?;
let (string, _) = tag("]")(string)?;
let (string, _) = Sanitizer::parse_whitespaces(string)?;
let (string, _) = tag("by")(string)?;
let (string, _) = Sanitizer::parse_whitespaces(string)?;
let (string, second) = Operand::parse(string)?;
let (string, _) = Sanitizer::parse_whitespaces(string)?;
let (string, _) = tag(";")(string)?;
Ok((string, Self { mapping, first, second }))
}
}
impl<N: Network> FromStr for Increment<N> {
type Err = Error;
#[inline]
fn from_str(string: &str) -> Result<Self> {
match Self::parse(string) {
Ok((remainder, object)) => {
ensure!(remainder.is_empty(), "Failed to parse string. Found invalid character in: \"{remainder}\"");
Ok(object)
}
Err(error) => bail!("Failed to parse string. {error}"),
}
}
}
impl<N: Network> Debug for Increment<N> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Display::fmt(self, f)
}
}
impl<N: Network> Display for Increment<N> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{} ", Self::opcode())?;
write!(f, "{}[{}] ", self.mapping, self.first)?;
write!(f, "by ")?;
write!(f, "{};", self.second)
}
}
impl<N: Network> FromBytes for Increment<N> {
fn read_le<R: Read>(mut reader: R) -> IoResult<Self> {
let mapping = Identifier::read_le(&mut reader)?;
let first = Operand::read_le(&mut reader)?;
let second = Operand::read_le(&mut reader)?;
Ok(Self { mapping, first, second })
}
}
impl<N: Network> ToBytes for Increment<N> {
fn write_le<W: Write>(&self, mut writer: W) -> IoResult<()> {
self.mapping.write_le(&mut writer)?;
self.first.write_le(&mut writer)?;
self.second.write_le(&mut writer)
}
}
#[cfg(test)]
mod tests {
use super::*;
use console::{network::Testnet3, program::Register};
type CurrentNetwork = Testnet3;
#[test]
fn test_parse() {
let (string, increment) = Increment::<CurrentNetwork>::parse("increment account[r0] by r1;").unwrap();
assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'");
assert_eq!(increment.mapping, Identifier::from_str("account").unwrap());
assert_eq!(increment.operands().len(), 2, "The number of operands is incorrect");
assert_eq!(increment.first, Operand::Register(Register::Locator(0)), "The first operand is incorrect");
assert_eq!(increment.second, Operand::Register(Register::Locator(1)), "The second operand is incorrect");
}
}