From 28b9fed8e5b2a8afd714fb27e48ad6bbf91a812b Mon Sep 17 00:00:00 2001 From: alessandrokonrad Date: Sun, 16 Oct 2022 20:20:47 -0400 Subject: [PATCH] added i128 integer support --- crates/flat/src/decode.rs | 6 ++++ crates/flat/src/decode/decoder.rs | 34 +++++++++++++++++++ crates/flat/src/encode.rs | 8 +++++ crates/flat/src/encode/encoder.rs | 36 +++++++++++++++++++++ crates/flat/src/zigzag.rs | 14 ++++++++ crates/uplc/src/ast.rs | 2 +- crates/uplc/src/flat.rs | 4 +-- crates/uplc/src/machine.rs | 2 +- crates/uplc/src/machine/error.rs | 4 +-- crates/uplc/src/machine/runtime.rs | 14 ++++---- crates/uplc/src/parser.rs | 5 ++- crates/uplc/src/program_builder/constant.rs | 4 +-- 12 files changed, 117 insertions(+), 16 deletions(-) diff --git a/crates/flat/src/decode.rs b/crates/flat/src/decode.rs index 0cd2e4ab..816b9415 100644 --- a/crates/flat/src/decode.rs +++ b/crates/flat/src/decode.rs @@ -36,6 +36,12 @@ impl Decode<'_> for isize { } } +impl Decode<'_> for i128 { + fn decode(d: &mut Decoder) -> Result { + d.big_integer() + } +} + impl Decode<'_> for usize { fn decode(d: &mut Decoder) -> Result { d.word() diff --git a/crates/flat/src/decode/decoder.rs b/crates/flat/src/decode/decoder.rs index ad0efd60..51be5905 100644 --- a/crates/flat/src/decode/decoder.rs +++ b/crates/flat/src/decode/decoder.rs @@ -35,6 +35,18 @@ impl<'b> Decoder<'b> { Ok(zigzag::to_isize(self.word()?)) } + /// Decode an integer of any size. + /// This is byte alignment agnostic. + /// First we decode the next 8 bits of the buffer. + /// We take the 7 least significant bits as the 7 least significant bits of the current unsigned integer. + /// If the most significant bit of the 8 bits is 1 then we take the next 8 and repeat the process above, + /// filling in the next 7 least significant bits of the unsigned integer and so on. + /// If the most significant bit was instead 0 we stop decoding any more bits. + /// Finally we use zigzag to convert the unsigned integer back to a signed integer. + pub fn big_integer(&mut self) -> Result { + Ok(zigzag::to_i128(self.big_word()?)) + } + /// Decode a single bit of the buffer to get a bool. /// We mask out a single bit of the buffer based on used bits. /// and check if it is 0 for false or 1 for true. @@ -130,6 +142,28 @@ impl<'b> Decoder<'b> { Ok(final_word) } + /// Decode a word of any size. + /// This is byte alignment agnostic. + /// First we decode the next 8 bits of the buffer. + /// We take the 7 least significant bits as the 7 least significant bits of the current unsigned integer. + /// If the most significant bit of the 8 bits is 1 then we take the next 8 and repeat the process above, + /// filling in the next 7 least significant bits of the unsigned integer and so on. + /// If the most significant bit was instead 0 we stop decoding any more bits. + pub fn big_word(&mut self) -> Result { + let mut leading_bit = 1; + let mut final_word: u128 = 0; + let mut shl: u128 = 0; + // continue looping if lead bit is 1 which is 128 as a u8 otherwise exit + while leading_bit > 0 { + let word8 = self.bits8(8)?; + let word7 = word8 & 127; + final_word |= (word7 as u128) << shl; + shl += 7; + leading_bit = word8 & 128; + } + Ok(final_word) + } + /// Decode a list of items with a decoder function. /// This is byte alignment agnostic. /// Decode a bit from the buffer. diff --git a/crates/flat/src/encode.rs b/crates/flat/src/encode.rs index 4d1706dc..4e982208 100644 --- a/crates/flat/src/encode.rs +++ b/crates/flat/src/encode.rs @@ -26,6 +26,14 @@ impl Encode for u8 { } } +impl Encode for i128 { + fn encode(&self, e: &mut Encoder) -> Result<(), Error> { + e.big_integer(*self); + + Ok(()) + } +} + impl Encode for isize { fn encode(&self, e: &mut Encoder) -> Result<(), Error> { e.integer(*self); diff --git a/crates/flat/src/encode/encoder.rs b/crates/flat/src/encode/encoder.rs index 65fad039..1b5f7cde 100644 --- a/crates/flat/src/encode/encoder.rs +++ b/crates/flat/src/encode/encoder.rs @@ -98,6 +98,19 @@ impl Encoder { self } + /// Encode an integer of any size. + /// This is byte alignment agnostic. + /// First we use zigzag once to double the number and encode the negative sign as the least significant bit. + /// Next we encode the 7 least significant bits of the unsigned integer. If the number is greater than + /// 127 we encode a leading 1 followed by repeating the encoding above for the next 7 bits and so on. + pub fn big_integer(&mut self, i: i128) -> &mut Self { + let i = zigzag::to_u128(i); + + self.big_word(i); + + self + } + /// Encode a char of 32 bits. /// This is byte alignment agnostic. /// We encode the 7 least significant bits of the unsigned byte. If the char value is greater than @@ -152,6 +165,29 @@ impl Encoder { self } + /// Encode a unsigned integer of any size. + /// This is byte alignment agnostic. + /// We encode the 7 least significant bits of the unsigned byte. If the char value is greater than + /// 127 we encode a leading 1 followed by repeating the above for the next 7 bits and so on. + pub fn big_word(&mut self, c: u128) -> &mut Self { + let mut d = c; + loop { + let mut w = (d & 127) as u8; + d >>= 7; + + if d != 0 { + w |= 128; + } + self.bits(8, w); + + if d == 0 { + break; + } + } + + self + } + /// Encode a list of bytes with a function /// This is byte alignment agnostic. /// If there are bytes in a list then write 1 bit followed by the functions encoding. diff --git a/crates/flat/src/zigzag.rs b/crates/flat/src/zigzag.rs index 316a35b5..4b9f4c4a 100644 --- a/crates/flat/src/zigzag.rs +++ b/crates/flat/src/zigzag.rs @@ -11,3 +11,17 @@ pub fn to_usize(x: isize) -> usize { pub fn to_isize(u: usize) -> isize { ((u >> 1) as isize) ^ (-((u & 1) as isize)) } + +pub fn to_u128(x: i128) -> u128 { + let double_x = x << 1; + + if x.is_positive() || x == 0 { + double_x as u128 + } else { + (-double_x - 1) as u128 + } +} + +pub fn to_i128(u: u128) -> i128 { + ((u >> 1) as i128) ^ (-((u & 1) as i128)) +} diff --git a/crates/uplc/src/ast.rs b/crates/uplc/src/ast.rs index f1c5b81d..e02e202d 100644 --- a/crates/uplc/src/ast.rs +++ b/crates/uplc/src/ast.rs @@ -122,7 +122,7 @@ where #[derive(Debug, Clone, PartialEq)] pub enum Constant { // tag: 0 - Integer(isize), + Integer(i128), // tag: 1 ByteString(Vec), // tag: 2 diff --git a/crates/uplc/src/flat.rs b/crates/uplc/src/flat.rs index dd766547..7a3008af 100644 --- a/crates/uplc/src/flat.rs +++ b/crates/uplc/src/flat.rs @@ -310,7 +310,7 @@ fn encode_type(typ: &Type, bytes: &mut Vec) { impl<'b> Decode<'b> for Constant { fn decode(d: &mut Decoder) -> Result { match &decode_constant(d)?[..] { - [0] => Ok(Constant::Integer(isize::decode(d)?)), + [0] => Ok(Constant::Integer(i128::decode(d)?)), [1] => Ok(Constant::ByteString(Vec::::decode(d)?)), [2] => Ok(Constant::String(String::decode(d)?)), [3] => Ok(Constant::Unit), @@ -354,7 +354,7 @@ impl<'b> Decode<'b> for Constant { fn decode_constant_value(typ: Type, d: &mut Decoder) -> Result { match typ { - Type::Integer => Ok(Constant::Integer(isize::decode(d)?)), + Type::Integer => Ok(Constant::Integer(i128::decode(d)?)), Type::ByteString => Ok(Constant::ByteString(Vec::::decode(d)?)), Type::String => Ok(Constant::String(String::decode(d)?)), Type::Unit => Ok(Constant::Unit), diff --git a/crates/uplc/src/machine.rs b/crates/uplc/src/machine.rs index 8740699a..f992bb34 100644 --- a/crates/uplc/src/machine.rs +++ b/crates/uplc/src/machine.rs @@ -515,7 +515,7 @@ impl Value { PlutusData::BigInt(i) => { if let BigInt::Int(g) = i { let numb: i128 = (*g).try_into().unwrap(); - total += Value::Con(Constant::Integer(numb as isize)).to_ex_mem(); + total += Value::Con(Constant::Integer(numb)).to_ex_mem(); } else { unreachable!() }; diff --git a/crates/uplc/src/machine/error.rs b/crates/uplc/src/machine/error.rs index 690c1643..ba81ab79 100644 --- a/crates/uplc/src/machine/error.rs +++ b/crates/uplc/src/machine/error.rs @@ -37,9 +37,9 @@ pub enum Error { #[error("Decoding utf8")] Utf8(#[from] FromUtf8Error), #[error("Out of Bounds\n\nindex: {}\nbytestring: {}\npossible: 0 - {}", .0, hex::encode(.1), .1.len() - 1)] - ByteStringOutOfBounds(isize, Vec), + ByteStringOutOfBounds(i128, Vec), #[error("Divide By Zero\n\n{0} / {1}")] - DivideByZero(isize, isize), + DivideByZero(i128, i128), #[error("Ed25519S PublicKey should be 32 bytes but it was {0}")] UnexpectedEd25519PublicKeyLength(usize), #[error("Ed25519S Signature should be 64 bytes but it was {0}")] diff --git a/crates/uplc/src/machine/runtime.rs b/crates/uplc/src/machine/runtime.rs index 8cd314b3..5d3e9956 100644 --- a/crates/uplc/src/machine/runtime.rs +++ b/crates/uplc/src/machine/runtime.rs @@ -349,7 +349,7 @@ impl DefaultFunction { if *arg2 != 0 { let ret = (*arg1 as f64) / (*arg2 as f64); - Ok(Value::Con(Constant::Integer(ret.floor() as isize))) + Ok(Value::Con(Constant::Integer(ret.floor() as i128))) } else { Err(Error::DivideByZero(*arg1, *arg2)) } @@ -363,7 +363,7 @@ impl DefaultFunction { let ret = if ret < 0. { ret.ceil() } else { ret.floor() }; - Ok(Value::Con(Constant::Integer(ret as isize))) + Ok(Value::Con(Constant::Integer(ret as i128))) } else { Err(Error::DivideByZero(*arg1, *arg2)) } @@ -447,7 +447,7 @@ impl DefaultFunction { }, DefaultFunction::LengthOfByteString => match &args[0] { Value::Con(Constant::ByteString(arg1)) => { - Ok(Value::Con(Constant::Integer(arg1.len() as isize))) + Ok(Value::Con(Constant::Integer(arg1.len() as i128))) } _ => unreachable!(), }, @@ -456,7 +456,7 @@ impl DefaultFunction { let index = *arg2 as usize; if 0 <= *arg2 && index < arg1.len() { - let ret = arg1[index] as isize; + let ret = arg1[index] as i128; Ok(Value::Con(Constant::Integer(ret))) } else { @@ -749,7 +749,7 @@ impl DefaultFunction { Type::Integer, Type::List(Box::new(Type::Data)), // TODO: handle other types of constructor tags - Box::new(Constant::Integer(convert_tag_to_constr(c.tag as isize))), + Box::new(Constant::Integer(convert_tag_to_constr(c.tag as i128))), Box::new(Constant::ProtoList( Type::Data, c.fields @@ -798,7 +798,7 @@ impl DefaultFunction { if let BigInt::Int(i) = b { let x: i128 = (*i).try_into().unwrap(); - Ok(Value::Con(Constant::Integer(x as isize))) + Ok(Value::Con(Constant::Integer(x))) } else { unreachable!() } @@ -844,7 +844,7 @@ impl DefaultFunction { } } -fn convert_tag_to_constr(tag: isize) -> isize { +fn convert_tag_to_constr(tag: i128) -> i128 { if tag < 128 { tag - 121 } else if (1280..1401).contains(&tag) { diff --git a/crates/uplc/src/parser.rs b/crates/uplc/src/parser.rs index 80d0ef08..e22c8db8 100644 --- a/crates/uplc/src/parser.rs +++ b/crates/uplc/src/parser.rs @@ -106,7 +106,7 @@ peg::parser! { = "(" _* "error" _* ")" { Term::Error } rule constant_integer() -> Constant - = "integer" _+ i:number() { Constant::Integer(i as isize) } + = "integer" _+ i:big_number() { Constant::Integer(i as i128) } rule constant_bytestring() -> Constant = "bytestring" _+ "#" i:ident()* { @@ -125,6 +125,9 @@ peg::parser! { rule number() -> isize = n:$("-"* ['0'..='9']+) {? n.parse().or(Err("isize")) } + rule big_number() -> i128 + = n:$("-"* ['0'..='9']+) {? n.parse().or(Err("isize")) } + rule constant_data() -> Constant = "data" _+ "#" i:ident()* { Constant::Data( diff --git a/crates/uplc/src/program_builder/constant.rs b/crates/uplc/src/program_builder/constant.rs index 23b8b963..532298d4 100644 --- a/crates/uplc/src/program_builder/constant.rs +++ b/crates/uplc/src/program_builder/constant.rs @@ -2,7 +2,7 @@ use crate::ast::{Constant, Term}; use crate::program_builder::WithTerm; pub trait WithConstant: WithTerm { - fn with_int(self, int: isize) -> Self::Next { + fn with_int(self, int: i128) -> Self::Next { let term = Term::Constant(Constant::Integer(int)); self.next(term) } @@ -42,7 +42,7 @@ mod tests { proptest! { #[test] fn build_named__with_const( - int: isize + int: i128 ) { let code = format!(r"(program 11.22.33