From c683f709d504e3f38897e0a462945dc6eac65612 Mon Sep 17 00:00:00 2001 From: Toanh Date: Fri, 15 Mar 2024 13:45:00 +0700 Subject: [PATCH] Fix format issues --- lib/stringify.ak | 362 ++++++++++++++++++++++++++++++++++-------- lib/stringify_test.ak | 12 ++ lib/utils.ak | 13 +- 3 files changed, 313 insertions(+), 74 deletions(-) diff --git a/lib/stringify.ak b/lib/stringify.ak index 7c68640..925666a 100644 --- a/lib/stringify.ak +++ b/lib/stringify.ak @@ -1,11 +1,12 @@ +use aiken/builtin use aiken/bytearray use aiken/cbor -use aiken/dict +use aiken/dict.{Dict} use aiken/list use aiken/string use aiken/transaction.{ - Datum, InlineDatum, Input, Mint, NoDatum, Output, OutputReference, Spend, - Transaction, TransactionId, + Datum, InlineDatum, Input, Mint, NoDatum, Output, OutputReference, Publish, + Redeemer, ScriptPurpose, Spend, Transaction, TransactionId, WithdrawFrom, } use aiken/transaction/credential.{ Address, Credential, Inline, ScriptCredential, StakeCredential, @@ -13,7 +14,7 @@ use aiken/transaction/credential.{ } use aiken/transaction/value.{MintedValue, Value, flatten, from_minted_value} as value_utils -use utils.{format_cbor, is_close, is_open, to_bytearray} +use utils.{is_close, is_open, to_bytearray} pub fn int(val) -> String { string.from_int(val) @@ -23,18 +24,15 @@ fn credential(cred: Credential) -> String { when cred is { VerificationKeyCredential(hash) -> string.concat( - @"VerificationKeyCredential(", - string.from_bytearray(hash) |> with_single_quotes, + @"VerificationKeyCredential", + string.from_bytearray(hash) |> with_single_quotes |> with_parentheses, ) - |> string.concat(@")") ScriptCredential(hash) -> string.concat( - @"ScriptCredential(", - string.from_bytearray(hash) |> with_single_quotes, + @"ScriptCredential", + string.from_bytearray(hash) |> with_single_quotes |> with_parentheses, ) - |> string.concat(@")") } - |> indent } pub fn stake_credential(self: Option) { @@ -43,10 +41,13 @@ pub fn stake_credential(self: Option) { Some(Inline(cred)) -> credential(cred) _ -> fail @"Unsupported type of stake credential" } - |> indent } pub fn out_ref(self: OutputReference) { + do_out_ref(self) |> indent +} + +fn do_out_ref(self: OutputReference) { let OutputReference(TransactionId(tx_hash), index) = self @"OutputReference(TransactionId({}), {})" |> apply_params( @@ -55,10 +56,13 @@ pub fn out_ref(self: OutputReference) { string.from_int(index), ], ) - |> indent } pub fn address(self: Address) { + do_address(self) |> indent +} + +fn do_address(self: Address) { @"Address {\npayment_credential: {},\nstake_credential: {},\n}" |> apply_params( [ @@ -66,26 +70,46 @@ pub fn address(self: Address) { stake_credential(self.stake_credential), ], ) - |> indent } pub fn value(self: Value) { - [@"Value(", @")"] - |> string.join( + do_value(self) |> indent +} + +fn do_value(self: Value) { + @"Value" + |> string.concat( self |> flatten - |> list.map(fn(asset) { asset |> cbor.diagnostic |> format_cbor }) - |> string.join(@","), + |> list.map( + fn(asset) { + let (pid, name, qty) = asset + [ + string.from_bytearray(pid) |> with_single_quotes, + string.from_bytearray(name) |> with_single_quotes, + string.from_int(qty), + ] + |> string.join(@", ") + |> with_parentheses + }, + ) + |> string.join( + @",\n", + ) + |> as_block + |> with_square_brackets + |> with_parentheses, ) - |> indent } pub fn minted_value(self: MintedValue) { value(from_minted_value(self)) } -pub fn data(self: Data) { - cbor.diagnostic(self) |> format_cbor +pub fn data(self: Data) -> String { + cbor.diagnostic(self) + |> format_cbor + |> indent } pub fn datum(self: Datum) { @@ -93,57 +117,82 @@ pub fn datum(self: Datum) { NoDatum -> @"NoDatum" InlineDatum(dtm) -> @"InlineDatum({})" - |> apply_params([cbor.diagnostic(dtm) |> format_cbor]) + |> apply_params( + [ + cbor.diagnostic(dtm) + |> format_cbor, + ], + ) _ -> fail @"unsupported datum" } } pub fn output(self: Output) { + do_output(self) |> indent +} + +fn do_output(self: Output) { @"Output {\naddress: {},\nvalue: {},\ndatum: {}\n}" |> apply_params([address(self.address), value(self.value), datum(self.datum)]) - |> indent } pub fn input(self: Input) { + do_input(self) |> indent +} + +fn do_input(self: Input) { @"Input {\noutput_reference: {},\noutput: {}\n}" - |> apply_params([out_ref(self.output_reference), output(self.output)]) - |> indent + |> apply_params([do_out_ref(self.output_reference), do_output(self.output)]) +} + +pub fn redeemers(self: Dict) { + do_redeemers(self) |> indent +} + +fn do_redeemers(self: Dict) { + list.map( + dict.keys(self), + fn(purpose) { + string.concat( + @"Purpose", + when purpose is { + Spend(ref) -> + @"Spend" + |> string.concat( + out_ref(ref) + |> with_parentheses, + ) + Mint(pid) -> + @"Mint" + |> string.concat( + string.from_bytearray(pid) + |> with_single_quotes + |> with_parentheses, + ) + WithdrawFrom(cred) -> + string.concat( + @"WithdrawFrom", + stake_credential(Some(cred)) |> with_parentheses, + ) + Publish(cert) -> + string.concat(@"Publish", cbor.diagnostic(cert) |> with_parentheses) + } + |> with_parentheses, + ) + }, + ) + |> string.join( + @",\n", + ) + |> as_block + |> with_square_brackets } pub fn tx(self: Transaction) { @"Transaction {\nredeemers: {},\nextra_signatories: {},\nmint: {},\nreference_inputs: {},\ninputs: {},\noutputs: {},\n}" |> apply_params( [ - list.map( - dict.keys(self.redeemers), - fn(purpose) { - @"Purpose" - |> string.concat( - when purpose is { - Spend(ref) -> - @"Spend" - |> string.concat( - out_ref(ref) - |> with_parentheses, - ) - Mint(pid) -> - @"Mint" - |> string.concat( - string.from_bytearray(pid) - |> with_single_quotes - |> with_parentheses, - ) - _ -> @"" - } - |> with_parentheses, - ) - }, - ) - |> string.join( - @",\n", - ) - |> as_block - |> with_square_brackets, + do_redeemers(self.redeemers), self.extra_signatories |> list.map( fn(sig) { @@ -153,11 +202,11 @@ pub fn tx(self: Transaction) { ) |> string.join(@", ") |> with_square_brackets, - minted_value(self.mint), + value(self.mint |> from_minted_value), when self.reference_inputs is { [] -> @"[]" _ -> - list.map(self.reference_inputs, fn(inp) { input(inp) }) + list.map(self.reference_inputs, fn(inp) { do_input(inp) }) |> string.join( @",\n", ) @@ -167,7 +216,7 @@ pub fn tx(self: Transaction) { when self.inputs is { [] -> @"[]" _ -> - list.map(self.inputs, fn(inp) { input(inp) }) + list.map(self.inputs, fn(inp) { do_input(inp) }) |> string.join( @",\n", ) @@ -177,7 +226,7 @@ pub fn tx(self: Transaction) { when self.outputs is { [] -> @"[]" _ -> - list.map(self.outputs, fn(out) { output(out) }) + list.map(self.outputs, fn(out) { do_output(out) }) |> string.join( @",\n", ) @@ -189,7 +238,7 @@ pub fn tx(self: Transaction) { |> indent } -// ==========private fn============ +// ========== utils ============ fn tab(lvl: Int) { list.repeat(@" ", lvl) |> string.join(@"") } @@ -198,8 +247,8 @@ pub fn indent(str: String) { do_indent(string.to_bytearray(str), 0) |> bytearray.to_string } -pub fn log(self: a, serializer: fn(a) -> String) { - trace serializer(self) +pub fn log(self: a, msg: String, serializer: fn(a) -> String) -> a { + trace string.concat(msg, serializer(self)) self } @@ -208,7 +257,7 @@ fn do_indent(bytes: ByteArray, lvl: Int) { when next_newline_cursor is { Some((_, cursor)) -> { let left = bytearray.take(bytes, cursor) - let right = bytearray.drop(bytes, cursor + 1) + let right = bytearray.slice(bytes, cursor + 1, bytearray.length(bytes)) let indent_lvl = bytearray.foldl( left, @@ -281,3 +330,190 @@ fn as_block(builder: String) { ] |> string.join(builder) } + +pub fn first_of_quad(quad: (a, b, c, d)) { + quad.1st +} + +pub fn first_of_triple(tuple: (a, b, c)) { + tuple.1st +} + +pub fn pop_back(self: ByteArray, qty: Int) { + bytearray.take(self, builtin.length_of_bytearray(self) - qty) +} + +pub fn last_char(self: ByteArray) { + bytearray.drop(self, builtin.length_of_bytearray(self) - 1) +} + +pub fn is_open(chr: ByteArray) { + or { + chr == "[", + chr == "{", + } +} + +pub fn is_close(chr: ByteArray) { + or { + chr == "]", + chr == "}", + } +} + +pub fn newline(self: ByteArray) { + self |> bytearray.concat("\n") +} + +pub fn to_decimal(byte: Int) { + if byte >= 97 && byte <= 102 { + byte - 97 + 10 + } else if byte >= 65 && byte <= 70 { + byte - 65 + 10 + } else if byte >= 48 && byte <= 57 { + byte - 48 + } else { + fail @"invalid digit" + } +} + +pub fn hex_to_string(hex: ByteArray) { + bytearray.foldl( + // pushing a dummy byte to flush the last character + hex |> bytearray.concat("0"), + ("", 0, 0), + fn(byte, results) { + let (acc, code, idx) = results + let dec = to_decimal(byte) + if idx > 0 && idx % 2 == 0 { + ( + acc + |> bytearray.concat(code |> to_safe_code |> to_bytearray), + dec, + idx + 1, + ) + } else { + (acc, code * 16 + dec, idx + 1) + } + }, + ) + |> first_of_triple +} + +fn format_cbor(cbr: String) { + cbr + |> bytearray.from_string + |> do_format_cbor + |> bytearray.to_string +} + +pub fn to_bytearray(byte: Int) { + #"" |> bytearray.push(byte) +} + +pub fn do_format_cbor(bytes) { + bytes + |> bytearray.foldl( + ("", 0, False, ""), + fn(byte, results) { + let (builder, level, has_token, token) = results + let chr = to_bytearray(byte) + if is_open(chr) { + ( + builder + |> bytearray.concat(chr), + level + 1, + has_token, + token, + ) + } else if is_close(chr) { + ( + builder + |> fn(builder) { + if !has_token && builtin.length_of_bytearray(token) > 0 { + builder + |> bytearray.concat(", # ") + |> bytearray.concat( + token + |> hex_to_string, + ) + } else { + builder + } + } + |> fn(builder) { + // pretty empty struct to be inline + let last_chr = builder |> pop_back(level + 1) |> last_char + if is_open(last_chr) { + builder |> pop_back(level + 1) + } else { + builder |> newline + } + } + |> bytearray.concat(chr), + level - 1, + has_token, + "", + ) + } else if chr == "'" { + (builder |> bytearray.concat(chr), level, !has_token, token) + } else if chr == "," { + ( + builder + |> bytearray.concat(chr) + |> fn(builder) { + if !has_token && builtin.length_of_bytearray(token) > 0 { + builder + |> bytearray.concat(" # ") + |> bytearray.concat( + token + |> hex_to_string, + ) + } else { + builder + } + } + |> newline, + level, + has_token, + if !has_token { + "" + } else { + token + }, + ) + } else if chr == "_" { + ( + builder // |> pop_back(level + 1) + |> newline, + level, + has_token, + token, + ) + } else if chr == " " { + (builder, level, has_token, token) + } else { + ( + builder |> bytearray.concat(chr), + level, + has_token, + if has_token { + token |> bytearray.concat(chr) + } else { + token + }, + ) + } + }, + ) + |> first_of_quad +} + +pub fn to_safe_code(code: Int) -> Int { + if code > 127 { + // fallback to '?' when the code is a special character + 63 + } else { + code + } +} diff --git a/lib/stringify_test.ak b/lib/stringify_test.ak index 2cd65fc..f1af827 100644 --- a/lib/stringify_test.ak +++ b/lib/stringify_test.ak @@ -92,6 +92,18 @@ test log_output_2() { ( my_output |> log(stringify.output) ) == my_output } +test log_data() { + let struct = + MyDatum { + bytes: "bytes", + int: 1, + str: @"str", + int_list: [1, 2, 3, 4], + bytes_list: ["1", "2", "3"], + } + ( struct |> log(fn(d) { stringify.data(d) }) ) == struct +} + fn script_purpose_compare(a: ScriptPurpose, b: ScriptPurpose) { bytearray.compare( a |> cbor.diagnostic |> string.to_bytearray, diff --git a/lib/utils.ak b/lib/utils.ak index 83674c2..4b2a067 100644 --- a/lib/utils.ak +++ b/lib/utils.ak @@ -1,6 +1,5 @@ use aiken/builtin use aiken/bytearray -use aiken/list pub fn first_of_quad(quad: (a, b, c, d)) { quad.1st @@ -34,16 +33,8 @@ pub fn is_close(chr: ByteArray) { } } -fn indent(self: ByteArray, level: Int) { - self - |> bytearray.concat( - list.repeat("\t", level) - |> list.reduce("", bytearray.concat), - ) -} - -pub fn newline(self: ByteArray, indent_lvl: Int) { - self |> bytearray.concat("\n") |> indent(indent_lvl) +pub fn newline(self: ByteArray, _indent_lvl: Int) { + self |> bytearray.concat("\n") } pub fn to_decimal(byte: Int) {