diff --git a/crates/uplc/src/pretty.rs b/crates/uplc/src/pretty.rs index 2ddd7762..c34f3928 100644 --- a/crates/uplc/src/pretty.rs +++ b/crates/uplc/src/pretty.rs @@ -3,8 +3,10 @@ use crate::{ flat::Binder, plutus_data_to_bytes, }; +use pallas_codec::utils::KeyValuePairs; +use pallas_primitives::babbage::{PlutusData, Constr}; use pretty::RcDoc; -use std::ascii::escape_default; +use std::{ascii::escape_default, io::Read}; impl<'a, T> Program where @@ -201,31 +203,31 @@ impl Constant { Constant::Bool(b) => RcDoc::text("bool") .append(RcDoc::line()) .append(RcDoc::text(if *b { "True" } else { "False" })), - Constant::ProtoList(r#type, items) => RcDoc::text("list") - .append(RcDoc::line_()) - .append(RcDoc::text("<")) + Constant::ProtoList(r#type, items) => RcDoc::text("(") + .append("list") + .append(RcDoc::space()) .append(r#type.to_doc()) - .append(RcDoc::text(">")) + .append(")") .append(RcDoc::line()) .append(RcDoc::text("[")) .append(RcDoc::intersperse( items.iter().map(|c| c.to_doc_list()), - RcDoc::text(","), + RcDoc::text(", "), )) .append(RcDoc::text("]")), - Constant::ProtoPair(type_left, type_right, left, right) => RcDoc::text("pair") - .append(RcDoc::line_()) - .append(RcDoc::text("<")) + Constant::ProtoPair(type_left, type_right, left, right) => RcDoc::text("(") + .append("pair") + .append(RcDoc::space()) .append(type_left.to_doc()) - .append(RcDoc::text(", ")) + .append(RcDoc::space()) .append(type_right.to_doc()) - .append(RcDoc::text(">")) + .append(RcDoc::text(")")) .append(RcDoc::line()) - .append(RcDoc::text("[")) + .append(RcDoc::text("(")) .append(left.to_doc_list()) - .append(RcDoc::text(",")) + .append(RcDoc::text(", ")) .append(right.to_doc_list()) - .append(RcDoc::text("]")), + .append(RcDoc::text(")")), d @ Constant::Data(_) => RcDoc::text("data ").append(d.to_doc_list()), } } @@ -242,18 +244,66 @@ impl Constant { Constant::ProtoList(_, items) => RcDoc::text("[") .append(RcDoc::intersperse( items.iter().map(|c| c.to_doc_list()), - RcDoc::text(","), + RcDoc::text(", "), )) .append(RcDoc::text("]")), - Constant::ProtoPair(_, _, left, right) => RcDoc::text("[") + Constant::ProtoPair(_, _, left, right) => RcDoc::text("(") .append((*left).to_doc_list()) .append(RcDoc::text(", ")) .append((*right).to_doc_list()) - .append(RcDoc::text("]")), + .append(RcDoc::text(")")), - Constant::Data(data) => RcDoc::text("#").append(RcDoc::text(hex::encode( - plutus_data_to_bytes(data).unwrap(), - ))), + Constant::Data(data) => RcDoc::text("(") + .append(Self::to_doc_list_plutus_data(&data)) + .append(RcDoc::text(")")) + } + } + + // This feels a little awkward here; not sure if it should be upstreamed to pallas + fn to_doc_list_plutus_data(data: &PlutusData) -> RcDoc<()> { + match data { + PlutusData::Constr(Constr{ tag, fields, ..}) => RcDoc::text("Constr") + .append(RcDoc::space()) + .append(RcDoc::as_string(tag)) + .append(RcDoc::space()) + .append(RcDoc::text("[")) + .append(RcDoc::intersperse( + fields.iter().map(|f| Self::to_doc_list_plutus_data(f)), + RcDoc::text(", "), + )) + .append(RcDoc::text("]")), + PlutusData::Map(kvp) => RcDoc::text("Map") + .append(RcDoc::space()) + .append(RcDoc::text("[")) + .append(RcDoc::intersperse( + kvp.iter().map(|(key, value)| RcDoc::text("(") + .append(Self::to_doc_list_plutus_data(key)) + .append(RcDoc::text(", ")) + .append(Self::to_doc_list_plutus_data(value)) + .append(RcDoc::text(")")), + ), + RcDoc::text(", "), + )) + .append(RcDoc::text("]")), + PlutusData::BigInt(bi) => RcDoc::text("I") + .append(RcDoc::space()) + .append(match bi { + pallas_primitives::babbage::BigInt::Int(v) => RcDoc::text(v.to_string()), + pallas_primitives::babbage::BigInt::BigUInt(v) => RcDoc::text(v.to_string()), + pallas_primitives::babbage::BigInt::BigNInt(v) => RcDoc::text(v.to_string()), + }), + PlutusData::BoundedBytes(bs) => RcDoc::text("B") + .append(RcDoc::space()) + .append(RcDoc::text("#")) + .append(RcDoc::text(hex::encode(bs.to_vec()))), + PlutusData::Array(a) => RcDoc::text("List") + .append(RcDoc::space()) + .append(RcDoc::text("[")) + .append(RcDoc::intersperse( + a.iter().map(|item| Self::to_doc_list_plutus_data(item)), + RcDoc::text(", "), + )) + .append(RcDoc::text("]")), } } } @@ -266,10 +316,11 @@ impl Type { Type::String => RcDoc::text("string"), Type::ByteString => RcDoc::text("bytestring"), Type::Unit => RcDoc::text("unit"), - Type::List(r#type) => RcDoc::text("list") - .append(RcDoc::text("<")) + Type::List(r#type) => RcDoc::text("(list") + .append(RcDoc::line()) .append(r#type.to_doc()) - .append(RcDoc::text(">")), + .append(RcDoc::line_()) + .append(")"), Type::Pair(l, r) => RcDoc::text("pair") .append(RcDoc::text("<")) .append(l.to_doc()) diff --git a/crates/uplc/tests/pretty_tests.rs b/crates/uplc/tests/pretty_tests.rs new file mode 100644 index 00000000..cde45713 --- /dev/null +++ b/crates/uplc/tests/pretty_tests.rs @@ -0,0 +1,125 @@ +use num_bigint::{ToBigInt}; +use uplc::{ + ast::{DeBruijn, Constant, Type, Term}, PlutusData, Constr, +}; + +// Examples sourced from https://github.com/input-output-hk/plutus/issues/4751#issuecomment-1538377273 + +#[test] +fn constant_list_integer() { + let term = Term::::Constant(Constant::ProtoList(Type::Integer.into(), vec![ + Constant::Integer(0.to_bigint().unwrap()), + Constant::Integer(1.to_bigint().unwrap()), + Constant::Integer(2.to_bigint().unwrap()), + ]).into()); + assert_eq!(term.to_pretty(), "(con (list integer) [0, 1, 2])"); +} + +#[test] +fn constant_pair_bool_bytestring() { + let term = Term::::Constant(Constant::ProtoPair( + Type::Bool.into(), Type::ByteString.into(), + Constant::Bool(true).into(), Constant::ByteString(vec![0x01, 0x23, 0x45]).into(), + ).into()); + assert_eq!(term.to_pretty(), "(con (pair bool bytestring) (True, #012345))"); +} + +#[test] +fn constant_pair_unit_string() { + let term = Term::::Constant(Constant::ProtoPair( + Type::Unit.into(), Type::String.into(), + Constant::Unit.into(), Constant::String("hello universe".into()).into(), + ).into()); + assert_eq!(term.to_pretty(), "(con (pair unit string) ((), \"hello universe\"))") +} + +#[test] +fn constant_deeply_nested_list() { + let t0 = Type::Integer; + let t1 = Type::List(t0.clone().into()); + let t2 = Type::List(t1.clone().into()); + let term = Term::::Constant(Constant::ProtoList(t2, vec![ + Constant::ProtoList(t1.clone(), vec![ + Constant::ProtoList(t0.clone(), vec![ + Constant::Integer(-1.to_bigint().unwrap()), + ]), + Constant::ProtoList(t0.clone(), vec![]) + ]), + Constant::ProtoList(t1.clone(), vec![ + Constant::ProtoList(t0.clone(), vec![]), + Constant::ProtoList(t0.clone(), vec![ + Constant::Integer(2.to_bigint().unwrap()), + Constant::Integer(3.to_bigint().unwrap()) + ]), + ]) + ]).into()); + assert_eq!(term.to_pretty(), "(con (list (list (list integer))) [[[-1], []], [[], [2, 3]]])"); +} + +#[test] +fn constant_data_constr() { + let term = Term::::Constant(Constant::Data( + PlutusData::Constr(Constr:: { + tag: 1, + any_constructor: None, + fields: vec![PlutusData::BigInt(pallas_primitives::alonzo::BigInt::Int(2.into()))] + }) + ).into()); + assert_eq!(term.to_pretty(), "(con data (Constr 1 [I 2]))"); +} + +#[test] +fn constant_data_map() { + let term = Term::::Constant(Constant::Data( + PlutusData::Map(uplc::KeyValuePairs::Def(vec![ + ( + PlutusData::BigInt(pallas_primitives::alonzo::BigInt::Int(0.into())), + PlutusData::BoundedBytes(vec![0x00].into()), + ), + ( + PlutusData::BigInt(pallas_primitives::alonzo::BigInt::Int(1.into())), + PlutusData::BoundedBytes(vec![0x0f].into()), + ), + ])) + ).into()); + assert_eq!(term.to_pretty(), "(con data (Map [(I 0, B #00), (I 1, B #0f)]))"); +} + +#[test] +fn constant_data_list() { + let term = Term::::Constant(Constant::Data( + PlutusData::Array(vec![ + PlutusData::BigInt(pallas_primitives::alonzo::BigInt::Int(0.into())), + PlutusData::BigInt(pallas_primitives::alonzo::BigInt::Int(1.into())), + ]) + ).into()); + assert_eq!(term.to_pretty(), "(con data (List [I 0, I 1]))"); +} + +#[test] +fn constant_data_int() { + let term = Term::::Constant(Constant::Data( + PlutusData::BigInt(pallas_primitives::alonzo::BigInt::Int(2.into())), + ).into()); + assert_eq!(term.to_pretty(), "(con data (I 2))"); + + // TODO: large integers currently encode as bytestrings, which isn't great + /* + let term = Term::::Constant(Constant::Data( + PlutusData::BigInt(pallas_primitives::alonzo::BigInt::BigUInt(vec![2,3,4].into())), + ).into()); + assert_eq!(term.to_pretty(), "(con data (I 131844))"); + let term = Term::::Constant(Constant::Data( + PlutusData::BigInt(pallas_primitives::alonzo::BigInt::BigNInt(vec![FF,FD,FC,FC].into())), + ).into()); + assert_eq!(term.to_pretty(), "(con data (I -131844))"); + */ +} + +#[test] +fn constant_data_bytes() { + let term = Term::::Constant(Constant::Data( + PlutusData::BoundedBytes(vec![0x00, 0x1A].into()), + ).into()); + assert_eq!(term.to_pretty(), "(con data (B #001a))"); +} \ No newline at end of file