Add several builtins for strings and bytestring

Co-authored-by: rvcas <x@rvcas.dev>
This commit is contained in:
Kasey White 2022-08-07 22:12:26 -04:00 committed by Lucas
parent 0e5d25b019
commit 11ee99c199
7 changed files with 324 additions and 76 deletions

1
Cargo.lock generated
View File

@ -642,6 +642,7 @@ checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99"
name = "uplc"
version = "0.0.7"
dependencies = [
"cryptoxide",
"flat-rs",
"hex",
"minicbor 0.18.0",

View File

@ -1,5 +1,5 @@
(program
1.0.0
(con (list (list integer)) [[7], [5]])
[ (builtin decodeUtf8) [ (builtin encodeUtf8) (con string "hello world") ] ]
)

View File

@ -13,6 +13,7 @@ exclude = ["test_data/*"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
cryptoxide = "0.4.2"
flat-rs = { path = "../flat", version = "0.0.7" }
hex = "0.4.3"
minicbor = { version = "0.18.0", features = ["std"] }

View File

@ -334,7 +334,7 @@ impl Machine {
self.spend_budget(cost)?;
runtime.call()
runtime.call(&mut self.logs)
} else {
Ok(Value::Builtin { fun, term, runtime })
}
@ -430,7 +430,7 @@ impl Value {
((i.abs() as f64).log2().floor() as i64 / 64) + 1
}
}
Constant::ByteString(b) => (((b.len() - 1) / 8) + 1) as i64,
Constant::ByteString(b) => (((b.len() as i64 - 1) / 8) + 1),
Constant::String(s) => s.chars().count() as i64,
Constant::Unit => 1,
Constant::Bool(_) => 1,

View File

@ -552,13 +552,40 @@ impl BuiltinCosts {
.cpu
.cost(args[0].to_ex_mem(), args[1].to_ex_mem()),
},
DefaultFunction::MultiplyInteger => todo!(),
DefaultFunction::MultiplyInteger => ExBudget {
mem: self
.multiply_integer
.mem
.cost(args[0].to_ex_mem(), args[1].to_ex_mem()),
cpu: self
.multiply_integer
.cpu
.cost(args[0].to_ex_mem(), args[1].to_ex_mem()),
},
DefaultFunction::DivideInteger => todo!(),
DefaultFunction::QuotientInteger => todo!(),
DefaultFunction::RemainderInteger => todo!(),
DefaultFunction::ModInteger => todo!(),
DefaultFunction::EqualsInteger => todo!(),
DefaultFunction::LessThanInteger => todo!(),
DefaultFunction::EqualsInteger => ExBudget {
mem: self
.equals_integer
.mem
.cost(args[0].to_ex_mem(), args[1].to_ex_mem()),
cpu: self
.equals_integer
.cpu
.cost(args[0].to_ex_mem(), args[1].to_ex_mem()),
},
DefaultFunction::LessThanInteger => ExBudget {
mem: self
.less_than_integer
.mem
.cost(args[0].to_ex_mem(), args[1].to_ex_mem()),
cpu: self
.less_than_integer
.cpu
.cost(args[0].to_ex_mem(), args[1].to_ex_mem()),
},
DefaultFunction::LessThanEqualsInteger => ExBudget {
mem: self
.less_than_equals_integer
@ -569,24 +596,75 @@ impl BuiltinCosts {
.cpu
.cost(args[0].to_ex_mem(), args[1].to_ex_mem()),
},
DefaultFunction::AppendByteString => todo!(),
DefaultFunction::AppendByteString => ExBudget {
mem: self
.append_byte_string
.mem
.cost(args[0].to_ex_mem(), args[1].to_ex_mem()),
cpu: self
.append_byte_string
.cpu
.cost(args[0].to_ex_mem(), args[1].to_ex_mem()),
},
DefaultFunction::ConsByteString => todo!(),
DefaultFunction::SliceByteString => todo!(),
DefaultFunction::LengthOfByteString => todo!(),
DefaultFunction::IndexByteString => todo!(),
DefaultFunction::EqualsByteString => todo!(),
DefaultFunction::EqualsByteString => ExBudget {
mem: self
.equals_byte_string
.mem
.cost(args[0].to_ex_mem(), args[1].to_ex_mem()),
cpu: self
.equals_byte_string
.cpu
.cost(args[0].to_ex_mem(), args[1].to_ex_mem()),
},
DefaultFunction::LessThanByteString => todo!(),
DefaultFunction::LessThanEqualsByteString => todo!(),
DefaultFunction::Sha2_256 => todo!(),
DefaultFunction::Sha3_256 => todo!(),
DefaultFunction::Blake2b_256 => todo!(),
DefaultFunction::Sha2_256 => ExBudget {
mem: self.sha2_256.mem.cost(args[0].to_ex_mem()),
cpu: self.sha2_256.cpu.cost(args[0].to_ex_mem()),
},
DefaultFunction::Sha3_256 => ExBudget {
mem: self.sha3_256.mem.cost(args[0].to_ex_mem()),
cpu: self.sha3_256.cpu.cost(args[0].to_ex_mem()),
},
DefaultFunction::Blake2b_256 => ExBudget {
mem: self.blake2b_256.mem.cost(args[0].to_ex_mem()),
cpu: self.blake2b_256.cpu.cost(args[0].to_ex_mem()),
},
DefaultFunction::VerifySignature => todo!(),
DefaultFunction::VerifyEcdsaSecp256k1Signature => todo!(),
DefaultFunction::VerifySchnorrSecp256k1Signature => todo!(),
DefaultFunction::AppendString => todo!(),
DefaultFunction::EqualsString => todo!(),
DefaultFunction::EncodeUtf8 => todo!(),
DefaultFunction::DecodeUtf8 => todo!(),
DefaultFunction::AppendString => ExBudget {
mem: self
.append_string
.mem
.cost(args[0].to_ex_mem(), args[1].to_ex_mem()),
cpu: self
.append_string
.cpu
.cost(args[0].to_ex_mem(), args[1].to_ex_mem()),
},
DefaultFunction::EqualsString => ExBudget {
mem: self
.equals_string
.mem
.cost(args[0].to_ex_mem(), args[1].to_ex_mem()),
cpu: self
.equals_string
.cpu
.cost(args[0].to_ex_mem(), args[1].to_ex_mem()),
},
DefaultFunction::EncodeUtf8 => ExBudget {
mem: self.encode_utf8.mem.cost(args[0].to_ex_mem()),
cpu: self.encode_utf8.cpu.cost(args[0].to_ex_mem()),
},
DefaultFunction::DecodeUtf8 => ExBudget {
mem: self.decode_utf8.mem.cost(args[0].to_ex_mem()),
cpu: self.decode_utf8.cpu.cost(args[0].to_ex_mem()),
},
DefaultFunction::IfThenElse => ExBudget {
mem: self.if_then_else.mem.cost(
args[0].to_ex_mem(),
@ -599,8 +677,26 @@ impl BuiltinCosts {
args[2].to_ex_mem(),
),
},
DefaultFunction::ChooseUnit => todo!(),
DefaultFunction::Trace => todo!(),
DefaultFunction::ChooseUnit => ExBudget {
mem: self
.choose_unit
.mem
.cost(args[0].to_ex_mem(), args[1].to_ex_mem()),
cpu: self
.choose_unit
.cpu
.cost(args[0].to_ex_mem(), args[1].to_ex_mem()),
},
DefaultFunction::Trace => ExBudget {
mem: self
.trace
.mem
.cost(args[0].to_ex_mem(), args[1].to_ex_mem()),
cpu: self
.trace
.cpu
.cost(args[0].to_ex_mem(), args[1].to_ex_mem()),
},
DefaultFunction::FstPair => todo!(),
DefaultFunction::SndPair => todo!(),
DefaultFunction::ChooseList => todo!(),

View File

@ -1,3 +1,5 @@
use std::string::FromUtf8Error;
use thiserror::Error;
use crate::ast::{NamedDeBruijn, Term, Type};
@ -28,4 +30,6 @@ pub enum Error {
NotAConstant(Value),
#[error("The evaluation never reached a final state")]
MachineNeverReachedDone,
#[error("Decoding utf8")]
Utf8(#[from] FromUtf8Error),
}

View File

@ -46,8 +46,8 @@ impl BuiltinRuntime {
self.forces += 1;
}
pub fn call(&self) -> Result<Value, Error> {
self.fun.call(&self.args)
pub fn call(&self, logs: &mut Vec<String>) -> Result<Value, Error> {
self.fun.call(&self.args, logs)
}
pub fn push(&mut self, arg: Value) -> Result<(), Error> {
@ -74,35 +74,35 @@ impl DefaultFunction {
match self {
DefaultFunction::AddInteger => 2,
DefaultFunction::SubtractInteger => 2,
DefaultFunction::MultiplyInteger => todo!(),
DefaultFunction::MultiplyInteger => 2,
DefaultFunction::DivideInteger => todo!(),
DefaultFunction::QuotientInteger => todo!(),
DefaultFunction::RemainderInteger => todo!(),
DefaultFunction::ModInteger => todo!(),
DefaultFunction::EqualsInteger => todo!(),
DefaultFunction::LessThanInteger => todo!(),
DefaultFunction::EqualsInteger => 2,
DefaultFunction::LessThanInteger => 2,
DefaultFunction::LessThanEqualsInteger => 2,
DefaultFunction::AppendByteString => todo!(),
DefaultFunction::AppendByteString => 2,
DefaultFunction::ConsByteString => todo!(),
DefaultFunction::SliceByteString => todo!(),
DefaultFunction::LengthOfByteString => todo!(),
DefaultFunction::IndexByteString => todo!(),
DefaultFunction::EqualsByteString => todo!(),
DefaultFunction::EqualsByteString => 2,
DefaultFunction::LessThanByteString => todo!(),
DefaultFunction::LessThanEqualsByteString => todo!(),
DefaultFunction::Sha2_256 => todo!(),
DefaultFunction::Sha3_256 => todo!(),
DefaultFunction::Blake2b_256 => todo!(),
DefaultFunction::Sha2_256 => 1,
DefaultFunction::Sha3_256 => 1,
DefaultFunction::Blake2b_256 => 1,
DefaultFunction::VerifySignature => todo!(),
DefaultFunction::VerifyEcdsaSecp256k1Signature => todo!(),
DefaultFunction::VerifySchnorrSecp256k1Signature => todo!(),
DefaultFunction::AppendString => todo!(),
DefaultFunction::EqualsString => todo!(),
DefaultFunction::EncodeUtf8 => todo!(),
DefaultFunction::DecodeUtf8 => todo!(),
DefaultFunction::AppendString => 2,
DefaultFunction::EqualsString => 2,
DefaultFunction::EncodeUtf8 => 1,
DefaultFunction::DecodeUtf8 => 1,
DefaultFunction::IfThenElse => 3,
DefaultFunction::ChooseUnit => todo!(),
DefaultFunction::Trace => todo!(),
DefaultFunction::ChooseUnit => 2,
DefaultFunction::Trace => 2,
DefaultFunction::FstPair => todo!(),
DefaultFunction::SndPair => todo!(),
DefaultFunction::ChooseList => todo!(),
@ -133,35 +133,35 @@ impl DefaultFunction {
match self {
DefaultFunction::AddInteger => 0,
DefaultFunction::SubtractInteger => 0,
DefaultFunction::MultiplyInteger => todo!(),
DefaultFunction::MultiplyInteger => 0,
DefaultFunction::DivideInteger => todo!(),
DefaultFunction::QuotientInteger => todo!(),
DefaultFunction::RemainderInteger => todo!(),
DefaultFunction::ModInteger => todo!(),
DefaultFunction::EqualsInteger => todo!(),
DefaultFunction::LessThanInteger => todo!(),
DefaultFunction::EqualsInteger => 0,
DefaultFunction::LessThanInteger => 0,
DefaultFunction::LessThanEqualsInteger => 0,
DefaultFunction::AppendByteString => todo!(),
DefaultFunction::AppendByteString => 0,
DefaultFunction::ConsByteString => todo!(),
DefaultFunction::SliceByteString => todo!(),
DefaultFunction::LengthOfByteString => todo!(),
DefaultFunction::IndexByteString => todo!(),
DefaultFunction::EqualsByteString => todo!(),
DefaultFunction::EqualsByteString => 0,
DefaultFunction::LessThanByteString => todo!(),
DefaultFunction::LessThanEqualsByteString => todo!(),
DefaultFunction::Sha2_256 => todo!(),
DefaultFunction::Sha3_256 => todo!(),
DefaultFunction::Blake2b_256 => todo!(),
DefaultFunction::Sha2_256 => 0,
DefaultFunction::Sha3_256 => 0,
DefaultFunction::Blake2b_256 => 0,
DefaultFunction::VerifySignature => todo!(),
DefaultFunction::VerifyEcdsaSecp256k1Signature => todo!(),
DefaultFunction::VerifySchnorrSecp256k1Signature => todo!(),
DefaultFunction::AppendString => todo!(),
DefaultFunction::EqualsString => todo!(),
DefaultFunction::EncodeUtf8 => todo!(),
DefaultFunction::DecodeUtf8 => todo!(),
DefaultFunction::AppendString => 0,
DefaultFunction::EqualsString => 0,
DefaultFunction::EncodeUtf8 => 0,
DefaultFunction::DecodeUtf8 => 0,
DefaultFunction::IfThenElse => 1,
DefaultFunction::ChooseUnit => 1,
DefaultFunction::Trace => todo!(),
DefaultFunction::Trace => 1,
DefaultFunction::FstPair => todo!(),
DefaultFunction::SndPair => todo!(),
DefaultFunction::ChooseList => todo!(),
@ -192,41 +192,53 @@ impl DefaultFunction {
match self {
DefaultFunction::AddInteger => arg.expect_type(Type::Integer),
DefaultFunction::SubtractInteger => arg.expect_type(Type::Integer),
DefaultFunction::MultiplyInteger => todo!(),
DefaultFunction::MultiplyInteger => arg.expect_type(Type::Integer),
DefaultFunction::DivideInteger => todo!(),
DefaultFunction::QuotientInteger => todo!(),
DefaultFunction::RemainderInteger => todo!(),
DefaultFunction::ModInteger => todo!(),
DefaultFunction::EqualsInteger => todo!(),
DefaultFunction::LessThanInteger => todo!(),
DefaultFunction::EqualsInteger => arg.expect_type(Type::Integer),
DefaultFunction::LessThanInteger => arg.expect_type(Type::Integer),
DefaultFunction::LessThanEqualsInteger => arg.expect_type(Type::Integer),
DefaultFunction::AppendByteString => todo!(),
DefaultFunction::AppendByteString => arg.expect_type(Type::ByteString),
DefaultFunction::ConsByteString => todo!(),
DefaultFunction::SliceByteString => todo!(),
DefaultFunction::LengthOfByteString => todo!(),
DefaultFunction::IndexByteString => todo!(),
DefaultFunction::EqualsByteString => todo!(),
DefaultFunction::EqualsByteString => arg.expect_type(Type::ByteString),
DefaultFunction::LessThanByteString => todo!(),
DefaultFunction::LessThanEqualsByteString => todo!(),
DefaultFunction::Sha2_256 => todo!(),
DefaultFunction::Sha3_256 => todo!(),
DefaultFunction::Blake2b_256 => todo!(),
DefaultFunction::Sha2_256 => arg.expect_type(Type::ByteString),
DefaultFunction::Sha3_256 => arg.expect_type(Type::ByteString),
DefaultFunction::Blake2b_256 => arg.expect_type(Type::ByteString),
DefaultFunction::VerifySignature => todo!(),
DefaultFunction::VerifyEcdsaSecp256k1Signature => todo!(),
DefaultFunction::VerifySchnorrSecp256k1Signature => todo!(),
DefaultFunction::AppendString => todo!(),
DefaultFunction::EqualsString => todo!(),
DefaultFunction::EncodeUtf8 => todo!(),
DefaultFunction::DecodeUtf8 => todo!(),
DefaultFunction::AppendString => arg.expect_type(Type::String),
DefaultFunction::EqualsString => arg.expect_type(Type::String),
DefaultFunction::EncodeUtf8 => arg.expect_type(Type::String),
DefaultFunction::DecodeUtf8 => arg.expect_type(Type::ByteString),
DefaultFunction::IfThenElse => {
if args.is_empty() && !arg.is_bool() {
if args.is_empty() {
arg.expect_type(Type::Bool)
} else {
Ok(())
}
}
DefaultFunction::ChooseUnit => todo!(),
DefaultFunction::Trace => todo!(),
DefaultFunction::ChooseUnit => {
if args.is_empty() {
arg.expect_type(Type::Unit)
} else {
Ok(())
}
}
DefaultFunction::Trace => {
if args.is_empty() {
arg.expect_type(Type::String)
} else {
Ok(())
}
}
DefaultFunction::FstPair => todo!(),
DefaultFunction::SndPair => todo!(),
DefaultFunction::ChooseList => todo!(),
@ -256,7 +268,7 @@ impl DefaultFunction {
// This should be safe because we've already checked
// the types of the args as they were pushed. Although
// the unreachables look ugly, it's the reality of the situation.
pub fn call(&self, args: &[Value]) -> Result<Value, Error> {
pub fn call(&self, args: &[Value], logs: &mut Vec<String>) -> Result<Value, Error> {
match self {
DefaultFunction::AddInteger => {
let args = (&args[0], &args[1]);
@ -278,13 +290,40 @@ impl DefaultFunction {
_ => unreachable!(),
}
}
DefaultFunction::MultiplyInteger => todo!(),
DefaultFunction::MultiplyInteger => {
let args = (&args[0], &args[1]);
match args {
(Value::Con(Constant::Integer(arg1)), Value::Con(Constant::Integer(arg2))) => {
Ok(Value::Con(Constant::Integer(arg1 * arg2)))
}
_ => unreachable!(),
}
}
DefaultFunction::DivideInteger => todo!(),
DefaultFunction::QuotientInteger => todo!(),
DefaultFunction::RemainderInteger => todo!(),
DefaultFunction::ModInteger => todo!(),
DefaultFunction::EqualsInteger => todo!(),
DefaultFunction::LessThanInteger => todo!(),
DefaultFunction::EqualsInteger => {
let args = (&args[0], &args[1]);
match args {
(Value::Con(Constant::Integer(arg1)), Value::Con(Constant::Integer(arg2))) => {
Ok(Value::Con(Constant::Bool(arg1 == arg2)))
}
_ => unreachable!(),
}
}
DefaultFunction::LessThanInteger => {
let args = (&args[0], &args[1]);
match args {
(Value::Con(Constant::Integer(arg1)), Value::Con(Constant::Integer(arg2))) => {
Ok(Value::Con(Constant::Bool(arg1 < arg2)))
}
_ => unreachable!(),
}
}
DefaultFunction::LessThanEqualsInteger => {
let args = (&args[0], &args[1]);
@ -295,24 +334,121 @@ impl DefaultFunction {
_ => unreachable!(),
}
}
DefaultFunction::AppendByteString => todo!(),
DefaultFunction::AppendByteString => {
let args = (&args[0], &args[1]);
match args {
(
Value::Con(Constant::ByteString(arg1)),
Value::Con(Constant::ByteString(arg2)),
) => Ok(Value::Con(Constant::ByteString(
arg1.iter().copied().chain(arg2.iter().copied()).collect(),
))),
_ => unreachable!(),
}
}
DefaultFunction::ConsByteString => todo!(),
DefaultFunction::SliceByteString => todo!(),
DefaultFunction::LengthOfByteString => todo!(),
DefaultFunction::IndexByteString => todo!(),
DefaultFunction::EqualsByteString => todo!(),
DefaultFunction::EqualsByteString => {
let args = (&args[0], &args[1]);
match args {
(
Value::Con(Constant::ByteString(arg1)),
Value::Con(Constant::ByteString(arg2)),
) => Ok(Value::Con(Constant::Bool(arg1 == arg2))),
_ => unreachable!(),
}
}
DefaultFunction::LessThanByteString => todo!(),
DefaultFunction::LessThanEqualsByteString => todo!(),
DefaultFunction::Sha2_256 => todo!(),
DefaultFunction::Sha3_256 => todo!(),
DefaultFunction::Blake2b_256 => todo!(),
DefaultFunction::Sha2_256 => match &args[0] {
Value::Con(Constant::ByteString(arg1)) => {
use cryptoxide::{digest::Digest, sha2::Sha256};
let mut hasher = Sha256::new();
hasher.input(arg1);
let mut bytes = vec![0; hasher.output_bytes()];
hasher.result(&mut bytes);
Ok(Value::Con(Constant::ByteString(bytes)))
}
_ => unreachable!(),
},
DefaultFunction::Sha3_256 => match &args[0] {
Value::Con(Constant::ByteString(arg1)) => {
use cryptoxide::{digest::Digest, sha3::Sha3_256};
let mut hasher = Sha3_256::new();
hasher.input(arg1);
let mut bytes = vec![0; hasher.output_bytes()];
hasher.result(&mut bytes);
Ok(Value::Con(Constant::ByteString(bytes)))
}
_ => unreachable!(),
},
DefaultFunction::Blake2b_256 => match &args[0] {
Value::Con(Constant::ByteString(arg1)) => {
use cryptoxide::{blake2b::Blake2b, digest::Digest};
let mut digest = [0u8; 32];
let mut context = Blake2b::new(32);
context.input(arg1);
context.result(&mut digest);
Ok(Value::Con(Constant::ByteString(digest.to_vec())))
}
_ => unreachable!(),
},
DefaultFunction::VerifySignature => todo!(),
DefaultFunction::VerifyEcdsaSecp256k1Signature => todo!(),
DefaultFunction::VerifySchnorrSecp256k1Signature => todo!(),
DefaultFunction::AppendString => todo!(),
DefaultFunction::EqualsString => todo!(),
DefaultFunction::EncodeUtf8 => todo!(),
DefaultFunction::DecodeUtf8 => todo!(),
DefaultFunction::AppendString => {
let args = (&args[0], &args[1]);
match args {
(Value::Con(Constant::String(arg1)), Value::Con(Constant::String(arg2))) => {
Ok(Value::Con(Constant::String(format!("{}{}", arg1, arg2))))
}
_ => unreachable!(),
}
}
DefaultFunction::EqualsString => {
let args = (&args[0], &args[1]);
match args {
(Value::Con(Constant::String(arg1)), Value::Con(Constant::String(arg2))) => {
Ok(Value::Con(Constant::Bool(arg1 == arg2)))
}
_ => unreachable!(),
}
}
DefaultFunction::EncodeUtf8 => match &args[0] {
Value::Con(Constant::String(arg1)) => {
let bytes = arg1.as_bytes().to_vec();
Ok(Value::Con(Constant::ByteString(bytes)))
}
_ => unreachable!(),
},
DefaultFunction::DecodeUtf8 => match &args[0] {
Value::Con(Constant::ByteString(arg1)) => {
let string = String::from_utf8(arg1.clone())?;
Ok(Value::Con(Constant::String(string)))
}
_ => unreachable!(),
},
DefaultFunction::IfThenElse => match args[0] {
Value::Con(Constant::Bool(condition)) => {
if condition {
@ -323,8 +459,18 @@ impl DefaultFunction {
}
_ => unreachable!(),
},
DefaultFunction::ChooseUnit => todo!(),
DefaultFunction::Trace => todo!(),
DefaultFunction::ChooseUnit => match &args[0] {
Value::Con(Constant::Unit) => Ok(args[1].clone()),
_ => unreachable!(),
},
DefaultFunction::Trace => match &args[0] {
Value::Con(Constant::String(arg1)) => {
logs.push(arg1.clone());
Ok(args[1].clone())
}
_ => unreachable!(),
},
DefaultFunction::FstPair => todo!(),
DefaultFunction::SndPair => todo!(),
DefaultFunction::ChooseList => todo!(),