From 08262a9920ddc94d1602b23bc2d4e03c596288c0 Mon Sep 17 00:00:00 2001 From: Tomi Jaga Date: Tue, 15 Jul 2025 21:07:35 -0700 Subject: [PATCH 1/6] place holder [update commit message] --- .bench/serde.bench.json | 100 +++ bench/serde.bench.mo | 43 - bench/types.bench.mo | 1479 ++++++++++++++++++++++++++++++++ mops.toml | 17 +- src/Candid/Blob/Decoder.mo | 44 +- src/Candid/Blob/Encoder.mo | 10 +- src/Candid/Blob/RepIndyHash.mo | 2 +- src/Utils.mo | 16 +- tests/Candid.Large.test.mo | 19 +- tests/Candid.Test.mo | 19 +- 10 files changed, 1654 insertions(+), 95 deletions(-) create mode 100644 .bench/serde.bench.json create mode 100644 bench/types.bench.mo diff --git a/.bench/serde.bench.json b/.bench/serde.bench.json new file mode 100644 index 0000000..8d4258b --- /dev/null +++ b/.bench/serde.bench.json @@ -0,0 +1,100 @@ +{ + "version": 1, + "moc": "0.14.9", + "replica": "dfx", + "replicaVersion": "0.26.0", + "gc": "copying", + "forceGc": true, + "results": [ + [ + "Serde: One Shot:decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 308306995, + "rts_memory_size": 0, + "rts_total_allocation": 22892744, + "rts_collector_instructions": 31603911, + "rts_mutator_instructions": -10283, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 1581832, + "rts_reclaimed": 21310912 + } + ], + [ + "Serde: One Shot:encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 1152554248, + "rts_memory_size": 38076416, + "rts_total_allocation": 65628396, + "rts_collector_instructions": 6995, + "rts_mutator_instructions": -7896, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 308, + "rts_reclaimed": 65628088 + } + ], + [ + "Serde: One Shot sans type inference:decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 385683071, + "rts_memory_size": 0, + "rts_total_allocation": 26540744, + "rts_collector_instructions": 6638, + "rts_mutator_instructions": -7896, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 272, + "rts_reclaimed": 26540472 + } + ], + [ + "Serde: One Shot sans type inference:encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 918929773, + "rts_memory_size": 0, + "rts_total_allocation": 35914440, + "rts_collector_instructions": 6638, + "rts_mutator_instructions": -7896, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 272, + "rts_reclaimed": 35914168 + } + ], + [ + "Motoko (to_candid(), from_candid()):decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 31841278, + "rts_memory_size": 0, + "rts_total_allocation": 540264, + "rts_collector_instructions": 6638, + "rts_mutator_instructions": -7896, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 272, + "rts_reclaimed": 539992 + } + ], + [ + "Motoko (to_candid(), from_candid()):encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 10262063, + "rts_memory_size": 0, + "rts_total_allocation": 596508, + "rts_collector_instructions": 6638, + "rts_mutator_instructions": -7896, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 272, + "rts_reclaimed": 596236 + } + ] + ] +} \ No newline at end of file diff --git a/bench/serde.bench.mo b/bench/serde.bench.mo index c918c11..72573f3 100644 --- a/bench/serde.bench.mo +++ b/bench/serde.bench.mo @@ -24,10 +24,8 @@ module { bench.rows([ "Serde: One Shot", "Serde: One Shot sans type inference", - // "Serde 'mo:motoko_candid' lib", "Motoko (to_candid(), from_candid())", - // "#Nat" ]); bench.cols([ @@ -185,23 +183,6 @@ module { }; }; - // case ("Serde 'mo:motoko_candid' lib", "decode()") { - // for (i in Itertools.range(0, limit)) { - // let item : StoreItem = buffer.get(i); - // let candid_blob = candify_store_item.to_blob(item); - // candid_blobs.add(candid_blob); - // let #ok(candid) = LegacyCandidDecoder.decode(candid_blob, StoreItemKeys, null); - // candid_buffer.add(candid); - // }; - // }; - // case ("Serde 'mo:motoko_candid' lib", "encode()") { - // for (i in Itertools.range(0, limit)) { - // let candid = candid_buffer.get(i); - // let res = LegacyCandidEncoder.encode(candid, null); - // let #ok(blob) = res; - // }; - // }; - case ("Serde: One Shot", "decode()") { for (i in Itertools.range(0, limit)) { let item = buffer.get(i); @@ -224,8 +205,6 @@ module { let item = buffer.get(i); let candid_blob = candify_store_item.to_blob(item); - let FormattedStoreItem = Serde.Candid.formatCandidType([StoreItem], null); - let options = { Serde.Candid.defaultOptions with types = ?FormattedStoreItem }; @@ -256,28 +235,6 @@ module { }; }; - case ("Serde: One Shot FR sans type inference", "decode()") {}; - - case ("Serde: One Shot FR sans type inference", "encode()") { - for (i in Itertools.range(0, limit)) { - let candid = candid_buffer.get(i); - - let options = { - Serde.Candid.defaultOptions with types = ?[StoreItem] - }; - let res = CandidEncoderFR.one_shot(candid, ?options); - let #ok(blob) = res; - }; - }; - - // case ("#Nat", "decode()"){ - // for (i in Itertools.range(0, limit)) { - // let item = buffer.get(i); - // let candid_blob = candify_store_item.to_blob(item); - // let #ok(candid) = CandidDecoder.decode(candid_blob, StoreItemKeys, null); - // // candid_buffer.add(candid); - // }; - // }; case (_, _) { Debug.trap("Should be unreachable:\n row = \"" # debug_show row # "\" and col = \"" # debug_show col # "\""); }; diff --git a/bench/types.bench.mo b/bench/types.bench.mo new file mode 100644 index 0000000..be96199 --- /dev/null +++ b/bench/types.bench.mo @@ -0,0 +1,1479 @@ +import Iter "mo:base/Iter"; +import Debug "mo:base/Debug"; +import Prelude "mo:base/Prelude"; +import Text "mo:base/Text"; +import Char "mo:base/Char"; +import Buffer "mo:base/Buffer"; +import Array "mo:base/Array"; +import Blob "mo:base/Blob"; +import Principal "mo:base/Principal"; +import Int "mo:base/Int"; +import Int8 "mo:base/Int8"; +import Int16 "mo:base/Int16"; +import Int32 "mo:base/Int32"; +import Int64 "mo:base/Int64"; +import Nat "mo:base/Nat"; +import Nat8 "mo:base/Nat8"; +import Nat16 "mo:base/Nat16"; +import Nat32 "mo:base/Nat32"; +import Nat64 "mo:base/Nat64"; +import Float "mo:base/Float"; + +import Bench "mo:bench"; +import Fuzz "mo:fuzz"; +import Itertools "mo:itertools/Iter"; + +import Serde "../src"; +import CandidEncoder "../src/Candid/Blob/Encoder"; +import CandidDecoder "../src/Candid/Blob/Decoder"; + +module { + public func init() : Bench.Bench { + let bench = Bench.Bench(); + + bench.name("Benchmarking Serde by Data Types"); + bench.description("Performance comparison across all supported Candid data types with 10k operations"); + + bench.rows([ + // Primitive Types + "Nat", + "Nat8", + "Nat16", + "Nat32", + "Nat64", + "Int", + "Int8", + "Int16", + "Int32", + "Int64", + "Float", + "Bool", + "Text", + "Null", + "Empty", + "Principal", + "Blob", + + // Compound Types + "Option(Nat)", + "Option(Text)", + "Array(Nat8)", + "Array(Text)", + "Array(Record)", + // "Record(Simple)", // failing with 'Blob index out of bounds' + "Record(Nested)", + // "Tuple(Mixed)", // failing with 'Blob index out of bounds' + "Variant(Simple)", + "Variant(Complex)", + // "Map(Text->Nat)", // redundant - same as record + + // Performance Edge Cases + "Large Text", + "Large Array", + "Deep Nesting", + "Wide Record", + // "Recursive Structure", // not supported + ]); + + bench.cols([ + "encode()", + "encode(sans inference)", + "decode()", + "decode(sans inference)", + ]); + + type Candid = Serde.Candid; + + func createGenerator(seed : Nat) : { next() : Nat } { + // Pure bitwise xorshift64 - no multiplication or addition! + var state : Nat64 = Nat64.fromNat(seed); + if (state == 0) state := 1; // Avoid zero state + + { + next = func() : Nat { + // Only XOR and bit shifts - fastest possible + state ^= state << 13 : Nat64; + state ^= state >> 7 : Nat64; + state ^= state << 17 : Nat64; + Nat64.toNat(state); + }; + }; + }; + + let fuzz = Fuzz.create(createGenerator(42)); + let limit = 1; + + // Generate test data for each type + let nat_values = Buffer.Buffer(limit); + let nat8_values = Buffer.Buffer(limit); + let nat16_values = Buffer.Buffer(limit); + let nat32_values = Buffer.Buffer(limit); + let nat64_values = Buffer.Buffer(limit); + let int_values = Buffer.Buffer(limit); + let int8_values = Buffer.Buffer(limit); + let int16_values = Buffer.Buffer(limit); + let int32_values = Buffer.Buffer(limit); + let int64_values = Buffer.Buffer(limit); + let float_values = Buffer.Buffer(limit); + let bool_values = Buffer.Buffer(limit); + let text_values = Buffer.Buffer(limit); + let principal_values = Buffer.Buffer(limit); + let blob_values = Buffer.Buffer(limit); + + // Compound type test data + let option_nat_values = Buffer.Buffer(limit); + let option_text_values = Buffer.Buffer(limit); + let array_nat8_values = Buffer.Buffer<[Nat8]>(limit); + let array_text_values = Buffer.Buffer<[Text]>(limit); + + // Complex structures + type SimpleRecord = { id : Nat; name : Text; active : Bool }; + type NestedRecord = { + user : SimpleRecord; + metadata : { created : Int; tags : [Text] }; + settings : ?{ theme : Text; notifications : Bool }; + }; + type MixedTuple = (Nat, Text, Bool, ?Float); + type SimpleVariant = { #success : Nat; #error : Text; #pending }; + type ComplexVariant = { + #user : SimpleRecord; + #admin : NestedRecord; + #guest; + }; + + let simple_record_values = Buffer.Buffer(limit); + let nested_record_values = Buffer.Buffer(limit); + let mixed_tuple_values = Buffer.Buffer(limit); + let simple_variant_values = Buffer.Buffer(limit); + let complex_variant_values = Buffer.Buffer(limit); + let map_values = Buffer.Buffer<[(Text, Nat)]>(limit); + + // Edge case data + let large_text_values = Buffer.Buffer(limit); + let large_array_values = Buffer.Buffer<[Nat]>(limit); + let deep_nesting_values = Buffer.Buffer(limit); + let wide_record_values = Buffer.Buffer(limit); + + let random_principal = fuzz.principal.randomPrincipal(29); + + Debug.print("Generating test data for all types..."); + + // Populate test data + for (i in Itertools.range(0, limit)) { + // Primitive types + let nat = fuzz.nat.randomRange(0, 1_000_000); + let nat8 = fuzz.nat8.random(); + let text = fuzz.text.randomAlphanumeric(fuzz.nat.randomRange(5, 10)); + let int = fuzz.int.randomRange(-1_000_000, 1_000_000); + + nat_values.add(nat); + nat8_values.add(nat8); + nat16_values.add(fuzz.nat16.random()); + nat32_values.add(fuzz.nat32.random()); + nat64_values.add(fuzz.nat64.random()); + int_values.add(int); + int8_values.add(fuzz.int8.random()); + int16_values.add(fuzz.int16.random()); + int32_values.add(fuzz.int32.random()); + int64_values.add(fuzz.int64.random()); + float_values.add(fuzz.float.randomRange(-1000.0, 1000.0)); + bool_values.add(fuzz.bool.random()); + text_values.add(text); + principal_values.add(random_principal); + blob_values.add(Blob.fromArray(fuzz.array.randomArray(fuzz.nat.randomRange(10, 100), fuzz.nat8.random))); + + // Option types + option_nat_values.add(if (fuzz.bool.random()) ?nat else null); + option_text_values.add(if (fuzz.bool.random()) ?text else null); + + // Arrays + array_nat8_values.add(fuzz.array.randomArray(fuzz.nat.randomRange(3, 10), func() : Nat8 = nat8)); + array_text_values.add(fuzz.array.randomArray(fuzz.nat.randomRange(3, 10), func() = text)); + + // Records and complex types + simple_record_values.add({ + id = nat; + name = text; + active = fuzz.bool.random(); + }); + + nested_record_values.add({ + user = { + id = fuzz.nat.randomRange(1, 1000); + name = text; + active = fuzz.bool.random(); + }; + metadata = { + created = int; + tags = fuzz.array.randomArray(fuzz.nat.randomRange(1, 5), func() = text); + }; + settings = if (fuzz.bool.random()) ?{ + theme = fuzz.array.randomEntry(["dark", "light", "auto"]).1; + notifications = fuzz.bool.random(); + } else null; + }); + + mixed_tuple_values.add(( + fuzz.nat.randomRange(0, 1000), + fuzz.text.randomAlphanumeric(10), + fuzz.bool.random(), + if (fuzz.bool.random()) ?fuzz.float.randomRange(0.0, 100.0) else null, + )); + + simple_variant_values.add( + switch (fuzz.nat.randomRange(0, 2)) { + case (0) #success(nat); + case (1) #error(text); + case (_) #pending; + } + ); + + complex_variant_values.add( + switch (fuzz.nat.randomRange(0, 2)) { + case (0) #user({ + id = nat; + name = text; + active = fuzz.bool.random(); + }); + case (1) #admin({ + user = { + id = nat; + name = text; + active = fuzz.bool.random(); + }; + metadata = { + created = int; + tags = fuzz.array.randomArray(fuzz.nat.randomRange(1, 5), func() = text); + }; + settings = if (fuzz.bool.random()) ?{ + theme = fuzz.array.randomEntry(["dark", "light", "auto"]).1; + notifications = fuzz.bool.random(); + } else null; + }); + case (_) #guest; + } + ); + + map_values.add( + Array.tabulate<(Text, Nat)>( + fuzz.nat.randomRange(3, 8), + func(j) = ( + "key" # Nat.toText(j), + nat, + ), + ) + ); + + // Edge cases + large_text_values.add(fuzz.text.randomAlphanumeric(fuzz.nat.randomRange(1000, 5000))); + large_array_values.add(fuzz.array.randomArray(fuzz.nat.randomRange(500, 1000), func() = nat)); + + // Deep nesting - 5 levels deep + deep_nesting_values.add(#Record([("level1", #Record([("level2", #Record([("level3", #Record([("level4", #Record([("level5", #Nat(nat))]))]))]))]))])); + + // Wide record - 20 fields + wide_record_values.add( + #Record( + Array.tabulate<(Text, Candid)>( + 20, + func(j) = ( + "field" # Nat.toText(j), + #Nat(nat), + ), + ) + ) + ); + }; + + Debug.print("Generated test data for all types"); + + // Define type schemas for sans-inference benchmarks + let primitive_types = { + nat = [#Nat : Serde.Candid.CandidType]; + nat8 = [#Nat8 : Serde.Candid.CandidType]; + nat16 = [#Nat16 : Serde.Candid.CandidType]; + nat32 = [#Nat32 : Serde.Candid.CandidType]; + nat64 = [#Nat64 : Serde.Candid.CandidType]; + int = [#Int : Serde.Candid.CandidType]; + int8 = [#Int8 : Serde.Candid.CandidType]; + int16 = [#Int16 : Serde.Candid.CandidType]; + int32 = [#Int32 : Serde.Candid.CandidType]; + int64 = [#Int64 : Serde.Candid.CandidType]; + float = [#Float : Serde.Candid.CandidType]; + bool = [#Bool : Serde.Candid.CandidType]; + text = [#Text : Serde.Candid.CandidType]; + null_ = [#Null : Serde.Candid.CandidType]; + empty = [#Empty : Serde.Candid.CandidType]; + principal = [#Principal : Serde.Candid.CandidType]; + blob = [#Blob : Serde.Candid.CandidType]; + }; + + let compound_types = { + option_nat = [#Option(#Nat) : Serde.Candid.CandidType]; + option_text = [#Option(#Text)]; + array_nat8 = [#Array(#Nat8)]; + array_text = [#Array(#Text)]; + array_record = [#Array(#Record([("id", #Nat), ("name", #Text), ("active", #Bool)]))]; + simple_record = [#Record([("id", #Nat), ("name", #Text), ("active", #Bool)])]; + nested_record = [#Record([("user", #Record([("id", #Nat), ("name", #Text), ("active", #Bool)])), ("metadata", #Record([("created", #Int), ("tags", #Array(#Text))])), ("settings", #Option(#Record([("theme", #Text), ("notifications", #Bool)])))])]; + tuple_mixed = [#Tuple([#Nat, #Text, #Bool, #Option(#Float)])]; + variant_simple = [#Variant([("success", #Nat), ("error", #Text), ("pending", #Null)])]; + variant_complex = [#Variant([("user", #Record([("id", #Nat), ("name", #Text), ("active", #Bool)])), ("admin", #Record([("user", #Record([("id", #Nat), ("name", #Text), ("active", #Bool)])), ("metadata", #Record([("created", #Int), ("tags", #Array(#Text))])), ("settings", #Option(#Record([("theme", #Text), ("notifications", #Bool)])))])), ("guest", #Null)])]; + map_text_nat = [#Map([("", #Nat)])]; + large_text = [#Text]; + large_array = [#Array(#Nat)]; + deep_nesting = [#Record([("level1", #Record([("level2", #Record([("level3", #Record([("level4", #Record([("level5", #Nat)]))]))]))]))])]; + wide_record = [#Record(Array.tabulate<(Text, Serde.Candid.CandidType)>(20, func(j) = ("field" # Nat.toText(j), #Nat)))]; + }; + + // Format type schemas for optimal performance with sans-inference encoding + let formatted_primitive_types = { + nat = Serde.Candid.formatCandidType(primitive_types.nat, null); + nat8 = Serde.Candid.formatCandidType(primitive_types.nat8, null); + nat16 = Serde.Candid.formatCandidType(primitive_types.nat16, null); + nat32 = Serde.Candid.formatCandidType(primitive_types.nat32, null); + nat64 = Serde.Candid.formatCandidType(primitive_types.nat64, null); + int = Serde.Candid.formatCandidType(primitive_types.int, null); + int8 = Serde.Candid.formatCandidType(primitive_types.int8, null); + int16 = Serde.Candid.formatCandidType(primitive_types.int16, null); + int32 = Serde.Candid.formatCandidType(primitive_types.int32, null); + int64 = Serde.Candid.formatCandidType(primitive_types.int64, null); + float = Serde.Candid.formatCandidType(primitive_types.float, null); + bool = Serde.Candid.formatCandidType(primitive_types.bool, null); + text = Serde.Candid.formatCandidType(primitive_types.text, null); + null_ = Serde.Candid.formatCandidType(primitive_types.null_, null); + empty = Serde.Candid.formatCandidType(primitive_types.empty, null); + principal = Serde.Candid.formatCandidType(primitive_types.principal, null); + blob = Serde.Candid.formatCandidType(primitive_types.blob, null); + }; + + let formatted_compound_types = { + option_nat = Serde.Candid.formatCandidType(compound_types.option_nat, null); + option_text = Serde.Candid.formatCandidType(compound_types.option_text, null); + array_nat8 = Serde.Candid.formatCandidType(compound_types.array_nat8, null); + array_text = Serde.Candid.formatCandidType(compound_types.array_text, null); + array_record = Serde.Candid.formatCandidType(compound_types.array_record, null); + simple_record = Serde.Candid.formatCandidType(compound_types.simple_record, null); + nested_record = Serde.Candid.formatCandidType(compound_types.nested_record, null); + tuple_mixed = Serde.Candid.formatCandidType(compound_types.tuple_mixed, null); + variant_simple = Serde.Candid.formatCandidType(compound_types.variant_simple, null); + variant_complex = Serde.Candid.formatCandidType(compound_types.variant_complex, null); + map_text_nat = Serde.Candid.formatCandidType(compound_types.map_text_nat, null); + large_text = Serde.Candid.formatCandidType(compound_types.large_text, null); + large_array = Serde.Candid.formatCandidType(compound_types.large_array, null); + deep_nesting = Serde.Candid.formatCandidType(compound_types.deep_nesting, null); + wide_record = Serde.Candid.formatCandidType(compound_types.wide_record, null); + }; + + // Storage for encoded blobs + let encoded_blobs = { + var nat = Buffer.Buffer(limit); + var nat8 = Buffer.Buffer(limit); + var nat16 = Buffer.Buffer(limit); + var nat32 = Buffer.Buffer(limit); + var nat64 = Buffer.Buffer(limit); + var int = Buffer.Buffer(limit); + var int8 = Buffer.Buffer(limit); + var int16 = Buffer.Buffer(limit); + var int32 = Buffer.Buffer(limit); + var int64 = Buffer.Buffer(limit); + var float = Buffer.Buffer(limit); + var bool = Buffer.Buffer(limit); + var text = Buffer.Buffer(limit); + var principal = Buffer.Buffer(limit); + var blob = Buffer.Buffer(limit); + var option_nat = Buffer.Buffer(limit); + var option_text = Buffer.Buffer(limit); + var array_nat8 = Buffer.Buffer(limit); + var array_text = Buffer.Buffer(limit); + var simple_record = Buffer.Buffer(limit); + var nested_record = Buffer.Buffer(limit); + var mixed_tuple = Buffer.Buffer(limit); + var simple_variant = Buffer.Buffer(limit); + var complex_variant = Buffer.Buffer(limit); + var map_values = Buffer.Buffer(limit); + var large_text = Buffer.Buffer(limit); + var large_array = Buffer.Buffer(limit); + var deep_nesting = Buffer.Buffer(limit); + var wide_record = Buffer.Buffer(limit); + }; + + bench.runner( + func(row, col) = switch (col, row) { + // Primitive Types - Encoding + case ("encode()", "Nat") { + for (i in Itertools.range(0, limit)) { + let #ok(blob) = CandidEncoder.one_shot([#Nat(nat_values.get(i))], null); + encoded_blobs.nat.add(blob); + }; + }; + case ("encode()", "Nat8") { + for (i in Itertools.range(0, limit)) { + let #ok(blob) = CandidEncoder.one_shot([#Nat8(nat8_values.get(i))], null); + encoded_blobs.nat8.add(blob); + }; + }; + case ("encode()", "Nat16") { + for (i in Itertools.range(0, limit)) { + let #ok(blob) = CandidEncoder.one_shot([#Nat16(nat16_values.get(i))], null); + encoded_blobs.nat16.add(blob); + }; + }; + case ("encode()", "Nat32") { + for (i in Itertools.range(0, limit)) { + let #ok(blob) = CandidEncoder.one_shot([#Nat32(nat32_values.get(i))], null); + encoded_blobs.nat32.add(blob); + }; + }; + case ("encode()", "Nat64") { + for (i in Itertools.range(0, limit)) { + let #ok(blob) = CandidEncoder.one_shot([#Nat64(nat64_values.get(i))], null); + encoded_blobs.nat64.add(blob); + }; + }; + case ("encode()", "Int") { + for (i in Itertools.range(0, limit)) { + let #ok(blob) = CandidEncoder.one_shot([#Int(int_values.get(i))], null); + encoded_blobs.int.add(blob); + }; + }; + case ("encode()", "Int8") { + for (i in Itertools.range(0, limit)) { + let #ok(blob) = CandidEncoder.one_shot([#Int8(int8_values.get(i))], null); + encoded_blobs.int8.add(blob); + }; + }; + case ("encode()", "Int16") { + for (i in Itertools.range(0, limit)) { + let #ok(blob) = CandidEncoder.one_shot([#Int16(int16_values.get(i))], null); + encoded_blobs.int16.add(blob); + }; + }; + case ("encode()", "Int32") { + for (i in Itertools.range(0, limit)) { + let #ok(blob) = CandidEncoder.one_shot([#Int32(int32_values.get(i))], null); + encoded_blobs.int32.add(blob); + }; + }; + case ("encode()", "Int64") { + for (i in Itertools.range(0, limit)) { + let #ok(blob) = CandidEncoder.one_shot([#Int64(int64_values.get(i))], null); + encoded_blobs.int64.add(blob); + }; + }; + case ("encode()", "Float") { + for (i in Itertools.range(0, limit)) { + let #ok(blob) = CandidEncoder.one_shot([#Float(float_values.get(i))], null); + encoded_blobs.float.add(blob); + }; + }; + case ("encode()", "Bool") { + for (i in Itertools.range(0, limit)) { + let #ok(blob) = CandidEncoder.one_shot([#Bool(bool_values.get(i))], null); + encoded_blobs.bool.add(blob); + }; + }; + case ("encode()", "Text") { + for (i in Itertools.range(0, limit)) { + let #ok(blob) = CandidEncoder.one_shot([#Text(text_values.get(i))], null); + encoded_blobs.text.add(blob); + }; + }; + case ("encode()", "Null") { + for (i in Itertools.range(0, limit)) { + let #ok(blob) = CandidEncoder.one_shot([#Null], null); + }; + }; + case ("encode()", "Empty") { + for (i in Itertools.range(0, limit)) { + let #ok(blob) = CandidEncoder.one_shot([#Empty], null); + }; + }; + case ("encode()", "Principal") { + for (i in Itertools.range(0, limit)) { + let #ok(blob) = CandidEncoder.one_shot([#Principal(principal_values.get(i))], null); + encoded_blobs.principal.add(blob); + }; + }; + case ("encode()", "Blob") { + for (i in Itertools.range(0, limit)) { + let #ok(blob) = CandidEncoder.one_shot([#Blob(blob_values.get(i))], null); + encoded_blobs.blob.add(blob); + }; + }; + + // Compound Types - Encoding + case ("encode()", "Option(Nat)") { + for (i in Itertools.range(0, limit)) { + let #ok(blob) = CandidEncoder.one_shot( + [ + #Option( + switch (option_nat_values.get(i)) { + case (?n) #Nat(n); + case (null) #Null; + } + ) + ], + null, + ); + encoded_blobs.option_nat.add(blob); + }; + }; + case ("encode()", "Option(Text)") { + for (i in Itertools.range(0, limit)) { + let #ok(blob) = CandidEncoder.one_shot( + [ + #Option( + switch (option_text_values.get(i)) { + case (?t) #Text(t); + case (null) #Null; + } + ) + ], + null, + ); + encoded_blobs.option_text.add(blob); + }; + }; + case ("encode()", "Array(Nat8)") { + for (i in Itertools.range(0, limit)) { + let arr = Array.map(array_nat8_values.get(i), func(n) = #Nat8(n)); + let #ok(blob) = CandidEncoder.one_shot([#Array(arr)], null); + encoded_blobs.array_nat8.add(blob); + }; + }; + case ("encode()", "Array(Text)") { + for (i in Itertools.range(0, limit)) { + let arr = Array.map(array_text_values.get(i), func(t) = #Text(t)); + let #ok(blob) = CandidEncoder.one_shot([#Array(arr)], null); + encoded_blobs.array_text.add(blob); + }; + }; + case ("encode()", "Array(Record)") { + for (i in Itertools.range(0, limit)) { + let record_arr = Array.map( + [simple_record_values.get(i)], + func(r) = #Record([("id", #Nat(r.id)), ("name", #Text(r.name)), ("active", #Bool(r.active))]), + ); + let #ok(blob) = CandidEncoder.one_shot([#Array(record_arr)], null); + encoded_blobs.simple_record.add(blob); + }; + }; + case ("encode()", "Record(Simple)") { + for (i in Itertools.range(0, limit)) { + let record = simple_record_values.get(i); + let #ok(blob) = CandidEncoder.one_shot( + [ + #Record([("id", #Nat(record.id)), ("name", #Text(record.name)), ("active", #Bool(record.active))]) + ], + null, + ); + encoded_blobs.simple_record.add(blob); + }; + }; + case ("encode()", "Record(Nested)") { + for (i in Itertools.range(0, limit)) { + let record = nested_record_values.get(i); + let settings_candid = switch (record.settings) { + case (?s) #Option(#Record([("theme", #Text(s.theme)), ("notifications", #Bool(s.notifications))])); + case (null) #Option(#Null); + }; + let #ok(blob) = CandidEncoder.one_shot( + [ + #Record([ + ("user", #Record([("id", #Nat(record.user.id)), ("name", #Text(record.user.name)), ("active", #Bool(record.user.active))])), + ("metadata", #Record([("created", #Int(record.metadata.created)), ("tags", #Array(Array.map(record.metadata.tags, func(t) = #Text(t))))])), + ("settings", settings_candid), + ]) + ], + null, + ); + encoded_blobs.nested_record.add(blob); + }; + }; + case ("encode()", "Tuple(Mixed)") { + for (i in Itertools.range(0, limit)) { + let (n, t, b, f) = mixed_tuple_values.get(i); + let float_opt = switch (f) { + case (?fl) #Option(#Float(fl)); + case (null) #Option(#Null); + }; + let #ok(blob) = CandidEncoder.one_shot( + [ + #Tuple([#Nat(n), #Text(t), #Bool(b), float_opt]) + ], + null, + ); + encoded_blobs.mixed_tuple.add(blob); + }; + }; + case ("encode()", "Variant(Simple)") { + for (i in Itertools.range(0, limit)) { + let variant_candid = switch (simple_variant_values.get(i)) { + case (#success(n)) #Variant(("success", #Nat(n))); + case (#error(msg)) #Variant(("error", #Text(msg))); + case (#pending) #Variant(("pending", #Null)); + }; + let #ok(blob) = CandidEncoder.one_shot([variant_candid], null); + encoded_blobs.simple_variant.add(blob); + }; + }; + case ("encode()", "Variant(Complex)") { + for (i in Itertools.range(0, limit)) { + let variant_candid = switch (complex_variant_values.get(i)) { + case (#user(u)) #Variant(("user", #Record([("id", #Nat(u.id)), ("name", #Text(u.name)), ("active", #Bool(u.active))]))); + case (#admin(a)) { + let settings_candid = switch (a.settings) { + case (?s) #Option(#Record([("theme", #Text(s.theme)), ("notifications", #Bool(s.notifications))])); + case (null) #Option(#Null); + }; + #Variant(("admin", #Record([("user", #Record([("id", #Nat(a.user.id)), ("name", #Text(a.user.name)), ("active", #Bool(a.user.active))])), ("metadata", #Record([("created", #Int(a.metadata.created)), ("tags", #Array(Array.map(a.metadata.tags, func(t) = #Text(t))))])), ("settings", settings_candid)]))); + }; + case (#guest) #Variant(("guest", #Null)); + }; + let #ok(blob) = CandidEncoder.one_shot([variant_candid], null); + encoded_blobs.complex_variant.add(blob); + }; + }; + case ("encode()", "Map(Text->Nat)") { + for (i in Itertools.range(0, limit)) { + let map_entries = Array.map<(Text, Nat), (Text, Candid)>( + map_values.get(i), + func((k, v)) = (k, #Nat(v)), + ); + let #ok(blob) = CandidEncoder.one_shot([#Map(map_entries)], null); + encoded_blobs.map_values.add(blob); + }; + }; + case ("encode()", "Large Text") { + for (i in Itertools.range(0, limit)) { + let #ok(blob) = CandidEncoder.one_shot([#Text(large_text_values.get(i))], null); + encoded_blobs.large_text.add(blob); + }; + }; + case ("encode()", "Large Array") { + for (i in Itertools.range(0, limit)) { + let arr = Array.map(large_array_values.get(i), func(n) = #Nat(n)); + let #ok(blob) = CandidEncoder.one_shot([#Array(arr)], null); + encoded_blobs.large_array.add(blob); + }; + }; + case ("encode()", "Deep Nesting") { + for (i in Itertools.range(0, limit)) { + let #ok(blob) = CandidEncoder.one_shot([deep_nesting_values.get(i)], null); + encoded_blobs.deep_nesting.add(blob); + }; + }; + case ("encode()", "Wide Record") { + for (i in Itertools.range(0, limit)) { + let #ok(blob) = CandidEncoder.one_shot([wide_record_values.get(i)], null); + encoded_blobs.wide_record.add(blob); + }; + }; + case ("encode()", "Recursive Structure") { + for (i in Itertools.range(0, limit)) { + let #ok(blob) = CandidEncoder.one_shot([deep_nesting_values.get(i)], null); + }; + }; + + // Primitive Types - Decoding + case ("decode()", "Nat") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.nat.get(i), [], null); + }; + }; + case ("decode()", "Nat8") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.nat8.get(i), [], null); + }; + }; + case ("decode()", "Nat16") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.nat16.get(i), [], null); + }; + }; + case ("decode()", "Nat32") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.nat32.get(i), [], null); + }; + }; + case ("decode()", "Nat64") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.nat64.get(i), [], null); + }; + }; + case ("decode()", "Int") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.int.get(i), [], null); + }; + }; + case ("decode()", "Int8") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.int8.get(i), [], null); + }; + }; + case ("decode()", "Int16") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.int16.get(i), [], null); + }; + }; + case ("decode()", "Int32") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.int32.get(i), [], null); + }; + }; + case ("decode()", "Int64") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.int64.get(i), [], null); + }; + }; + case ("decode()", "Float") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.float.get(i), [], null); + }; + }; + case ("decode()", "Bool") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.bool.get(i), [], null); + }; + }; + case ("decode()", "Text") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.text.get(i), [], null); + }; + }; + case ("decode()", "Null") { + for (i in Itertools.range(0, limit)) { + let #ok(blob) = CandidEncoder.one_shot([#Null], null); + let #ok(candid) = CandidDecoder.one_shot(blob, [], null); + }; + }; + case ("decode()", "Empty") { + for (i in Itertools.range(0, limit)) { + let #ok(blob) = CandidEncoder.one_shot([#Empty], null); + let #ok(candid) = CandidDecoder.one_shot(blob, [], null); + }; + }; + case ("decode()", "Principal") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.principal.get(i), [], null); + }; + }; + case ("decode()", "Blob") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.blob.get(i), [], null); + }; + }; + + // Compound Types - Decoding + case ("decode()", "Option(Nat)") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.option_nat.get(i), [], null); + }; + }; + case ("decode()", "Option(Text)") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.option_text.get(i), [], null); + }; + }; + case ("decode()", "Array(Nat8)") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.array_nat8.get(i), [], null); + }; + }; + case ("decode()", "Array(Text)") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.array_text.get(i), [], null); + }; + }; + case ("decode()", "Array(Record)") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.simple_record.get(i), ["id", "name", "active"], null); + }; + }; + case ("decode()", "Record(Simple)") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.simple_record.get(i), ["id", "name", "active"], null); + }; + }; + case ("decode()", "Record(Nested)") { + for (i in Itertools.range(0, limit)) { + let record_keys = ["user", "metadata", "settings"]; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.nested_record.get(i), record_keys, null); + }; + }; + case ("decode()", "Tuple(Mixed)") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.mixed_tuple.get(i), [], null); + }; + }; + case ("decode()", "Variant(Simple)") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.simple_variant.get(i), [], null); + }; + }; + case ("decode()", "Variant(Complex)") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.complex_variant.get(i), [], null); + }; + }; + case ("decode()", "Map(Text->Nat)") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.map_values.get(i), [], null); + }; + }; + case ("decode()", "Large Text") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.large_text.get(i), [], null); + }; + }; + case ("decode()", "Large Array") { + for (i in Itertools.range(0, limit)) { + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.large_array.get(i), [], null); + }; + }; + case ("decode()", "Deep Nesting") { + for (i in Itertools.range(0, limit)) { + let record_keys = ["level1", "level2", "level3", "level4", "level5"]; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.deep_nesting.get(i), record_keys, null); + }; + }; + case ("decode()", "Wide Record") { + for (i in Itertools.range(0, limit)) { + let record_keys = Array.tabulate(20, func(j) = "field" # Nat.toText(j)); + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.wide_record.get(i), record_keys, null); + }; + }; + case ("decode()", "Recursive Structure") { + for (i in Itertools.range(0, limit)) { + let record_keys = ["level1", "level2", "level3", "level4", "level5"]; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.deep_nesting.get(i), record_keys, null); + }; + }; + + // Sans-inference encoding (with formatted types for optimal performance) + case ("encode(sans inference)", "Nat") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.nat + }; + let #ok(blob) = CandidEncoder.one_shot([#Nat(nat_values.get(i))], ?options); + }; + }; + case ("encode(sans inference)", "Nat8") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.nat8 + }; + let #ok(blob) = CandidEncoder.one_shot([#Nat8(nat8_values.get(i))], ?options); + }; + }; + case ("encode(sans inference)", "Nat16") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.nat16 + }; + let #ok(blob) = CandidEncoder.one_shot([#Nat16(nat16_values.get(i))], ?options); + }; + }; + case ("encode(sans inference)", "Nat32") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.nat32 + }; + let #ok(blob) = CandidEncoder.one_shot([#Nat32(nat32_values.get(i))], ?options); + }; + }; + case ("encode(sans inference)", "Nat64") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.nat64 + }; + let #ok(blob) = CandidEncoder.one_shot([#Nat64(nat64_values.get(i))], ?options); + }; + }; + case ("encode(sans inference)", "Int") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.int + }; + let #ok(blob) = CandidEncoder.one_shot([#Int(int_values.get(i))], ?options); + }; + }; + case ("encode(sans inference)", "Int8") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.int8 + }; + let #ok(blob) = CandidEncoder.one_shot([#Int8(int8_values.get(i))], ?options); + }; + }; + case ("encode(sans inference)", "Int16") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.int16 + }; + let #ok(blob) = CandidEncoder.one_shot([#Int16(int16_values.get(i))], ?options); + }; + }; + case ("encode(sans inference)", "Int32") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.int32 + }; + let #ok(blob) = CandidEncoder.one_shot([#Int32(int32_values.get(i))], ?options); + }; + }; + case ("encode(sans inference)", "Int64") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.int64 + }; + let #ok(blob) = CandidEncoder.one_shot([#Int64(int64_values.get(i))], ?options); + }; + }; + case ("encode(sans inference)", "Float") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.float + }; + let #ok(blob) = CandidEncoder.one_shot([#Float(float_values.get(i))], ?options); + }; + }; + case ("encode(sans inference)", "Bool") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.bool + }; + let #ok(blob) = CandidEncoder.one_shot([#Bool(bool_values.get(i))], ?options); + }; + }; + case ("encode(sans inference)", "Text") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.text + }; + let #ok(blob) = CandidEncoder.one_shot([#Text(text_values.get(i))], ?options); + }; + }; + case ("encode(sans inference)", "Null") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.null_ + }; + let #ok(blob) = CandidEncoder.one_shot([#Null], ?options); + }; + }; + case ("encode(sans inference)", "Empty") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.empty + }; + let #ok(blob) = CandidEncoder.one_shot([#Empty], ?options); + }; + }; + case ("encode(sans inference)", "Principal") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.principal + }; + let #ok(blob) = CandidEncoder.one_shot([#Principal(principal_values.get(i))], ?options); + }; + }; + case ("encode(sans inference)", "Blob") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.blob + }; + let #ok(blob) = CandidEncoder.one_shot([#Blob(blob_values.get(i))], ?options); + }; + }; + case ("encode(sans inference)", "Option(Nat)") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.option_nat + }; + let #ok(blob) = CandidEncoder.one_shot( + [ + #Option( + switch (option_nat_values.get(i)) { + case (?n) #Nat(n); + case (null) #Null; + } + ) + ], + ?options, + ); + }; + }; + case ("encode(sans inference)", "Option(Text)") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.option_text + }; + let #ok(blob) = CandidEncoder.one_shot( + [ + #Option( + switch (option_text_values.get(i)) { + case (?t) #Text(t); + case (null) #Null; + } + ) + ], + ?options, + ); + }; + }; + case ("encode(sans inference)", "Array(Nat8)") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.array_nat8 + }; + let arr = Array.map(array_nat8_values.get(i), func(n) = #Nat8(n)); + let #ok(blob) = CandidEncoder.one_shot([#Array(arr)], ?options); + }; + }; + case ("encode(sans inference)", "Array(Text)") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.array_text + }; + let arr = Array.map(array_text_values.get(i), func(t) = #Text(t)); + let #ok(blob) = CandidEncoder.one_shot([#Array(arr)], ?options); + }; + }; + case ("encode(sans inference)", "Array(Record)") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.array_record + }; + let record_arr = Array.map( + [simple_record_values.get(i)], + func(r) = #Record([("id", #Nat(r.id)), ("name", #Text(r.name)), ("active", #Bool(r.active))]), + ); + let #ok(blob) = CandidEncoder.one_shot([#Array(record_arr)], ?options); + }; + }; + case ("encode(sans inference)", "Record(Simple)") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.simple_record + }; + let record = simple_record_values.get(i); + let #ok(blob) = CandidEncoder.one_shot( + [ + #Record([("id", #Nat(record.id)), ("name", #Text(record.name)), ("active", #Bool(record.active))]) + ], + ?options, + ); + }; + }; + case ("encode(sans inference)", "Record(Nested)") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.nested_record + }; + let record = nested_record_values.get(i); + let settings_candid = switch (record.settings) { + case (?s) #Option(#Record([("theme", #Text(s.theme)), ("notifications", #Bool(s.notifications))])); + case (null) #Option(#Null); + }; + let #ok(blob) = CandidEncoder.one_shot( + [ + #Record([ + ("user", #Record([("id", #Nat(record.user.id)), ("name", #Text(record.user.name)), ("active", #Bool(record.user.active))])), + ("metadata", #Record([("created", #Int(record.metadata.created)), ("tags", #Array(Array.map(record.metadata.tags, func(t) = #Text(t))))])), + ("settings", settings_candid), + ]) + ], + ?options, + ); + }; + }; + case ("encode(sans inference)", "Tuple(Mixed)") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.tuple_mixed + }; + let (n, t, b, f) = mixed_tuple_values.get(i); + let float_opt = switch (f) { + case (?fl) #Option(#Float(fl)); + case (null) #Option(#Null); + }; + let #ok(blob) = CandidEncoder.one_shot( + [ + #Tuple([#Nat(n), #Text(t), #Bool(b), float_opt]) + ], + ?options, + ); + }; + }; + case ("encode(sans inference)", "Variant(Simple)") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.variant_simple + }; + let variant_candid = switch (simple_variant_values.get(i)) { + case (#success(n)) #Variant(("success", #Nat(n))); + case (#error(msg)) #Variant(("error", #Text(msg))); + case (#pending) #Variant(("pending", #Null)); + }; + let #ok(blob) = CandidEncoder.one_shot([variant_candid], ?options); + }; + }; + case ("encode(sans inference)", "Variant(Complex)") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.variant_complex + }; + let variant_candid = switch (complex_variant_values.get(i)) { + case (#user(u)) #Variant(("user", #Record([("id", #Nat(u.id)), ("name", #Text(u.name)), ("active", #Bool(u.active))]))); + case (#admin(a)) { + let settings_candid = switch (a.settings) { + case (?s) #Option(#Record([("theme", #Text(s.theme)), ("notifications", #Bool(s.notifications))])); + case (null) #Option(#Null); + }; + #Variant(("admin", #Record([("user", #Record([("id", #Nat(a.user.id)), ("name", #Text(a.user.name)), ("active", #Bool(a.user.active))])), ("metadata", #Record([("created", #Int(a.metadata.created)), ("tags", #Array(Array.map(a.metadata.tags, func(t) = #Text(t))))])), ("settings", settings_candid)]))); + }; + case (#guest) #Variant(("guest", #Null)); + }; + let #ok(blob) = CandidEncoder.one_shot([variant_candid], ?options); + }; + }; + case ("encode(sans inference)", "Map(Text->Nat)") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.map_text_nat + }; + let map_entries = Array.map<(Text, Nat), (Text, Candid)>( + map_values.get(i), + func((k, v)) = (k, #Nat(v)), + ); + let #ok(blob) = CandidEncoder.one_shot([#Map(map_entries)], ?options); + }; + }; + case ("encode(sans inference)", "Large Text") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.large_text + }; + let #ok(blob) = CandidEncoder.one_shot([#Text(large_text_values.get(i))], ?options); + }; + }; + case ("encode(sans inference)", "Large Array") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.large_array + }; + let arr = Array.map(large_array_values.get(i), func(n) = #Nat(n)); + let #ok(blob) = CandidEncoder.one_shot([#Array(arr)], ?options); + }; + }; + case ("encode(sans inference)", "Deep Nesting") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.deep_nesting + }; + let #ok(blob) = CandidEncoder.one_shot([deep_nesting_values.get(i)], ?options); + }; + }; + case ("encode(sans inference)", "Wide Record") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.wide_record + }; + let #ok(blob) = CandidEncoder.one_shot([wide_record_values.get(i)], ?options); + }; + }; + case ("encode(sans inference)", "Recursive Structure") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.deep_nesting + }; + let #ok(blob) = CandidEncoder.one_shot([deep_nesting_values.get(i)], ?options); + }; + }; + + // Sans-inference decoding (with predefined types) + case ("decode(sans inference)", "Nat") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.nat + }; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.nat.get(i), [], ?options); + }; + }; + case ("decode(sans inference)", "Nat8") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.nat8 + }; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.nat8.get(i), [], ?options); + }; + }; + case ("decode(sans inference)", "Nat16") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.nat16 + }; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.nat16.get(i), [], ?options); + }; + }; + case ("decode(sans inference)", "Nat32") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.nat32 + }; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.nat32.get(i), [], ?options); + }; + }; + case ("decode(sans inference)", "Nat64") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.nat64 + }; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.nat64.get(i), [], ?options); + }; + }; + case ("decode(sans inference)", "Int") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.int + }; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.int.get(i), [], ?options); + }; + }; + case ("decode(sans inference)", "Int8") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.int8 + }; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.int8.get(i), [], ?options); + }; + }; + case ("decode(sans inference)", "Int16") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.int16 + }; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.int16.get(i), [], ?options); + }; + }; + case ("decode(sans inference)", "Int32") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.int32 + }; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.int32.get(i), [], ?options); + }; + }; + case ("decode(sans inference)", "Int64") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.int64 + }; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.int64.get(i), [], ?options); + }; + }; + case ("decode(sans inference)", "Float") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.float + }; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.float.get(i), [], ?options); + }; + }; + case ("decode(sans inference)", "Bool") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.bool + }; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.bool.get(i), [], ?options); + }; + }; + case ("decode(sans inference)", "Text") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.text + }; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.text.get(i), [], ?options); + }; + }; + case ("decode(sans inference)", "Null") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.null_ + }; + let #ok(blob) = CandidEncoder.one_shot([#Null], ?options); + let #ok(candid) = CandidDecoder.one_shot(blob, [], ?options); + }; + }; + case ("decode(sans inference)", "Empty") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.empty + }; + let #ok(blob) = CandidEncoder.one_shot([#Empty], ?options); + let #ok(candid) = CandidDecoder.one_shot(blob, [], ?options); + }; + }; + case ("decode(sans inference)", "Principal") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.principal + }; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.principal.get(i), [], ?options); + }; + }; + case ("decode(sans inference)", "Blob") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_primitive_types.blob + }; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.blob.get(i), [], ?options); + }; + }; + case ("decode(sans inference)", "Option(Nat)") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.option_nat + }; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.option_nat.get(i), [], ?options); + }; + }; + case ("decode(sans inference)", "Option(Text)") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.option_text + }; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.option_text.get(i), [], ?options); + }; + }; + case ("decode(sans inference)", "Array(Nat8)") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.array_nat8 + }; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.array_nat8.get(i), [], ?options); + }; + }; + case ("decode(sans inference)", "Array(Text)") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.array_text + }; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.array_text.get(i), [], ?options); + }; + }; + case ("decode(sans inference)", "Array(Record)") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.array_record + }; + let record_keys = ["id", "name", "active"]; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.simple_record.get(i), record_keys, ?options); + }; + }; + case ("decode(sans inference)", "Record(Simple)") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.simple_record + }; + let record_keys = ["id", "name", "active"]; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.simple_record.get(i), record_keys, ?options); + }; + }; + case ("decode(sans inference)", "Record(Nested)") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.nested_record + }; + let record_keys = ["user", "metadata", "settings"]; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.nested_record.get(i), record_keys, ?options); + }; + }; + case ("decode(sans inference)", "Tuple(Mixed)") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.tuple_mixed + }; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.mixed_tuple.get(i), [], ?options); + }; + }; + case ("decode(sans inference)", "Variant(Simple)") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.variant_simple + }; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.simple_variant.get(i), [], ?options); + }; + }; + case ("decode(sans inference)", "Variant(Complex)") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.variant_complex + }; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.complex_variant.get(i), [], ?options); + }; + }; + case ("decode(sans inference)", "Map(Text->Nat)") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.map_text_nat + }; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.map_values.get(i), [], ?options); + }; + }; + case ("decode(sans inference)", "Large Text") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.large_text + }; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.large_text.get(i), [], ?options); + }; + }; + case ("decode(sans inference)", "Large Array") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.large_array + }; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.large_array.get(i), [], ?options); + }; + }; + case ("decode(sans inference)", "Deep Nesting") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.deep_nesting + }; + let record_keys = ["level1", "level2", "level3", "level4", "level5"]; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.deep_nesting.get(i), record_keys, ?options); + }; + }; + case ("decode(sans inference)", "Wide Record") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.wide_record + }; + let record_keys = Array.tabulate(20, func(j) = "field" # Nat.toText(j)); + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.wide_record.get(i), record_keys, ?options); + }; + }; + case ("decode(sans inference)", "Recursive Structure") { + for (i in Itertools.range(0, limit)) { + let options = { + Serde.Candid.defaultOptions with types = ?formatted_compound_types.deep_nesting + }; + let record_keys = ["level1", "level2", "level3", "level4", "level5"]; + let #ok(candid) = CandidDecoder.one_shot(encoded_blobs.deep_nesting.get(i), record_keys, ?options); + }; + }; + + case (_, _) { + Debug.trap("Unhandled benchmark case: row = \"" # row # "\", col = \"" # col # "\""); + }; + } + ); + + bench; + }; +}; diff --git a/mops.toml b/mops.toml index 23d8c5c..9607108 100644 --- a/mops.toml +++ b/mops.toml @@ -3,26 +3,27 @@ name = "serde" version = "3.2.2" description = "A serialisation and deserialisation library for Motoko." repository = "https://github.com/NatLabs/serde" -keywords = ["json", "candid", "cbor", "urlencoded", "serialization"] +keywords = [ "json", "candid", "cbor", "urlencoded", "serialization" ] license = "MIT" [dependencies] -base = "0.12.0" -itertools = "0.2.1" +base = "0.14.11" +itertools = "0.2.2" candid = "1.1.1" -xtended-numbers = "0.3.1" +xtended-numbers = "0.4.0" json-float = "https://github.com/NatLabs/json.mo#float@f3c8e7d418a7a8f2d6c0d7e2d276a0a82c2046ff" parser-combinators = "https://github.com/aviate-labs/parser-combinators.mo#v0.1.2@6a331bf78e9dcd7623977f06c8e561fd1a8c0103" cbor = "1.0.0" map = "9.0.1" -sha2 = "0.1.0" +sha2 = "0.1.4" +fuzz = "0.2.1" [dev-dependencies] -test = "2.0.0" +test = "2.1.1" bench = "1.0.0" -fuzz = "0.2.1" +fuzz = "1.0.0" rep-indy-hash = "0.1.1" [toolchain] wasmtime = "14.0.4" -moc = "0.11.1" +moc = "0.14.9" diff --git a/src/Candid/Blob/Decoder.mo b/src/Candid/Blob/Decoder.mo index e781d5a..9e6af0a 100644 --- a/src/Candid/Blob/Decoder.mo +++ b/src/Candid/Blob/Decoder.mo @@ -84,7 +84,7 @@ module { }; public func split(blob : Blob, options : ?T.Options) : Result { - let bytes = Blob.toArray(blob); + let bytes = blob; let state : [var Nat] = [var 0]; @@ -106,16 +106,16 @@ module { let sequences : CandidBlobSequences = { magic = Blob.fromArray(magic); - compound_types = Blob.fromArray(Utils.array_slice(bytes, compound_types_start_index, types_start_index)); - types = Blob.fromArray(Utils.array_slice(bytes, types_start_index, values_start_index)); - values = Blob.fromArray(Utils.array_slice(bytes, values_start_index, bytes.size())); + compound_types = Blob.fromArray(Utils.blob_slice(bytes, compound_types_start_index, types_start_index)); + types = Blob.fromArray(Utils.blob_slice(bytes, types_start_index, values_start_index)); + values = Blob.fromArray(Utils.blob_slice(bytes, values_start_index, bytes.size())); }; #ok(sequences); }; public func extract_values_sequence(blob : Blob) : Result { - let bytes = Blob.toArray(blob); + let bytes = blob; let state : [var Nat] = [var 0]; @@ -131,7 +131,7 @@ module { skip_types(bytes, state); let values_start_index = state[C.BYTES_INDEX]; - let values_sequence = Blob.fromArray(Utils.array_slice(bytes, values_start_index, bytes.size())); + let values_sequence = Blob.fromArray(Utils.blob_slice(bytes, values_start_index, bytes.size())); #ok(values_sequence) @@ -168,14 +168,14 @@ module { one_shot_decode(blob, record_key_map, Option.get(options, T.defaultOptions)); }; - func read(bytes : [Nat8], state : [var Nat]) : Nat8 { - let byte = bytes[state[C.BYTES_INDEX]]; + func read(bytes : Blob, state : [var Nat]) : Nat8 { + let byte = bytes.get(state[C.BYTES_INDEX]); state[C.BYTES_INDEX] += 1; byte; }; - func peek(bytes : [Nat8], state : [var Nat]) : Nat8 { - bytes[state[C.BYTES_INDEX]]; + func peek(bytes : Blob, state : [var Nat]) : Nat8 { + bytes.get(state[C.BYTES_INDEX]); }; func code_to_primitive_type(code : Nat8) : CandidType { @@ -220,7 +220,7 @@ module { (code >= 0x70 and code <= 0x7f) or (code == 0x6f) or (code == 0x68); }; - func extract_compound_types(bytes : [Nat8], state : [var Nat], total_compound_types : Nat, record_key_map : Map) : [ShallowCandidTypes] { + func extract_compound_types(bytes : Blob, state : [var Nat], total_compound_types : Nat, record_key_map : Map) : [ShallowCandidTypes] { func extract_compound_type(i : Nat) : ShallowCandidTypes { let compound_type_code = read(bytes, state); @@ -355,7 +355,7 @@ module { _build_compound_type(compound_types, start_pos, visited, is_recursive_set, recursive_types); }; - func build_types(bytes : [Nat8], state : [var Nat], compound_types : [ShallowCandidTypes], recursive_types : Map) : [CandidType] { + func build_types(bytes : Blob, state : [var Nat], compound_types : [ShallowCandidTypes], recursive_types : Map) : [CandidType] { let total_candid_types = decode_leb128(bytes, state); let candid_types = Array.tabulate( @@ -377,7 +377,7 @@ module { candid_types; }; - func skip_compound_types(bytes : [Nat8], state : [var Nat], total_compound_types : Nat) { + func skip_compound_types(bytes : Blob, state : [var Nat], total_compound_types : Nat) { var i = 0; while (i < total_compound_types) { let compound_type_code = read(bytes, state); @@ -420,7 +420,7 @@ module { }; }; - func skip_types(bytes : [Nat8], state : [var Nat]) { + func skip_types(bytes : Blob, state : [var Nat]) { let total_candid_types = decode_leb128(bytes, state); @@ -439,7 +439,7 @@ module { }; public func one_shot_decode(candid_blob : Blob, record_key_map : Map, options : T.Options) : Result<[Candid], Text> { - let bytes = Blob.toArray(candid_blob); + let bytes = candid_blob; // let stream = BitBuffer.fromArray(bytes); let is_types_set = Option.isSome(options.types); @@ -484,7 +484,7 @@ module { }; // https://en.wikipedia.org/wiki/LEB128 - // func decode_leb128(bytes : [Nat8], state : [var Nat]) : Nat { + // func decode_leb128(bytes : Blob, state : [var Nat]) : Nat { // var n64 : Nat64 = 0; // var shift : Nat64 = 0; @@ -514,7 +514,7 @@ module { // }; let nat64_bound = 18_446_744_073_709_551_616; - // func decode_leb128(bytes : [Nat8], state : [var Nat]) : Nat { + // func decode_leb128(bytes : Blob, state : [var Nat]) : Nat { // var n64 : Nat64 = 0; // var shift : Nat64 = 0; // var shifted : Nat = 0; @@ -558,7 +558,7 @@ module { // }; // https://en.wikipedia.org/wiki/LEB128 - func decode_leb128(bytes : [Nat8], state : [var Nat]) : Nat { + func decode_leb128(bytes : Blob, state : [var Nat]) : Nat { var n64 : Nat64 = 0; var shift : Nat64 = 0; @@ -574,7 +574,7 @@ module { Nat64.toNat(n64); }; - func decode_signed_leb_64(bytes : [Nat8], state : [var Nat]) : Int { + func decode_signed_leb_64(bytes : Blob, state : [var Nat]) : Int { var n64 : Nat64 = 0; var shift : Nat64 = 0; @@ -600,7 +600,7 @@ module { return -(Nat64.toNat(mask & two_complement)); }; - func decode_candid_values(bytes : [Nat8], candid_types : [CandidType], state : [var Nat], options : T.Options, recursive_map : Map) : Result<[Candid], Text> { + func decode_candid_values(bytes : Blob, candid_types : [CandidType], state : [var Nat], options : T.Options, recursive_map : Map) : Result<[Candid], Text> { var error : ?Text = null; let candid_values = Array.tabulate( @@ -624,7 +624,7 @@ module { #ok(candid_values); }; - func decode_value(bytes : [Nat8], state : [var Nat], options : T.Options, recursive_map : Map, candid_type : CandidType) : Result { + func decode_value(bytes : Blob, state : [var Nat], options : T.Options, recursive_map : Map, candid_type : CandidType) : Result { // Debug.print("Decoding candid type: " # debug_show (candid_type) # " at index: " # debug_show (state[C.BYTES_INDEX])); let value : Candid = switch (candid_type) { @@ -880,7 +880,7 @@ module { return decode_value(bytes, state, options, recursive_map, recursive_type); }; - case (val) Debug.trap(debug_show (val) # "decoding is not supported"); + case (val) Debug.trap(debug_show (val) # " decoding is not supported"); }; #ok(value); diff --git a/src/Candid/Blob/Encoder.mo b/src/Candid/Blob/Encoder.mo index 5731336..dd50d1e 100644 --- a/src/Candid/Blob/Encoder.mo +++ b/src/Candid/Blob/Encoder.mo @@ -110,7 +110,7 @@ module { let compound_type_buffer = Buffer.Buffer(200); let primitive_type_buffer = Buffer.Buffer(200); - let value_buffer = Buffer.Buffer(200); + let value_buffer = Buffer.Buffer(400); let counter = [var 0]; @@ -188,20 +188,20 @@ module { func check_is_tuple(candid_types : [(Text, Any)]) : Bool { let n = candid_types.size(); // 0-based index - var sum_of_n : Int = (n * (n + 1)) / 2; + var sum_of_n : Int = 0; var i = 0; label tuple_check while (i < candid_types.size()) { let record_key = candid_types[i].0; if (Utils.text_is_number(record_key)) { - sum_of_n -= (Utils.text_to_nat(record_key) + 1); + sum_of_n += (Utils.text_to_nat(record_key) + 1); } else break tuple_check; i += 1; }; - sum_of_n == 0; + sum_of_n == (n * (n + 1)) / 2; }; func tuple_type_to_record(tuple_types : [CandidType]) : [(Text, CandidType)] { @@ -934,7 +934,7 @@ module { }; case (#Record(record_types) or #Map(record_types), #Record(record_entries) or #Map(record_entries)) { - assert record_entries.size() == record_types.size(); + assert record_types.size() >= record_entries.size(); let is_tuple = check_is_tuple(record_types); diff --git a/src/Candid/Blob/RepIndyHash.mo b/src/Candid/Blob/RepIndyHash.mo index 7bd4b5b..2098bca 100644 --- a/src/Candid/Blob/RepIndyHash.mo +++ b/src/Candid/Blob/RepIndyHash.mo @@ -183,7 +183,7 @@ module { buffer.clear(); let resulting_hash = sha256.sum(); - sha256.reset(); + sha256.reset(); // !important to reset the sha256 instance for future use resulting_hash; diff --git a/src/Utils.mo b/src/Utils.mo index 2f5ed91..0855810 100644 --- a/src/Utils.mo +++ b/src/Utils.mo @@ -31,10 +31,22 @@ module { }; }; - public func array_slice(arr : [A], start : Nat, end : Nat) : [A] { + public type ArrayLike = { + size : () -> Nat; + get : (Nat) -> A; + }; + + public func array_slice(arr : ArrayLike, start : Nat, end : Nat) : [A] { Array.tabulate( end - start, - func(i : Nat) = arr[start + i], + func(i : Nat) = arr.get(start + i), + ); + }; + + public func blob_slice(blob : Blob, start : Nat, end : Nat) : [Nat8] { + Array.tabulate( + end - start, + func(i : Nat) = blob.get(start + i), ); }; diff --git a/tests/Candid.Large.test.mo b/tests/Candid.Large.test.mo index 6bf616b..800b3e5 100644 --- a/tests/Candid.Large.test.mo +++ b/tests/Candid.Large.test.mo @@ -5,6 +5,7 @@ import Prelude "mo:base/Prelude"; import Text "mo:base/Text"; import Char "mo:base/Char"; import Buffer "mo:base/Buffer"; +import Nat64 "mo:base/Nat64"; import Fuzz "mo:fuzz"; import Itertools "mo:itertools/Iter"; @@ -16,7 +17,23 @@ import CandidDecoder "../src/Candid/Blob/Decoder"; import LegacyCandidDecoder "../src/Candid/Blob/Decoder"; import LegacyCandidEncoder "../src/Candid/Blob/Encoder"; -let fuzz = Fuzz.fromSeed(0x7eadbeef); +func createGenerator(seed : Nat) : { next() : Nat } { + // Pure bitwise xorshift64 - no multiplication or addition! + var state : Nat64 = Nat64.fromNat(seed); + if (state == 0) state := 1; // Avoid zero state + + { + next = func() : Nat { + // Only XOR and bit shifts - fastest possible + state ^= state << 13 : Nat64; + state ^= state >> 7 : Nat64; + state ^= state << 17 : Nat64; + Nat64.toNat(state); + }; + }; +}; + +let fuzz = Fuzz.create(createGenerator(42)); let limit = 1_000; diff --git a/tests/Candid.Test.mo b/tests/Candid.Test.mo index e541dab..77351b1 100644 --- a/tests/Candid.Test.mo +++ b/tests/Candid.Test.mo @@ -107,13 +107,7 @@ suite( let blob = to_candid (records); let #ok(candid) = Candid.decode(blob, Serde.concatKeys([PermissionKeys, UserKeys, RecordKeys]), null); - let expected = [#Array([ - #Record([("group", #Text("null")), ("users", #Null)]), - #Record([("group", #Text("empty")), ("users", #Option(#Array([])))]), - #Record([("group", #Text("admins")), ("users", #Option(#Array([#Record([("age", #Nat(32)), ("permission", #Variant("admin", #Null)), ("name", #Text("John"))])])))]), - #Record([("group", #Text("users")), ("users", #Option(#Array([#Record([("age", #Nat(28)), ("permission", #Variant("read_all", #Null)), ("name", #Text("Ali"))]), #Record([("age", #Nat(40)), ("permission", #Variant("write_all", #Null)), ("name", #Text("James"))])])))]), - #Record([("group", #Text("base")), ("users", #Option(#Array([#Record([("age", #Nat(32)), ("permission", #Variant("read", #Array([#Text("posts"), #Text("comments")]))), ("name", #Text("Henry"))]), #Record([("age", #Nat(32)), ("permission", #Variant("write", #Array([#Text("posts"), #Text("comments")]))), ("name", #Text("Steven"))])])))]) - ])]; + let expected = [#Array([#Record([("group", #Text("null")), ("users", #Null)]), #Record([("group", #Text("empty")), ("users", #Option(#Array([])))]), #Record([("group", #Text("admins")), ("users", #Option(#Array([#Record([("age", #Nat(32)), ("permission", #Variant("admin", #Null)), ("name", #Text("John"))])])))]), #Record([("group", #Text("users")), ("users", #Option(#Array([#Record([("age", #Nat(28)), ("permission", #Variant("read_all", #Null)), ("name", #Text("Ali"))]), #Record([("age", #Nat(40)), ("permission", #Variant("write_all", #Null)), ("name", #Text("James"))])])))]), #Record([("group", #Text("base")), ("users", #Option(#Array([#Record([("age", #Nat(32)), ("permission", #Variant("read", #Array([#Text("posts"), #Text("comments")]))), ("name", #Text("Henry"))]), #Record([("age", #Nat(32)), ("permission", #Variant("write", #Array([#Text("posts"), #Text("comments")]))), ("name", #Text("Steven"))])])))])])]; assert candid == expected; }, @@ -278,7 +272,6 @@ suite( let motoko_blob = Blob.fromArray([1, 2, 3, 4]); let motoko_array : [Nat8] = [1, 2, 3, 4]; - let bytes_array = to_candid (motoko_blob); let bytes_blob = to_candid (motoko_blob); @@ -651,13 +644,13 @@ suite( let array_val : ?Variant = from_candid (array_blob); assert assertAllTrue([ - text_val == ? #text("hello"), - nat_val == ? #nat(123), - bool_val == ? #bool(true), - record_val == ? #record({ + text_val == ?#text("hello"), + nat_val == ?#nat(123), + bool_val == ?#bool(true), + record_val == ?#record({ site = "github"; }), - array_val == ? #array([1, 2, 3]), + array_val == ?#array([1, 2, 3]), ]); }, ); From a2042b2fdaa81a69034e960ba057bdb711ed16cd Mon Sep 17 00:00:00 2001 From: Tomi Jaga Date: Sun, 27 Jul 2025 14:48:41 -0700 Subject: [PATCH 2/6] Library Optimization to reduce instructions by: - using the ByteUtils library for binary conversions - created a new TypedSerializer that stores the candid type data so it eliminates the need to decode and encode the types each time. --- .bench/types.bench.json | 1750 +++++++++ bench/serde.bench.mo | 26 + src/Candid/Blob/Decoder.mo | 1411 ++++---- src/Candid/Blob/Encoder.mo | 3224 +++++++++-------- src/Candid/Blob/TypedSerializer.mo | 459 +++ src/Candid/lib.mo | 8 +- src/Utils.mo | 407 ++- tests/Candid.Large.test.mo | 236 -- tests/Candid.Test.mo | 181 +- tests/CandidTestUtils.mo | 102 + tests/{Candid.ICRC3.Test.mo => ICRC3.Test.mo} | 9 +- ...tiveType.Test.mo => PrimitiveType.Test.mo} | 88 + tests/RepIndyHash.Test.mo | 151 +- tests/Stress.test.mo | 410 +++ 14 files changed, 5729 insertions(+), 2733 deletions(-) create mode 100644 .bench/types.bench.json create mode 100644 src/Candid/Blob/TypedSerializer.mo delete mode 100644 tests/Candid.Large.test.mo create mode 100644 tests/CandidTestUtils.mo rename tests/{Candid.ICRC3.Test.mo => ICRC3.Test.mo} (81%) rename tests/{Candid.PrimitiveType.Test.mo => PrimitiveType.Test.mo} (55%) create mode 100644 tests/Stress.test.mo diff --git a/.bench/types.bench.json b/.bench/types.bench.json new file mode 100644 index 0000000..73fee6c --- /dev/null +++ b/.bench/types.bench.json @@ -0,0 +1,1750 @@ +{ + "version": 1, + "moc": "0.14.13", + "replica": "dfx", + "replicaVersion": "0.28.0-beta.1", + "gc": "incremental", + "forceGc": true, + "results": [ + [ + "Nat:encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 72692, + "rts_memory_size": 0, + "rts_total_allocation": 20204, + "rts_collector_instructions": 6559, + "rts_mutator_instructions": -13063, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 20204, + "rts_reclaimed": 0 + } + ], + [ + "Nat:encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 64497, + "rts_memory_size": 0, + "rts_total_allocation": 17284, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 17284, + "rts_reclaimed": 0 + } + ], + [ + "Nat:decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 14668, + "rts_memory_size": 0, + "rts_total_allocation": 10592, + "rts_collector_instructions": 6242, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10592, + "rts_reclaimed": 0 + } + ], + [ + "Nat:decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 29866, + "rts_memory_size": 0, + "rts_total_allocation": 10536, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10536, + "rts_reclaimed": 0 + } + ], + [ + "Nat8:encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 72122, + "rts_memory_size": 0, + "rts_total_allocation": 20192, + "rts_collector_instructions": 6553, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 20192, + "rts_reclaimed": 0 + } + ], + [ + "Nat8:encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 64226, + "rts_memory_size": 0, + "rts_total_allocation": 17272, + "rts_collector_instructions": 6254, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 17272, + "rts_reclaimed": 0 + } + ], + [ + "Nat8:decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 14455, + "rts_memory_size": 0, + "rts_total_allocation": 10552, + "rts_collector_instructions": 6242, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10552, + "rts_reclaimed": 0 + } + ], + [ + "Nat8:decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 29970, + "rts_memory_size": 0, + "rts_total_allocation": 10496, + "rts_collector_instructions": 6254, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10496, + "rts_reclaimed": 0 + } + ], + [ + "Nat16:encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 73276, + "rts_memory_size": 0, + "rts_total_allocation": 20200, + "rts_collector_instructions": 6553, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 20200, + "rts_reclaimed": 0 + } + ], + [ + "Nat16:encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 65677, + "rts_memory_size": 0, + "rts_total_allocation": 17280, + "rts_collector_instructions": 6242, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 17280, + "rts_reclaimed": 0 + } + ], + [ + "Nat16:decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 15356, + "rts_memory_size": 0, + "rts_total_allocation": 10572, + "rts_collector_instructions": 6254, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10572, + "rts_reclaimed": 0 + } + ], + [ + "Nat16:decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 31188, + "rts_memory_size": 0, + "rts_total_allocation": 10516, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10516, + "rts_reclaimed": 0 + } + ], + [ + "Nat32:encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 75053, + "rts_memory_size": 0, + "rts_total_allocation": 20208, + "rts_collector_instructions": 6547, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 20208, + "rts_reclaimed": 0 + } + ], + [ + "Nat32:encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 67752, + "rts_memory_size": 0, + "rts_total_allocation": 17288, + "rts_collector_instructions": 6242, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 17288, + "rts_reclaimed": 0 + } + ], + [ + "Nat32:decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 16643, + "rts_memory_size": 0, + "rts_total_allocation": 10612, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10612, + "rts_reclaimed": 0 + } + ], + [ + "Nat32:decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 32792, + "rts_memory_size": 0, + "rts_total_allocation": 10556, + "rts_collector_instructions": 6242, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10556, + "rts_reclaimed": 0 + } + ], + [ + "Nat64:encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 77811, + "rts_memory_size": 0, + "rts_total_allocation": 20228, + "rts_collector_instructions": 6559, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 20228, + "rts_reclaimed": 0 + } + ], + [ + "Nat64:encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 70808, + "rts_memory_size": 0, + "rts_total_allocation": 17308, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 17308, + "rts_reclaimed": 0 + } + ], + [ + "Nat64:decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 18709, + "rts_memory_size": 0, + "rts_total_allocation": 10708, + "rts_collector_instructions": 6254, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10708, + "rts_reclaimed": 0 + } + ], + [ + "Nat64:decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 35175, + "rts_memory_size": 0, + "rts_total_allocation": 10652, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10652, + "rts_reclaimed": 0 + } + ], + [ + "Int:encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 75309, + "rts_memory_size": 0, + "rts_total_allocation": 20204, + "rts_collector_instructions": 6547, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 20204, + "rts_reclaimed": 0 + } + ], + [ + "Int:encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 68734, + "rts_memory_size": 0, + "rts_total_allocation": 17284, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 17284, + "rts_reclaimed": 0 + } + ], + [ + "Int:decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 17281, + "rts_memory_size": 0, + "rts_total_allocation": 10592, + "rts_collector_instructions": 6254, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10592, + "rts_reclaimed": 0 + } + ], + [ + "Int:decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 34064, + "rts_memory_size": 0, + "rts_total_allocation": 10536, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10536, + "rts_reclaimed": 0 + } + ], + [ + "Int8:encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 74577, + "rts_memory_size": 0, + "rts_total_allocation": 20192, + "rts_collector_instructions": 6547, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 20192, + "rts_reclaimed": 0 + } + ], + [ + "Int8:encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 68301, + "rts_memory_size": 0, + "rts_total_allocation": 17272, + "rts_collector_instructions": 6242, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 17272, + "rts_reclaimed": 0 + } + ], + [ + "Int8:decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 16930, + "rts_memory_size": 0, + "rts_total_allocation": 10552, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10552, + "rts_reclaimed": 0 + } + ], + [ + "Int8:decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 34030, + "rts_memory_size": 0, + "rts_total_allocation": 10496, + "rts_collector_instructions": 6242, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10496, + "rts_reclaimed": 0 + } + ], + [ + "Int16:encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 75731, + "rts_memory_size": 0, + "rts_total_allocation": 20200, + "rts_collector_instructions": 6559, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 20200, + "rts_reclaimed": 0 + } + ], + [ + "Int16:encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 69752, + "rts_memory_size": 0, + "rts_total_allocation": 17280, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 17280, + "rts_reclaimed": 0 + } + ], + [ + "Int16:decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 17831, + "rts_memory_size": 0, + "rts_total_allocation": 10572, + "rts_collector_instructions": 6242, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10572, + "rts_reclaimed": 0 + } + ], + [ + "Int16:decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 35248, + "rts_memory_size": 0, + "rts_total_allocation": 10516, + "rts_collector_instructions": 6242, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10516, + "rts_reclaimed": 0 + } + ], + [ + "Int32:encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 77492, + "rts_memory_size": 0, + "rts_total_allocation": 20208, + "rts_collector_instructions": 6553, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 20208, + "rts_reclaimed": 0 + } + ], + [ + "Int32:encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 71811, + "rts_memory_size": 0, + "rts_total_allocation": 17288, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 17288, + "rts_reclaimed": 0 + } + ], + [ + "Int32:decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 19123, + "rts_memory_size": 0, + "rts_total_allocation": 10612, + "rts_collector_instructions": 6242, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10612, + "rts_reclaimed": 0 + } + ], + [ + "Int32:decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 36857, + "rts_memory_size": 0, + "rts_total_allocation": 10556, + "rts_collector_instructions": 6254, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10556, + "rts_reclaimed": 0 + } + ], + [ + "Int64:encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 80226, + "rts_memory_size": 0, + "rts_total_allocation": 20228, + "rts_collector_instructions": 6547, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 20228, + "rts_reclaimed": 0 + } + ], + [ + "Int64:encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 74843, + "rts_memory_size": 0, + "rts_total_allocation": 17308, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 17308, + "rts_reclaimed": 0 + } + ], + [ + "Int64:decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 21188, + "rts_memory_size": 0, + "rts_total_allocation": 10708, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10708, + "rts_reclaimed": 0 + } + ], + [ + "Int64:decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 39239, + "rts_memory_size": 0, + "rts_total_allocation": 10652, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10652, + "rts_reclaimed": 0 + } + ], + [ + "Float:encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 102683, + "rts_memory_size": 0, + "rts_total_allocation": 20932, + "rts_collector_instructions": 6547, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 20932, + "rts_reclaimed": 0 + } + ], + [ + "Float:encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 97520, + "rts_memory_size": 0, + "rts_total_allocation": 18012, + "rts_collector_instructions": 6254, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 18012, + "rts_reclaimed": 0 + } + ], + [ + "Float:decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 48826, + "rts_memory_size": 0, + "rts_total_allocation": 11428, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 11428, + "rts_reclaimed": 0 + } + ], + [ + "Float:decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 67194, + "rts_memory_size": 0, + "rts_total_allocation": 11372, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 11372, + "rts_reclaimed": 0 + } + ], + [ + "Bool:encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 77158, + "rts_memory_size": 0, + "rts_total_allocation": 20192, + "rts_collector_instructions": 6547, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 20192, + "rts_reclaimed": 0 + } + ], + [ + "Bool:encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 72320, + "rts_memory_size": 0, + "rts_total_allocation": 17272, + "rts_collector_instructions": 6254, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 17272, + "rts_reclaimed": 0 + } + ], + [ + "Bool:decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 19285, + "rts_memory_size": 0, + "rts_total_allocation": 10552, + "rts_collector_instructions": 6242, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10552, + "rts_reclaimed": 0 + } + ], + [ + "Bool:decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 38030, + "rts_memory_size": 0, + "rts_total_allocation": 10496, + "rts_collector_instructions": 6254, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10496, + "rts_reclaimed": 0 + } + ], + [ + "Text:encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 81842, + "rts_memory_size": 0, + "rts_total_allocation": 20260, + "rts_collector_instructions": 6553, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 20260, + "rts_reclaimed": 0 + } + ], + [ + "Text:encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 77278, + "rts_memory_size": 0, + "rts_total_allocation": 17340, + "rts_collector_instructions": 6242, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 17340, + "rts_reclaimed": 0 + } + ], + [ + "Text:decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 23306, + "rts_memory_size": 0, + "rts_total_allocation": 10772, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10772, + "rts_reclaimed": 0 + } + ], + [ + "Text:decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 42313, + "rts_memory_size": 0, + "rts_total_allocation": 10716, + "rts_collector_instructions": 6254, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10716, + "rts_reclaimed": 0 + } + ], + [ + "Null:encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 77016, + "rts_memory_size": 0, + "rts_total_allocation": 20156, + "rts_collector_instructions": 6242, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 20156, + "rts_reclaimed": 0 + } + ], + [ + "Null:encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 72968, + "rts_memory_size": 0, + "rts_total_allocation": 17236, + "rts_collector_instructions": 6254, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 17236, + "rts_reclaimed": 0 + } + ], + [ + "Null:decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 88300, + "rts_memory_size": 0, + "rts_total_allocation": 20604, + "rts_collector_instructions": 6242, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 20604, + "rts_reclaimed": 0 + } + ], + [ + "Null:decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 83698, + "rts_memory_size": 0, + "rts_total_allocation": 17596, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 17596, + "rts_reclaimed": 0 + } + ], + [ + "Empty:encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 77430, + "rts_memory_size": 0, + "rts_total_allocation": 20156, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 20156, + "rts_reclaimed": 0 + } + ], + [ + "Empty:encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 73680, + "rts_memory_size": 0, + "rts_total_allocation": 17236, + "rts_collector_instructions": 6242, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 17236, + "rts_reclaimed": 0 + } + ], + [ + "Empty:decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 88800, + "rts_memory_size": 0, + "rts_total_allocation": 20604, + "rts_collector_instructions": 6242, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 20604, + "rts_reclaimed": 0 + } + ], + [ + "Empty:decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 84421, + "rts_memory_size": 0, + "rts_total_allocation": 17596, + "rts_collector_instructions": 6254, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 17596, + "rts_reclaimed": 0 + } + ], + [ + "Principal:encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 98730, + "rts_memory_size": 0, + "rts_total_allocation": 20516, + "rts_collector_instructions": 6553, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 20516, + "rts_reclaimed": 0 + } + ], + [ + "Principal:encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 95090, + "rts_memory_size": 0, + "rts_total_allocation": 17596, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 17596, + "rts_reclaimed": 0 + } + ], + [ + "Principal:decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 33722, + "rts_memory_size": 0, + "rts_total_allocation": 11392, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 11392, + "rts_reclaimed": 0 + } + ], + [ + "Principal:decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 53690, + "rts_memory_size": 0, + "rts_total_allocation": 11336, + "rts_collector_instructions": 6242, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 11336, + "rts_reclaimed": 0 + } + ], + [ + "Blob:encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 243704, + "rts_memory_size": 0, + "rts_total_allocation": 28872, + "rts_collector_instructions": 6553, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 28872, + "rts_reclaimed": 0 + } + ], + [ + "Blob:encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 210308, + "rts_memory_size": 0, + "rts_total_allocation": 22568, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 22568, + "rts_reclaimed": 0 + } + ], + [ + "Blob:decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 98472, + "rts_memory_size": 0, + "rts_total_allocation": 16316, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 16316, + "rts_reclaimed": 0 + } + ], + [ + "Blob:decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 116728, + "rts_memory_size": 0, + "rts_total_allocation": 16044, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 16044, + "rts_reclaimed": 0 + } + ], + [ + "Option(Nat):encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 116006, + "rts_memory_size": 0, + "rts_total_allocation": 23296, + "rts_collector_instructions": 6553, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 23296, + "rts_reclaimed": 0 + } + ], + [ + "Option(Nat):encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 88982, + "rts_memory_size": 0, + "rts_total_allocation": 17712, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 17712, + "rts_reclaimed": 0 + } + ], + [ + "Option(Nat):decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 26921, + "rts_memory_size": 0, + "rts_total_allocation": 10920, + "rts_collector_instructions": 6242, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10920, + "rts_reclaimed": 0 + } + ], + [ + "Option(Nat):decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 45515, + "rts_memory_size": 0, + "rts_total_allocation": 10648, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10648, + "rts_reclaimed": 0 + } + ], + [ + "Option(Text):encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 120251, + "rts_memory_size": 0, + "rts_total_allocation": 23356, + "rts_collector_instructions": 6553, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 23356, + "rts_reclaimed": 0 + } + ], + [ + "Option(Text):encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 93315, + "rts_memory_size": 0, + "rts_total_allocation": 17772, + "rts_collector_instructions": 6242, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 17772, + "rts_reclaimed": 0 + } + ], + [ + "Option(Text):decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 30441, + "rts_memory_size": 0, + "rts_total_allocation": 11100, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 11100, + "rts_reclaimed": 0 + } + ], + [ + "Option(Text):decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 49302, + "rts_memory_size": 0, + "rts_total_allocation": 10828, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10828, + "rts_reclaimed": 0 + } + ], + [ + "Array(Nat8):encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 136470, + "rts_memory_size": 0, + "rts_total_allocation": 25040, + "rts_collector_instructions": 6547, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 25040, + "rts_reclaimed": 0 + } + ], + [ + "Array(Nat8):encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 91988, + "rts_memory_size": 0, + "rts_total_allocation": 17816, + "rts_collector_instructions": 6242, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 17816, + "rts_reclaimed": 0 + } + ], + [ + "Array(Nat8):decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 29575, + "rts_memory_size": 0, + "rts_total_allocation": 11072, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 11072, + "rts_reclaimed": 0 + } + ], + [ + "Array(Nat8):decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 48797, + "rts_memory_size": 0, + "rts_total_allocation": 10800, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10800, + "rts_reclaimed": 0 + } + ], + [ + "Array(Text):encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 187126, + "rts_memory_size": 0, + "rts_total_allocation": 26208, + "rts_collector_instructions": 6559, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 26208, + "rts_reclaimed": 0 + } + ], + [ + "Array(Text):encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 127872, + "rts_memory_size": 0, + "rts_total_allocation": 18456, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 18456, + "rts_reclaimed": 0 + } + ], + [ + "Array(Text):decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 57932, + "rts_memory_size": 0, + "rts_total_allocation": 12824, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 12824, + "rts_reclaimed": 0 + } + ], + [ + "Array(Text):decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 77426, + "rts_memory_size": 0, + "rts_total_allocation": 12552, + "rts_collector_instructions": 6242, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 12552, + "rts_reclaimed": 0 + } + ], + [ + "Array(Record):encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 298069, + "rts_memory_size": 0, + "rts_total_allocation": 33364, + "rts_collector_instructions": 6559, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 33364, + "rts_reclaimed": 0 + } + ], + [ + "Array(Record):encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 204295, + "rts_memory_size": 0, + "rts_total_allocation": 21484, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 21484, + "rts_reclaimed": 0 + } + ], + [ + "Array(Record):decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 73874, + "rts_memory_size": 0, + "rts_total_allocation": 14052, + "rts_collector_instructions": 6242, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 14052, + "rts_reclaimed": 0 + } + ], + [ + "Array(Record):decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 86210, + "rts_memory_size": 0, + "rts_total_allocation": 13376, + "rts_collector_instructions": 6242, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 13376, + "rts_reclaimed": 0 + } + ], + [ + "Record(Nested):encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 761090, + "rts_memory_size": 0, + "rts_total_allocation": 54784, + "rts_collector_instructions": 6553, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 54784, + "rts_reclaimed": 0 + } + ], + [ + "Record(Nested):encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 528828, + "rts_memory_size": 0, + "rts_total_allocation": 32152, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 32152, + "rts_reclaimed": 0 + } + ], + [ + "Record(Nested):decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 309543, + "rts_memory_size": 0, + "rts_total_allocation": 23948, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 23948, + "rts_reclaimed": 0 + } + ], + [ + "Record(Nested):decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 148515, + "rts_memory_size": 0, + "rts_total_allocation": 17364, + "rts_collector_instructions": 6254, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 17364, + "rts_reclaimed": 0 + } + ], + [ + "Variant(Simple):encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 149940, + "rts_memory_size": 0, + "rts_total_allocation": 24912, + "rts_collector_instructions": 6553, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 24912, + "rts_reclaimed": 0 + } + ], + [ + "Variant(Simple):encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 141065, + "rts_memory_size": 0, + "rts_total_allocation": 18860, + "rts_collector_instructions": 6242, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 18860, + "rts_reclaimed": 0 + } + ], + [ + "Variant(Simple):decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 63607, + "rts_memory_size": 0, + "rts_total_allocation": 12456, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 12456, + "rts_reclaimed": 0 + } + ], + [ + "Variant(Simple):decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 54068, + "rts_memory_size": 0, + "rts_total_allocation": 10752, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 10752, + "rts_reclaimed": 0 + } + ], + [ + "Variant(Complex):encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 959922, + "rts_memory_size": 0, + "rts_total_allocation": 65264, + "rts_collector_instructions": 6565, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 65264, + "rts_reclaimed": 0 + } + ], + [ + "Variant(Complex):encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 808793, + "rts_memory_size": 0, + "rts_total_allocation": 42560, + "rts_collector_instructions": 6242, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 42560, + "rts_reclaimed": 0 + } + ], + [ + "Variant(Complex):decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 417094, + "rts_memory_size": 0, + "rts_total_allocation": 27176, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 27176, + "rts_reclaimed": 0 + } + ], + [ + "Variant(Complex):decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 124034, + "rts_memory_size": 0, + "rts_total_allocation": 15360, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 15360, + "rts_reclaimed": 0 + } + ], + [ + "Large Text:encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 5829081, + "rts_memory_size": 0, + "rts_total_allocation": 146600, + "rts_collector_instructions": 6564, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 146600, + "rts_reclaimed": 0 + } + ], + [ + "Large Text:encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 5829668, + "rts_memory_size": 0, + "rts_total_allocation": 143680, + "rts_collector_instructions": 6265, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 143680, + "rts_reclaimed": 0 + } + ], + [ + "Large Text:decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 1907297, + "rts_memory_size": 0, + "rts_total_allocation": 138216, + "rts_collector_instructions": 6253, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 138216, + "rts_reclaimed": 0 + } + ], + [ + "Large Text:decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 1931456, + "rts_memory_size": 0, + "rts_total_allocation": 138160, + "rts_collector_instructions": 6259, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 138160, + "rts_reclaimed": 0 + } + ], + [ + "Large Array:encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 6041873, + "rts_memory_size": 0, + "rts_total_allocation": 229372, + "rts_collector_instructions": 6564, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 229372, + "rts_reclaimed": 0 + } + ], + [ + "Large Array:encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 2735910, + "rts_memory_size": 0, + "rts_total_allocation": 92780, + "rts_collector_instructions": 6253, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 92780, + "rts_reclaimed": 0 + } + ], + [ + "Large Array:decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 1221435, + "rts_memory_size": 0, + "rts_total_allocation": 86656, + "rts_collector_instructions": 6259, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 86656, + "rts_reclaimed": 0 + } + ], + [ + "Large Array:decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 1243882, + "rts_memory_size": 0, + "rts_total_allocation": 86384, + "rts_collector_instructions": 6265, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 86384, + "rts_reclaimed": 0 + } + ], + [ + "Deep Nesting:encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 627380, + "rts_memory_size": 0, + "rts_total_allocation": 54440, + "rts_collector_instructions": 6553, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 54440, + "rts_reclaimed": 0 + } + ], + [ + "Deep Nesting:encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 433625, + "rts_memory_size": 0, + "rts_total_allocation": 29820, + "rts_collector_instructions": 6254, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 29820, + "rts_reclaimed": 0 + } + ], + [ + "Deep Nesting:decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 125086, + "rts_memory_size": 0, + "rts_total_allocation": 17016, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 17016, + "rts_reclaimed": 0 + } + ], + [ + "Deep Nesting:decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 121838, + "rts_memory_size": 0, + "rts_total_allocation": 15376, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 15376, + "rts_reclaimed": 0 + } + ], + [ + "Wide Record:encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 850204, + "rts_memory_size": 0, + "rts_total_allocation": 45720, + "rts_collector_instructions": 6559, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 45720, + "rts_reclaimed": 0 + } + ], + [ + "Wide Record:encode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 507152, + "rts_memory_size": 0, + "rts_total_allocation": 26956, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 26956, + "rts_reclaimed": 0 + } + ], + [ + "Wide Record:decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 336816, + "rts_memory_size": 0, + "rts_total_allocation": 30388, + "rts_collector_instructions": 6242, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 30388, + "rts_reclaimed": 0 + } + ], + [ + "Wide Record:decode(sans inference)", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 329957, + "rts_memory_size": 0, + "rts_total_allocation": 28840, + "rts_collector_instructions": 6248, + "rts_mutator_instructions": -10592, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 28840, + "rts_reclaimed": 0 + } + ] + ] +} \ No newline at end of file diff --git a/bench/serde.bench.mo b/bench/serde.bench.mo index 72573f3..a4f0291 100644 --- a/bench/serde.bench.mo +++ b/bench/serde.bench.mo @@ -25,6 +25,7 @@ module { "Serde: One Shot", "Serde: One Shot sans type inference", "Motoko (to_candid(), from_candid())", + "Serde: Single Type Instance", ]); @@ -235,6 +236,31 @@ module { }; }; + case ("Serde: Single Type Instance", "decode()") { + let options = { + Serde.Candid.defaultOptions with types = ?FormattedStoreItem + }; + let serializer = Serde.Candid.TypedSerializer.fromBlob(candify_store_item.to_blob(buffer.get(0)), StoreItemKeys, ?options); + + for (i in Itertools.range(0, limit)) { + let item = buffer.get(i); + let candid_blob = candify_store_item.to_blob(item); + + let #ok(candid) = Serde.Candid.TypedSerializer.decode(serializer, candid_blob); + // candid_buffer.add(candid); + }; + }; + + case ("Serde: Single Type Instance", "encode()") { + + let serializer = Serde.Candid.TypedSerializer.new(FormattedStoreItem, null); + + for (i in Itertools.range(0, limit)) { + let candid = candid_buffer.get(i); + let res = Serde.Candid.TypedSerializer.encode(serializer, candid); + }; + }; + case (_, _) { Debug.trap("Should be unreachable:\n row = \"" # debug_show row # "\" and col = \"" # debug_show col # "\""); }; diff --git a/src/Candid/Blob/Decoder.mo b/src/Candid/Blob/Decoder.mo index dee5a34..fe07f79 100644 --- a/src/Candid/Blob/Decoder.mo +++ b/src/Candid/Blob/Decoder.mo @@ -21,7 +21,7 @@ import Option "mo:base/Option"; import Map "mo:map/Map"; import Set "mo:map/Set"; -import FloatX "mo:xtended-numbers/FloatX"; +import ByteUtils "mo:byte-utils"; import { hashName = hash_record_key } "mo:candid/Tag"; @@ -30,871 +30,874 @@ import Utils "../../Utils"; import CandidUtils "CandidUtils"; module { - type Iter = Iter.Iter; - type Result = Result.Result; - - type TrieMap = TrieMap.TrieMap; - type Candid = T.Candid; - type KeyValuePair = T.KeyValuePair; - - type Buffer = Buffer.Buffer; - type Hash = Nat32; - type Map = Map.Map; - type Set = Set.Set; - type Order = Order.Order; - - type CandidType = T.CandidType; - type ShallowCandidTypes = T.ShallowCandidTypes; - - let { nhash } = Set; - let { n32hash; thash } = Map; - - /// Decodes a blob encoded in the candid format into a list of the [Candid](./Types.mo#Candid) type in motoko - /// - /// ### Inputs - /// - **blob** - A blob encoded in the candid format - /// - **record_keys** - The record keys to use when decoding a record. - /// - **options** - An optional arguement to specify options for decoding. - - public func decode(blob : Blob, record_keys : [Text], options : ?T.Options) : Result<[Candid], Text> { - one_shot(blob, record_keys, options); - }; - - public func decodeOne(blob : Blob, record_keys : [Text], options : ?T.Options) : Result { - let result = one_shot(blob, record_keys, options); - - switch (result) { - case (#ok(candid_values)) { - if (candid_values.size() == 1) { - #ok(candid_values[0]); - } else { - #err("Expected one value in blob, instead got " # debug_show (candid_values.size())); - }; - }; - case (#err(msg)) #err(msg); + type Iter = Iter.Iter; + type Result = Result.Result; + + type TrieMap = TrieMap.TrieMap; + type Candid = T.Candid; + type KeyValuePair = T.KeyValuePair; + + type Buffer = Buffer.Buffer; + type Hash = Nat32; + type Map = Map.Map; + type Set = Set.Set; + type Order = Order.Order; + + type CandidType = T.CandidType; + type ShallowCandidTypes = T.ShallowCandidTypes; + + let { nhash } = Set; + let { n32hash; thash } = Map; + + /// Decodes a blob encoded in the candid format into a list of the [Candid](./Types.mo#Candid) type in motoko + /// + /// ### Inputs + /// - **blob** - A blob encoded in the candid format + /// - **record_keys** - The record keys to use when decoding a record. + /// - **options** - An optional arguement to specify options for decoding. + public func decode(blob : Blob, record_keys : [Text], options : ?T.Options) : Result<[Candid], Text> { + one_shot(blob, record_keys, options); }; - }; - - /// - type CandidBlobSequences = { - magic : Blob; - compound_types : Blob; - types : Blob; - values : Blob; - }; - public func split(blob : Blob, options : ?T.Options) : Result { - let bytes = blob; + public func decodeOne(blob : Blob, record_keys : [Text], options : ?T.Options) : Result { + let result = one_shot(blob, record_keys, options); - let state : [var Nat] = [var 0]; - - let magic = Array.tabulate(4, func(i : Nat) : Nat8 = read(bytes, state)); + switch (result) { + case (#ok(candid_values)) { + if (candid_values.size() == 1) { + #ok(candid_values[0]); + } else { + #err("Expected one value in blob, instead got " # debug_show (candid_values.size())); + }; + }; + case (#err(msg)) #err(msg); + }; + }; - if (magic != [0x44, 0x49, 0x44, 0x4c]) { - return #err("Invalid Magic Number"); + /// + type CandidBlobSequences = { + magic : Blob; + compound_types : Blob; + types : Blob; + values : Blob; }; - let compound_types_start_index = state[C.BYTES_INDEX]; - let total_compound_types = decode_leb128(bytes, state); - skip_compound_types(bytes, state, total_compound_types); + public func split(blob : Blob, options : ?T.Options) : Result { + let bytes = blob; - let types_start_index = state[C.BYTES_INDEX]; + let state : [var Nat] = [var 0]; - skip_types(bytes, state); + let magic = Array.tabulate(4, func(i : Nat) : Nat8 = read(bytes, state)); - let values_start_index = state[C.BYTES_INDEX]; + if (magic != [0x44, 0x49, 0x44, 0x4c]) { + return #err("Invalid Magic Number"); + }; - let sequences : CandidBlobSequences = { - magic = Blob.fromArray(magic); - compound_types = Blob.fromArray(Utils.blob_slice(bytes, compound_types_start_index, types_start_index)); - types = Blob.fromArray(Utils.blob_slice(bytes, types_start_index, values_start_index)); - values = Blob.fromArray(Utils.blob_slice(bytes, values_start_index, bytes.size())); - }; + let compound_types_start_index = state[C.BYTES_INDEX]; + let total_compound_types = decode_leb128(bytes, state); + skip_compound_types(bytes, state, total_compound_types); - #ok(sequences); - }; + let types_start_index = state[C.BYTES_INDEX]; - public func extract_values_sequence(blob : Blob) : Result { - let bytes = blob; + skip_types(bytes, state); - let state : [var Nat] = [var 0]; + let values_start_index = state[C.BYTES_INDEX]; - let magic = Array.tabulate(4, func(i : Nat) : Nat8 = read(bytes, state)); + let sequences : CandidBlobSequences = { + magic = Blob.fromArray(magic); + compound_types = Blob.fromArray(Utils.blob_slice(bytes, compound_types_start_index, types_start_index)); + types = Blob.fromArray(Utils.blob_slice(bytes, types_start_index, values_start_index)); + values = Blob.fromArray(Utils.blob_slice(bytes, values_start_index, bytes.size())); + }; - if (magic != [0x44, 0x49, 0x44, 0x4c]) { - return #err("Invalid Magic Number"); + #ok(sequences); }; - let total_compound_types = decode_leb128(bytes, state); - skip_compound_types(bytes, state, total_compound_types); - - skip_types(bytes, state); - - let values_start_index = state[C.BYTES_INDEX]; - let values_sequence = Blob.fromArray(Utils.blob_slice(bytes, values_start_index, bytes.size())); - - #ok(values_sequence) + public func extract_values_sequence(blob : Blob) : Result { + let bytes = blob; - }; + let state : [var Nat] = [var 0]; - public func one_shot(blob : Blob, record_keys : [Text], options : ?T.Options) : Result<[Candid], Text> { + let magic = Array.tabulate(4, func(i : Nat) : Nat8 = read(bytes, state)); - let record_key_map = Map.new(); - - var i = 0; - while (i < record_keys.size()) { - let key = formatVariantKey(record_keys[i]); - let hash = hash_record_key(key); - ignore Map.put(record_key_map, n32hash, hash, key); - i += 1; - }; + if (magic != [0x44, 0x49, 0x44, 0x4c]) { + return #err("Invalid Magic Number"); + }; - ignore do ? { - let key_pairs_to_rename = options!.renameKeys; + let total_compound_types = decode_leb128(bytes, state); + skip_compound_types(bytes, state, total_compound_types); - var i = 0; - while (i < key_pairs_to_rename.size()) { - let original_key = formatVariantKey(key_pairs_to_rename[i].0); - let new_key = formatVariantKey(key_pairs_to_rename[i].1); + skip_types(bytes, state); - let hash = hash_record_key(original_key); - ignore Map.put(record_key_map, n32hash, hash, new_key); + let values_start_index = state[C.BYTES_INDEX]; + let values_sequence = Blob.fromArray(Utils.blob_slice(bytes, values_start_index, bytes.size())); - i += 1; - }; + #ok(values_sequence) }; - one_shot_decode(blob, record_key_map, Option.get(options, T.defaultOptions)); - }; - - func read(bytes : Blob, state : [var Nat]) : Nat8 { - let byte = bytes.get(state[C.BYTES_INDEX]); - state[C.BYTES_INDEX] += 1; - byte; - }; - - func peek(bytes : Blob, state : [var Nat]) : Nat8 { - bytes.get(state[C.BYTES_INDEX]); - }; - - func code_to_primitive_type(code : Nat8) : CandidType { - if (code == T.TypeCode.Null) { - #Null; - } else if (code == T.TypeCode.Bool) { - #Bool; - } else if (code == T.TypeCode.Nat) { - #Nat; - } else if (code == T.TypeCode.Nat8) { - #Nat8; - } else if (code == T.TypeCode.Nat16) { - #Nat16; - } else if (code == T.TypeCode.Nat32) { - #Nat32; - } else if (code == T.TypeCode.Nat64) { - #Nat64; - } else if (code == T.TypeCode.Int) { - #Int; - } else if (code == T.TypeCode.Int8) { - #Int8; - } else if (code == T.TypeCode.Int16) { - #Int16; - } else if (code == T.TypeCode.Int32) { - #Int32; - } else if (code == T.TypeCode.Int64) { - #Int64; - } else if (code == T.TypeCode.Float) { - #Float; - } else if (code == T.TypeCode.Text) { - #Text; - } else if (code == T.TypeCode.Principal) { - #Principal; - } else if (code == T.TypeCode.Empty) { - #Empty; - } else { - Debug.trap("code [" # debug_show code # "] does not belong to a primitive type"); - }; - }; + public func one_shot(blob : Blob, record_keys : [Text], options : ?T.Options) : Result<[Candid], Text> { - func is_code_primitive_type(code : Nat8) : Bool { - (code >= 0x70 and code <= 0x7f) or (code == 0x6f) or (code == 0x68); - }; + let record_key_map = Map.new(); - func extract_compound_types(bytes : Blob, state : [var Nat], total_compound_types : Nat, record_key_map : Map) : [ShallowCandidTypes] { - - func extract_compound_type(i : Nat) : ShallowCandidTypes { - let compound_type_code = read(bytes, state); - - let shallow_type = if (compound_type_code == T.TypeCode.Option) { - let code = peek(bytes, state); - let code_or_ref = if (is_code_primitive_type(code)) { - ignore read(bytes, state); - Nat8.toNat(code); - } else { - let ref_pos = decode_leb128(bytes, state); + var i = 0; + while (i < record_keys.size()) { + let key = formatVariantKey(record_keys[i]); + let hash = hash_record_key(key); + ignore Map.put(record_key_map, n32hash, hash, key); + i += 1; }; - #OptionRef(code_or_ref); - } else if (compound_type_code == T.TypeCode.Array) { - let code = peek(bytes, state); - let code_or_ref = if (is_code_primitive_type(code)) { - ignore read(bytes, state); - Nat8.toNat(code); - } else { - let ref_pos = decode_leb128(bytes, state); - }; + ignore do ? { + let key_pairs_to_rename = options!.renameKeys; - #ArrayRef(code_or_ref); - } else if (compound_type_code == T.TypeCode.Record or compound_type_code == T.TypeCode.Variant) { - let size = decode_leb128(bytes, state); - let fields = Array.tabulate<(Text, Nat)>( - size, - func(i : Nat) : (Text, Nat) { - let hash = decode_leb128(bytes, state) |> Nat32.fromNat(_); - let field_key = switch (Map.get(record_key_map, n32hash, hash)) { - case (?field_key) field_key; - case (null) debug_show hash; - }; + var i = 0; + while (i < key_pairs_to_rename.size()) { + let original_key = formatVariantKey(key_pairs_to_rename[i].0); + let new_key = formatVariantKey(key_pairs_to_rename[i].1); - let code = peek(bytes, state); - let field_code_or_pos = if (is_code_primitive_type(code)) { - ignore read(bytes, state); - Nat8.toNat(code); - } else { - let ref_pos = decode_leb128(bytes, state); - }; + let hash = hash_record_key(original_key); + ignore Map.put(record_key_map, n32hash, hash, new_key); - (field_key, field_code_or_pos); - }, - ); + i += 1; + }; - if (compound_type_code == T.TypeCode.Record) { - #RecordRef(fields); - } else { - #VariantRef(fields); }; - } else { - Debug.trap("extract_compound_types(): expected compound type instead found " # debug_show (compound_type_code)); - }; - shallow_type; + one_shot_decode(blob, record_key_map, Option.get(options, T.defaultOptions)); }; - Array.tabulate(total_compound_types, extract_compound_type); - }; + func read(bytes : Blob, state : [var Nat]) : Nat8 { + let byte = bytes.get(state[C.BYTES_INDEX]); + state[C.BYTES_INDEX] += 1; + byte; + }; - func build_compound_type(compound_types : [ShallowCandidTypes], start_pos : Nat, recursive_types_map : Map) : CandidType { - func _build_compound_type(compound_types : [ShallowCandidTypes], start_pos : Nat, visited : Set, is_recursive_set : Set, recursive_types_map : Map) : CandidType { - var pos = start_pos; + func peek(bytes : Blob, state : [var Nat]) : Nat8 { + bytes.get(state[C.BYTES_INDEX]); + }; - func resolve_field_types((field_key, ref_pos) : (Text, Nat)) : ((Text, CandidType)) { - let visited_size = Set.size(visited); - let resolved_type : CandidType = if (is_code_primitive_type(Nat8.fromNat(ref_pos))) { - code_to_primitive_type(Nat8.fromNat(ref_pos)); - } else { - _build_compound_type(compound_types, ref_pos, visited, is_recursive_set, recursive_types_map); + // Helper function to create an iterator from read function + func createByteIterator(bytes : Blob, state : [var Nat]) : Iter.Iter { + object { + public func next() : ?Nat8 { + let i = state[C.BYTES_INDEX]; + if (i < bytes.size()) { + let byte = bytes[i]; + state[C.BYTES_INDEX] += 1; + ?byte; + } else { + null; + }; + }; }; + }; - while (Set.size(visited) > visited_size) { - ignore Set.pop(visited, nhash); + // Iterator-based helper functions + func read_from_iter(iter : Iter.Iter) : Nat8 { + switch (iter.next()) { + case (?byte) byte; + case (null) Debug.trap("Unexpected end of data stream"); }; + }; - (field_key, resolved_type); - }; - - switch (Map.get(recursive_types_map, nhash, pos)) { - case (?candid_type) return candid_type; - case (null) {}; - }; - - if (Set.has(visited, nhash, pos) and not Set.has(is_recursive_set, nhash, pos)) { - ignore Set.put(is_recursive_set, nhash, pos); - return #Recursive(pos); - }; + func decode_leb128_from_iter(iter : Iter.Iter) : Nat { + var n64 : Nat64 = 0; + var shift : Nat64 = 0; - ignore Set.put(visited, nhash, pos); + label decoding_leb loop { + let byte = read_from_iter(iter); - let resolved_compound_type = switch (compound_types.get(pos)) { - case (#OptionRef(ref_pos)) { - let ref_type = if (is_code_primitive_type(Nat8.fromNat(ref_pos))) { - code_to_primitive_type(Nat8.fromNat(ref_pos)); - } else { - _build_compound_type(compound_types, ref_pos, visited, is_recursive_set, recursive_types_map); - }; + n64 |= (Nat64.fromNat(Nat8.toNat(byte & 0x7f)) << shift); - #Option(ref_type); + if (byte & 0x80 == 0) break decoding_leb; + shift += 7; }; - case (#ArrayRef(ref_pos)) { - let ref_type = if (is_code_primitive_type(Nat8.fromNat(ref_pos))) { - code_to_primitive_type(Nat8.fromNat(ref_pos)); - } else { - _build_compound_type(compound_types, ref_pos, visited, is_recursive_set, recursive_types_map); - }; - #Array(ref_type); - }; - case (#RecordRef(fields)) { - let resolved_fields = Array.map<(Text, Nat), (Text, CandidType)>(fields, resolve_field_types); - #Record(resolved_fields); - }; - case (#VariantRef(fields)) { - let resolved_fields = Array.map<(Text, Nat), (Text, CandidType)>(fields, resolve_field_types); - #Variant(resolved_fields); - }; - }; - - if (Set.has(is_recursive_set, nhash, pos) and not Map.has(recursive_types_map, nhash, pos)) { - ignore Map.put(recursive_types_map, nhash, pos, resolved_compound_type); - }; - resolved_compound_type; + Nat64.toNat(n64); }; - let visited = Set.new(); - let is_recursive_set = Set.new(); + func decode_signed_leb_64_from_iter(iter : Iter.Iter) : Int { + var result : Nat64 = 0; + var shift : Nat64 = 0; + var byte : Nat8 = 0; + var i = 0; - _build_compound_type(compound_types, start_pos, visited, is_recursive_set, recursive_types_map); - }; + label analyzing loop { + byte := read_from_iter(iter); + i += 1; - func build_types(bytes : Blob, state : [var Nat], compound_types : [ShallowCandidTypes], recursive_types_map : Map) : [CandidType] { - let total_candid_types = decode_leb128(bytes, state); + // Add this byte's 7 bits to the result + result |= Nat64.fromNat(Nat8.toNat(byte & 0x7F)) << shift; + shift += 7; - let candid_types = Array.tabulate( - total_candid_types, - func(i : Nat) : CandidType { - let code = peek(bytes, state); - if (is_code_primitive_type(code)) { - ignore read(bytes, state); - let primitive_type = code_to_primitive_type(code); - primitive_type; - } else { - let start_pos = decode_leb128(bytes, state); - let compound_type = build_compound_type(compound_types, start_pos, recursive_types_map); - compound_type; + // If continuation bit is not set, we're done reading bytes + if ((byte & 0x80) == 0) { + break analyzing; + }; }; - }, - ); - candid_types; - }; + // Sign extend if this is a negative number + if (byte & 0x40 != 0 and shift < 64) { + // Fill the rest with 1s (sign extension) + result |= ^((Nat64.fromNat(1) << shift) - 1); + }; - func skip_compound_types(bytes : Blob, state : [var Nat], total_compound_types : Nat) { - var i = 0; - while (i < total_compound_types) { - let compound_type_code = read(bytes, state); + Int64.toInt(Int64.fromNat64(result)); + }; - if (compound_type_code == T.TypeCode.Option) { - let code = peek(bytes, state); - if (is_code_primitive_type(code)) { - ignore read(bytes, state); // advance past code - } else { - ignore decode_leb128(bytes, state); // start_pos - }; - } else if (compound_type_code == T.TypeCode.Array) { - let code = peek(bytes, state); - if (is_code_primitive_type(code)) { - ignore read(bytes, state); // advance past code + func code_to_primitive_type(code : Nat8) : CandidType { + if (code == T.TypeCode.Null) { + #Null; + } else if (code == T.TypeCode.Bool) { + #Bool; + } else if (code == T.TypeCode.Nat) { + #Nat; + } else if (code == T.TypeCode.Nat8) { + #Nat8; + } else if (code == T.TypeCode.Nat16) { + #Nat16; + } else if (code == T.TypeCode.Nat32) { + #Nat32; + } else if (code == T.TypeCode.Nat64) { + #Nat64; + } else if (code == T.TypeCode.Int) { + #Int; + } else if (code == T.TypeCode.Int8) { + #Int8; + } else if (code == T.TypeCode.Int16) { + #Int16; + } else if (code == T.TypeCode.Int32) { + #Int32; + } else if (code == T.TypeCode.Int64) { + #Int64; + } else if (code == T.TypeCode.Float) { + #Float; + } else if (code == T.TypeCode.Text) { + #Text; + } else if (code == T.TypeCode.Principal) { + #Principal; + } else if (code == T.TypeCode.Empty) { + #Empty; } else { - ignore decode_leb128(bytes, state); // start_pos + Debug.trap("code [" # debug_show code # "] does not belong to a primitive type"); }; - } else if (compound_type_code == T.TypeCode.Record or compound_type_code == T.TypeCode.Variant) { - let size = decode_leb128(bytes, state); - var i = 0; + }; - while (i < size) { - ignore decode_leb128(bytes, state); // hash + func is_code_primitive_type(code : Nat8) : Bool { + (code >= 0x70 and code <= 0x7f) or (code == 0x6f) or (code == 0x68); + }; - let code = peek(bytes, state); - if (is_code_primitive_type(code)) { - ignore read(bytes, state); // advance past code - } else { - ignore decode_leb128(bytes, state); // start_pos - }; + public func extract_compound_types(bytes : Blob, state : [var Nat], total_compound_types : Nat, record_key_map : Map) : [ShallowCandidTypes] { + + func extract_compound_type(i : Nat) : ShallowCandidTypes { + let compound_type_code = read(bytes, state); + + let shallow_type = if (compound_type_code == T.TypeCode.Option) { + let code = peek(bytes, state); + let code_or_ref = if (is_code_primitive_type(code)) { + ignore read(bytes, state); + Nat8.toNat(code); + } else { + let ref_pos = decode_leb128(bytes, state); + }; + + #OptionRef(code_or_ref); + } else if (compound_type_code == T.TypeCode.Array) { + let code = peek(bytes, state); + let code_or_ref = if (is_code_primitive_type(code)) { + ignore read(bytes, state); + Nat8.toNat(code); + } else { + let ref_pos = decode_leb128(bytes, state); + }; + + #ArrayRef(code_or_ref); + } else if (compound_type_code == T.TypeCode.Record or compound_type_code == T.TypeCode.Variant) { + let size = decode_leb128(bytes, state); + let fields = Array.tabulate<(Text, Nat)>( + size, + func(i : Nat) : (Text, Nat) { + let hash = decode_leb128(bytes, state) |> Nat32.fromNat(_); + let field_key = switch (Map.get(record_key_map, n32hash, hash)) { + case (?field_key) field_key; + case (null) debug_show hash; + }; + + let code = peek(bytes, state); + let field_code_or_pos = if (is_code_primitive_type(code)) { + ignore read(bytes, state); + Nat8.toNat(code); + } else { + let ref_pos = decode_leb128(bytes, state); + }; + + (field_key, field_code_or_pos); + }, + ); + + if (compound_type_code == T.TypeCode.Record) { + #RecordRef(fields); + } else { + #VariantRef(fields); + }; + } else { + Debug.trap("extract_compound_types(): expected compound type instead found " # debug_show (compound_type_code)); + }; - i += 1; + shallow_type; }; - } else { - Debug.trap("code [" # debug_show compound_type_code # "] does not belong to a compound type"); - }; - i += 1; + Array.tabulate(total_compound_types, extract_compound_type); }; - }; - - func skip_types(bytes : Blob, state : [var Nat]) { - let total_candid_types = decode_leb128(bytes, state); + func build_compound_type(compound_types : [ShallowCandidTypes], start_pos : Nat, recursive_types_map : Map) : CandidType { + func _build_compound_type(compound_types : [ShallowCandidTypes], start_pos : Nat, visited : Set, is_recursive_set : Set, recursive_types_map : Map) : CandidType { + var pos = start_pos; - var i = 0; + func resolve_field_types((field_key, ref_pos) : (Text, Nat)) : ((Text, CandidType)) { + let visited_size = Set.size(visited); + let resolved_type : CandidType = if (is_code_primitive_type(Nat8.fromNat(ref_pos))) { + code_to_primitive_type(Nat8.fromNat(ref_pos)); + } else { + _build_compound_type(compound_types, ref_pos, visited, is_recursive_set, recursive_types_map); + }; - while (i < total_candid_types) { - let code = peek(bytes, state); - if (is_code_primitive_type(code)) { - ignore read(bytes, state); // advance past code - } else { - ignore decode_leb128(bytes, state); // start_pos - }; + while (Set.size(visited) > visited_size) { + ignore Set.pop(visited, nhash); + }; - i += 1; - }; - }; + (field_key, resolved_type); + }; - public func one_shot_decode(candid_blob : Blob, record_key_map : Map, options : T.Options) : Result<[Candid], Text> { - let bytes = candid_blob; - // let stream = BitBuffer.fromArray(bytes); + switch (Map.get(recursive_types_map, nhash, pos)) { + case (?candid_type) return candid_type; + case (null) {}; + }; - let is_types_set = Option.isSome(options.types); + if (Set.has(visited, nhash, pos) and not Set.has(is_recursive_set, nhash, pos)) { + ignore Set.put(is_recursive_set, nhash, pos); + return #Recursive(pos); + }; - if (options.blob_contains_only_values and not is_types_set) { - return #err("if 'options.blob_contains_only_values' is set, you need to also pass in the types"); - }; + ignore Set.put(visited, nhash, pos); + + let resolved_compound_type = switch (compound_types.get(pos)) { + case (#OptionRef(ref_pos)) { + let ref_type = if (is_code_primitive_type(Nat8.fromNat(ref_pos))) { + code_to_primitive_type(Nat8.fromNat(ref_pos)); + } else { + _build_compound_type(compound_types, ref_pos, visited, is_recursive_set, recursive_types_map); + }; + + #Option(ref_type); + }; + case (#ArrayRef(ref_pos)) { + let ref_type = if (is_code_primitive_type(Nat8.fromNat(ref_pos))) { + code_to_primitive_type(Nat8.fromNat(ref_pos)); + } else { + _build_compound_type(compound_types, ref_pos, visited, is_recursive_set, recursive_types_map); + }; + #Array(ref_type); + }; + case (#RecordRef(fields)) { + let resolved_fields = Array.map<(Text, Nat), (Text, CandidType)>(fields, resolve_field_types); + #Record(resolved_fields); + }; + case (#VariantRef(fields)) { + let resolved_fields = Array.map<(Text, Nat), (Text, CandidType)>(fields, resolve_field_types); + #Variant(resolved_fields); + }; + }; - var candid_types = Option.get(options.types, []); + if (Set.has(is_recursive_set, nhash, pos) and not Map.has(recursive_types_map, nhash, pos)) { + ignore Map.put(recursive_types_map, nhash, pos, resolved_compound_type); + }; - let state : [var Nat] = [var 0]; + resolved_compound_type; + }; - let magic = Array.tabulate(4, func(i : Nat) : Nat8 = read(bytes, state)); + let visited = Set.new(); + let is_recursive_set = Set.new(); - if (magic != [0x44, 0x49, 0x44, 0x4c]) { - return #err("Invalid Magic Number"); + _build_compound_type(compound_types, start_pos, visited, is_recursive_set, recursive_types_map); }; - let recursive_types_map = Map.new(); - - if (not is_types_set) { - // extract types from blob - let total_compound_types = decode_leb128(bytes, state); - let compound_types = extract_compound_types(bytes, state, total_compound_types, record_key_map); - candid_types := build_types(bytes, state, compound_types, recursive_types_map); - - } else if (not options.blob_contains_only_values) { - // types are set but 'blob_contains_only_values' is not set, - // then skip type section and locate start of values section - let total_compound_types = decode_leb128(bytes, state); - skip_compound_types(bytes, state, total_compound_types); - skip_types(bytes, state); + public func build_types(bytes : Blob, state : [var Nat], compound_types : [ShallowCandidTypes], recursive_types_map : Map) : [CandidType] { + let total_candid_types = decode_leb128(bytes, state); + + let candid_types = Array.tabulate( + total_candid_types, + func(i : Nat) : CandidType { + let code = peek(bytes, state); + let candid_type = if (is_code_primitive_type(code)) { + ignore read(bytes, state); + let primitive_type = code_to_primitive_type(code); + primitive_type; + } else { + let start_pos = decode_leb128(bytes, state); + let compound_type = build_compound_type(compound_types, start_pos, recursive_types_map); + compound_type; + }; + + CandidUtils.sort_candid_type(candid_type); + + }, + ); + (candid_types); }; - // extract values with Candid variant Types - decode_candid_values(bytes, candid_types, state, options, recursive_types_map); - }; - - let C = { - BYTES_INDEX = 0; - }; - - // https://en.wikipedia.org/wiki/LEB128 - // func decode_leb128(bytes : Blob, state : [var Nat]) : Nat { - // var n64 : Nat64 = 0; - // var shift : Nat64 = 0; - - // var num : Nat = 0; + public func skip_compound_types(bytes : Blob, state : [var Nat], total_compound_types : Nat) { + var i = 0; + while (i < total_compound_types) { + let compound_type_code = read(bytes, state); + + if (compound_type_code == T.TypeCode.Option) { + let code = peek(bytes, state); + if (is_code_primitive_type(code)) { + ignore read(bytes, state); // advance past code + } else { + ignore decode_leb128(bytes, state); // start_pos + }; + } else if (compound_type_code == T.TypeCode.Array) { + let code = peek(bytes, state); + if (is_code_primitive_type(code)) { + ignore read(bytes, state); // advance past code + } else { + ignore decode_leb128(bytes, state); // start_pos + }; + } else if (compound_type_code == T.TypeCode.Record or compound_type_code == T.TypeCode.Variant) { + let size = decode_leb128(bytes, state); + var i = 0; + + while (i < size) { + ignore decode_leb128(bytes, state); // hash + + let code = peek(bytes, state); + if (is_code_primitive_type(code)) { + ignore read(bytes, state); // advance past code + } else { + ignore decode_leb128(bytes, state); // start_pos + }; + + i += 1; + }; + } else { + Debug.trap("code [" # debug_show compound_type_code # "] does not belong to a compound type"); + }; - // label decoding_leb loop { - // let byte = read(bytes, state); + i += 1; + }; + }; - // n64 |= (Nat64.fromNat(Nat8.toNat(byte & 0x7f)) << shift); + public func skip_types(bytes : Blob, state : [var Nat]) { - // shift += 7; + let total_candid_types = decode_leb128(bytes, state); - // if (shift % 63 == 0) { - // if (num != 0) num := num * (2 ** 63); - // num += Nat64.toNat(n64); + var i = 0; - // // n64 := 0; - // }; + while (i < total_candid_types) { + let code = peek(bytes, state); + if (is_code_primitive_type(code)) { + ignore read(bytes, state); // advance past code + } else { + ignore decode_leb128(bytes, state); // start_pos + }; - // if (byte & 0x80 == 0) break decoding_leb; + i += 1; + }; + }; - // }; + public func one_shot_decode(candid_blob : Blob, record_key_map : Map, options : T.Options) : Result<[Candid], Text> { + let bytes = candid_blob; + // let stream = BitBuffer.fromArray(bytes); - // let num2 = Nat64.toNat(n64); - // // Debug.print("(num, num2): " # debug_show (num, num2)); - // num2; + let is_types_set = Option.isSome(options.types); - // }; - let nat64_bound = 18_446_744_073_709_551_616; - // func decode_leb128(bytes : Blob, state : [var Nat]) : Nat { - // var n64 : Nat64 = 0; - // var shift : Nat64 = 0; - // var shifted : Nat = 0; + if (options.blob_contains_only_values and not is_types_set) { + return #err("if 'options.blob_contains_only_values' is set, you need to also pass in the types"); + }; - // var num : Nat = 0; + var candid_types = Option.get(options.types, []); - // label decoding_leb loop { - // let byte = read(bytes, state); + let state : [var Nat] = [var 0]; - // n64 |= (Nat64.fromNat(Nat8.toNat(byte & 0x7f)) << shift); + let magic = Array.tabulate(4, func(i : Nat) : Nat8 = read(bytes, state)); - // shift += 7; + if (magic != [0x44, 0x49, 0x44, 0x4c]) { + return #err("Invalid Magic Number"); + }; - // if (shift % 63 == 0) { - // // Debug.print("shift: " # debug_show (shift)); - // num += Nat64.toNat(n64) * (2 ** shifted); + let recursive_types_map = Map.new(); - // shifted += Nat64.toNat(shift); - // shift := 0; + if (not is_types_set) { + // extract types from blob + let total_compound_types = decode_leb128(bytes, state); + let compound_types = extract_compound_types(bytes, state, total_compound_types, record_key_map); + candid_types := build_types(bytes, state, compound_types, recursive_types_map); - // n64 := 0; - // }; + } else if (not options.blob_contains_only_values) { + // types are set but 'blob_contains_only_values' is not set, + // then skip type section and locate start of values section + let total_compound_types = decode_leb128(bytes, state); + skip_compound_types(bytes, state, total_compound_types); + skip_types(bytes, state); - // if (byte & 0x80 == 0) { - // if (num > nat64_bound) { - // num += Nat64.toNat(n64) * (2 ** shifted); - // } else { - // num := Nat64.toNat(n64); - // }; + }; - // break decoding_leb; + // extract values with Candid variant Types + decode_candid_values(bytes, candid_types, state, options, recursive_types_map); + }; - // }; + let C = { + BYTES_INDEX = 0; + }; - // }; + // https://en.wikipedia.org/wiki/LEB128 + // func decode_leb128(bytes : Blob, state : [var Nat]) : Nat { + // var n64 : Nat64 = 0; + // var shift : Nat64 = 0; - // // let num2 = Nat64.toNat(n64); - // // Debug.print("(num, num2): " # debug_show (num, num2)); - // num; + // var num : Nat = 0; - // }; + // label decoding_leb loop { + // let byte = read(bytes, state); - // https://en.wikipedia.org/wiki/LEB128 - func decode_leb128(bytes : Blob, state : [var Nat]) : Nat { - var n64 : Nat64 = 0; - var shift : Nat64 = 0; + // n64 |= (Nat64.fromNat(Nat8.toNat(byte & 0x7f)) << shift); - label decoding_leb loop { - let byte = read(bytes, state); + // shift += 7; - n64 |= (Nat64.fromNat(Nat8.toNat(byte & 0x7f)) << shift); + // if (shift % 63 == 0) { + // if (num != 0) num := num * (2 ** 63); + // num += Nat64.toNat(n64); - if (byte & 0x80 == 0) break decoding_leb; - shift += 7; - }; + // // n64 := 0; + // }; - Nat64.toNat(n64); - }; + // if (byte & 0x80 == 0) break decoding_leb; - func decode_signed_leb_64(bytes : Blob, state : [var Nat]) : Int { - var result : Nat64 = 0; - var shift : Nat64 = 0; - var byte : Nat8 = 0; - var i = 0; + // }; - label analyzing loop { - byte := read(bytes, state); - i += 1; + // let num2 = Nat64.toNat(n64); + // // Debug.print("(num, num2): " # debug_show (num, num2)); + // num2; - // Add this byte's 7 bits to the result - result |= Nat64.fromNat(Nat8.toNat(byte & 0x7F)) << shift; - shift += 7; + // }; + let nat64_bound = 18_446_744_073_709_551_616; + // func decode_leb128(bytes : Blob, state : [var Nat]) : Nat { + // var n64 : Nat64 = 0; + // var shift : Nat64 = 0; + // var shifted : Nat = 0; - // If continuation bit is not set, we're done reading bytes - if ((byte & 0x80) == 0) { - break analyzing; - }; - }; + // var num : Nat = 0; - // Sign extend if this is a negative number - if (byte & 0x40 != 0 and shift < 64) { - // Fill the rest with 1s (sign extension) - result |= ^((Nat64.fromNat(1) << shift) - 1); - }; + // label decoding_leb loop { + // let byte = read(bytes, state); - Int64.toInt(Int64.fromNat64(result)); - }; + // n64 |= (Nat64.fromNat(Nat8.toNat(byte & 0x7f)) << shift); - func decode_candid_values(bytes : Blob, candid_types : [CandidType], state : [var Nat], options : T.Options, recursive_map : Map) : Result<[Candid], Text> { - var error : ?Text = null; + // shift += 7; - let candid_values = Array.tabulate( - candid_types.size(), - func(i : Nat) : Candid { - switch (decode_value(bytes, state, options, recursive_map, candid_types[i])) { - case (#ok(candid_value)) candid_value; - case (#err(msg)) { - error := ?msg; - #Empty; - }; - }; - }, - ); + // if (shift % 63 == 0) { + // // Debug.print("shift: " # debug_show (shift)); + // num += Nat64.toNat(n64) * (2 ** shifted); - switch (error) { - case (?msg) return #err(msg); - case (null) {}; - }; + // shifted += Nat64.toNat(shift); + // shift := 0; - #ok(candid_values); - }; - - func decode_value(bytes : Blob, state : [var Nat], options : T.Options, recursive_map : Map, candid_type : CandidType) : Result { - // Debug.print("Decoding candid type: " # debug_show (candid_type) # " at index: " # debug_show (state[C.BYTES_INDEX])); - - let value : Candid = switch (candid_type) { - case (#Nat) #Nat(decode_leb128(bytes, state)); - case (#Nat8) #Nat8(read(bytes, state)); - case (#Nat16) { - let n = Nat8.toNat16(read(bytes, state)) | Nat8.toNat16(read(bytes, state)) << 8; - - #Nat16(n); - }; - case (#Nat32) { - let n = Nat32.fromNat(Nat8.toNat(read(bytes, state))) | Nat32.fromNat(Nat8.toNat(read(bytes, state))) << 8 | Nat32.fromNat(Nat8.toNat(read(bytes, state))) << 16 | Nat32.fromNat(Nat8.toNat(read(bytes, state))) << 24; - - #Nat32(n); - }; - case (#Nat64) { - let n = Nat64.fromNat(Nat8.toNat(read(bytes, state))) | Nat64.fromNat(Nat8.toNat(read(bytes, state))) << 8 | Nat64.fromNat(Nat8.toNat(read(bytes, state))) << 16 | Nat64.fromNat(Nat8.toNat(read(bytes, state))) << 24 | Nat64.fromNat(Nat8.toNat(read(bytes, state))) << 32 | Nat64.fromNat(Nat8.toNat(read(bytes, state))) << 40 | Nat64.fromNat(Nat8.toNat(read(bytes, state))) << 48 | Nat64.fromNat(Nat8.toNat(read(bytes, state))) << 56; - - #Nat64(n); - }; - - case (#Int) #Int(decode_signed_leb_64(bytes, state)); - case (#Int8) #Int8(Int8.fromNat8(read(bytes, state))); - case (#Int16) { - let n = Nat8.toNat16(read(bytes, state)) | Nat8.toNat16(read(bytes, state)) << 8; - - #Int16(Int16.fromNat16(n)); - }; - case (#Int32) { - let n = Nat32.fromNat(Nat8.toNat(read(bytes, state))) | Nat32.fromNat(Nat8.toNat(read(bytes, state))) << 8 | Nat32.fromNat(Nat8.toNat(read(bytes, state))) << 16 | Nat32.fromNat(Nat8.toNat(read(bytes, state))) << 24; - - #Int32(Int32.fromNat32(n)); - }; - case (#Int64) { - let n = Nat64.fromNat(Nat8.toNat(read(bytes, state))) | Nat64.fromNat(Nat8.toNat(read(bytes, state))) << 8 | Nat64.fromNat(Nat8.toNat(read(bytes, state))) << 16 | Nat64.fromNat(Nat8.toNat(read(bytes, state))) << 24 | Nat64.fromNat(Nat8.toNat(read(bytes, state))) << 32 | Nat64.fromNat(Nat8.toNat(read(bytes, state))) << 40 | Nat64.fromNat(Nat8.toNat(read(bytes, state))) << 48 | Nat64.fromNat(Nat8.toNat(read(bytes, state))) << 56; - - #Int64(Int64.fromNat64(n)); - }; - case (#Float) { - let bytes_iter = object { - public func next() : ?Nat8 { - let i = state[C.BYTES_INDEX]; - - if (i < bytes.size()) { - let byte = bytes[i]; - state[C.BYTES_INDEX] += 1; - ?(byte); - } else { - null; - }; - }; - }; + // n64 := 0; + // }; - let float_details = switch (FloatX.decode(bytes_iter, #f64, #lsb)) { - case (?f) f; - case (null) return #err("Could not decode float sequence"); - }; + // if (byte & 0x80 == 0) { + // if (num > nat64_bound) { + // num += Nat64.toNat(n64) * (2 ** shifted); + // } else { + // num := Nat64.toNat(n64); + // }; - let n = FloatX.toFloat(float_details); - #Float(n); - }; - - case (#Bool) { - let byte = read(bytes, state); - let b = if (byte == 0) false else true; - #Bool(b); - }; - case (#Null) #Null; - case (#Empty) #Empty; - case (#Text) { - let size = decode_leb128(bytes, state); - let text_bytes = Array.tabulate( - size, - func(i : Nat) : Nat8 { - read(bytes, state); - }, - ); + // break decoding_leb; - let blob = Blob.fromArray(text_bytes); - let text = switch (Text.decodeUtf8(blob)) { - case (?t) t; - case (null) return #err("Failed to decode utf8 text"); - }; + // }; - #Text(text); - }; - case (#Principal) { - assert read(bytes, state) == 0x01; // transparency state. opaque not supported yet. - let size = decode_leb128(bytes, state); - - let principal_bytes = Array.tabulate( - size, - func(i : Nat) : Nat8 { - read(bytes, state); - }, - ); + // }; - let blob = Blob.fromArray(principal_bytes); - let p = Principal.fromBlob(blob); + // // let num2 = Nat64.toNat(n64); + // // Debug.print("(num, num2): " # debug_show (num, num2)); + // num; - #Principal(p); - }; + // }; - // ====================== Compound Types ======================= + // https://en.wikipedia.org/wiki/LEB128 + func decode_leb128(bytes : Blob, state : [var Nat]) : Nat { + var n64 : Nat64 = 0; + var shift : Nat64 = 0; - case (#Option(opt_type)) { - let is_null = read(bytes, state) == 0; + label decoding_leb loop { + let byte = read(bytes, state); - if (is_null) return #ok(#Null); + n64 |= (Nat64.fromNat(Nat8.toNat(byte & 0x7f)) << shift); - let nested_value = switch (decode_value(bytes, state, options, recursive_map, opt_type)) { - case (#ok(value)) value; - case (#err(err_msg)) return #err(err_msg); + if (byte & 0x80 == 0) break decoding_leb; + shift += 7; }; - #Option(nested_value); - - }; + Nat64.toNat(n64); + }; - case (#Array(#Nat8)) { - let size = decode_leb128(bytes, state); + public func decode_candid_values(bytes : Blob, candid_types : [CandidType], state : [var Nat], options : T.Options, recursive_map : Map) : Result<[Candid], Text> { var error : ?Text = null; - let values = Array.tabulate( - size, - func(i : Nat) : Nat8 { - switch (decode_value(bytes, state, options, recursive_map, #Nat8)) { - case (#ok(#Nat8(value))) value; - case (#ok(unexpected_type)) { - error := ?("Expected #Nat8 value in blob type, instead got " # debug_show (unexpected_type)); - 0; - }; - case (#err(err_msg)) { - error := ?err_msg; - 0; - }; - }; - }, + let candid_values = Array.tabulate( + candid_types.size(), + func(i : Nat) : Candid { + switch (decode_value(bytes, state, options, recursive_map, candid_types[i])) { + case (#ok(candid_value)) candid_value; + case (#err(msg)) { + error := ?msg; + #Empty; + }; + }; + }, ); switch (error) { - case (?msg) return #err(msg); - case (null) {}; + case (?msg) return #err(msg); + case (null) {}; }; - let blob = Blob.fromArray(values); + #ok(candid_values); + }; - #Blob(blob); - }; + func decode_value(bytes : Blob, state : [var Nat], options : T.Options, recursive_map : Map, candid_type : CandidType) : Result { + let iter = createByteIterator(bytes, state); + decode_value_from_iter(iter, options, recursive_map, candid_type); + }; - case (#Array(arr_type)) { - let size = decode_leb128(bytes, state); - var error : ?Text = null; - let values = Array.tabulate( - size, - func(i : Nat) : Candid { - switch (decode_value(bytes, state, options, recursive_map, arr_type)) { - case (#ok(value)) value; - case (#err(err_msg)) { - error := ?err_msg; - #Empty; - }; + func decode_value_from_iter(iter : Iter.Iter, options : T.Options, recursive_map : Map, candid_type : CandidType) : Result { + // Debug.print("Decoding candid type: " # debug_show (candid_type)); + + let value : Candid = switch (candid_type) { + case (#Nat) #Nat(decode_leb128_from_iter(iter)); + case (#Nat8) #Nat8(read_from_iter(iter)); + case (#Nat16) #Nat16(ByteUtils.LE.toNat16(iter)); + case (#Nat32) #Nat32(ByteUtils.LE.toNat32(iter)); + case (#Nat64) #Nat64(ByteUtils.LE.toNat64(iter)); + + case (#Int) #Int(decode_signed_leb_64_from_iter(iter)); + case (#Int8) #Int8(Int8.fromNat8(read_from_iter(iter))); + case (#Int16) #Int16(ByteUtils.LE.toInt16(iter)); + case (#Int32) #Int32(ByteUtils.LE.toInt32(iter)); + case (#Int64) #Int64(ByteUtils.LE.toInt64(iter)); + case (#Float) #Float(ByteUtils.LE.toFloat(iter)); + + case (#Bool) { + let byte = read_from_iter(iter); + let b = if (byte == 0) false else true; + #Bool(b); }; - }, - ); + case (#Null) #Null; + case (#Empty) #Empty; + case (#Text) { + let size = decode_leb128_from_iter(iter); + let text_bytes = Array.tabulate( + size, + func(_ : Nat) : Nat8 { + read_from_iter(iter); + }, + ); + + let blob = Blob.fromArray(text_bytes); + let text = switch (Text.decodeUtf8(blob)) { + case (?t) t; + case (null) return #err("Failed to decode utf8 text"); + }; + + #Text(text); + }; + case (#Principal) { + assert read_from_iter(iter) == 0x01; // transparency state. opaque not supported yet. + let size = decode_leb128_from_iter(iter); - switch (error) { - case (?msg) return #err(msg); - case (null) {}; - }; + let principal_bytes = Array.tabulate( + size, + func(_ : Nat) : Nat8 { + read_from_iter(iter); + }, + ); - #Array(values); + let blob = Blob.fromArray(principal_bytes); + let p = Principal.fromBlob(blob); - }; + #Principal(p); + }; - case (#Record(record_types)) { - var error : ?Text = null; + // ====================== Compound Types ======================= - var is_tuple = true; - let n = record_types.size(); - var sum_of_n : Int = n; + case (#Option(opt_type)) { + let is_null = read_from_iter(iter) == 0; - let record_entries = Array.tabulate<(Text, Candid)>( - record_types.size(), - func(i : Nat) : (Text, Candid) { - let record_key = record_types[i].0; - if (Utils.text_is_number(record_key)) { - sum_of_n -= Utils.text_to_nat(record_key); - } else { - is_tuple := false; - }; + if (is_null) return #ok(#Null); - let record_type = record_types[i].1; + let nested_value = switch (decode_value_from_iter(iter, options, recursive_map, opt_type)) { + case (#ok(value)) value; + case (#err(err_msg)) return #err(err_msg); + }; - let value = switch (decode_value(bytes, state, options, recursive_map, record_type)) { - case (#ok(value)) value; - case (#err(msg)) { - error := ?(msg); - #Empty; - }; - }; + #Option(nested_value); - (record_key, value); - }, - ); + }; - is_tuple := is_tuple and sum_of_n == 0; + case (#Array(#Nat8)) { + let size = decode_leb128_from_iter(iter); + var error : ?Text = null; + + let values = Array.tabulate( + size, + func(_ : Nat) : Nat8 { + switch (decode_value_from_iter(iter, options, recursive_map, #Nat8)) { + case (#ok(#Nat8(value))) value; + case (#ok(unexpected_type)) { + error := ?("Expected #Nat8 value in blob type, instead got " # debug_show (unexpected_type)); + 0; + }; + case (#err(err_msg)) { + error := ?err_msg; + 0; + }; + }; + }, + ); + + switch (error) { + case (?msg) return #err(msg); + case (null) {}; + }; + + let blob = Blob.fromArray(values); + + #Blob(blob); + }; + case (#Blob(_)) { + return decode_value_from_iter(iter, options, recursive_map, #Array(#Nat8)); + }; - switch (error) { - case (?msg) return #err(msg); - case (null) {}; - }; + case (#Array(arr_type)) { + let size = decode_leb128_from_iter(iter); + var error : ?Text = null; + let values = Array.tabulate( + size, + func(_ : Nat) : Candid { + switch (decode_value_from_iter(iter, options, recursive_map, arr_type)) { + case (#ok(value)) value; + case (#err(err_msg)) { + error := ?err_msg; + #Empty; + }; + }; + }, + ); + + switch (error) { + case (?msg) return #err(msg); + case (null) {}; + }; + + #Array(values); - if (options.use_icrc_3_value_type) { - #Map(record_entries); - } else if (is_tuple and record_entries.size() > 0) { - #Tuple(Array.map<(Text, Candid), Candid>(record_entries, func((_, v) : (Any, Candid)) : Candid = v)); - } else { - #Record(record_entries); - }; - }; - case (#Tuple(tuple_types)) return decode_value( - bytes, - state, - options, - recursive_map, - #Record(Array.tabulate<(Text, CandidType)>(tuple_types.size(), func(i : Nat) : (Text, CandidType) = (debug_show (i), tuple_types[i]))), - ); - case (#Variant(variant_types)) { - let variant_index = decode_leb128(bytes, state); - - let variant_key = variant_types[variant_index].0; - let variant_type = variant_types[variant_index].1; + }; - var error : ?Text = null; + case (#Record(record_types)) { + var error : ?Text = null; + + var is_tuple = true; + let n = record_types.size(); + var sum_of_n : Int = n; + + let record_entries = Array.tabulate<(Text, Candid)>( + record_types.size(), + func(i : Nat) : (Text, Candid) { + let record_key = record_types[i].0; + if (Utils.text_is_number(record_key)) { + sum_of_n -= Utils.text_to_nat(record_key); + } else { + is_tuple := false; + }; + + let record_type = record_types[i].1; + + let value = switch (decode_value_from_iter(iter, options, recursive_map, record_type)) { + case (#ok(value)) value; + case (#err(msg)) { + error := ?(msg); + #Empty; + }; + }; + + (record_key, value); + }, + ); + + is_tuple := is_tuple and sum_of_n == 0; + + switch (error) { + case (?msg) return #err(msg); + case (null) {}; + }; + + if (options.use_icrc_3_value_type) { + #Map(record_entries); + } else if (is_tuple and record_entries.size() > 0) { + #Tuple(Array.map<(Text, Candid), Candid>(record_entries, func((_, v) : (Any, Candid)) : Candid = v)); + } else { + #Record(record_entries); + }; + }; + case (#Tuple(tuple_types)) return decode_value_from_iter( + iter, + options, + recursive_map, + #Record(Array.tabulate<(Text, CandidType)>(tuple_types.size(), func(i : Nat) : (Text, CandidType) = (debug_show (i), tuple_types[i]))), + ); + case (#Variant(variant_types)) { + let variant_index = decode_leb128_from_iter(iter); + + let variant_key = variant_types[variant_index].0; + let variant_type = variant_types[variant_index].1; + + var error : ?Text = null; + + let value = switch (decode_value_from_iter(iter, options, recursive_map, variant_type)) { + case (#ok(value)) value; + case (#err(msg)) { + error := ?(msg); + #Empty; + }; + }; + + #Variant(variant_key, value); + }; + case (#Recursive(pos)) { + let recursive_type = switch (Map.get(recursive_map, nhash, pos)) { + case (?recursive_type) recursive_type; + case (_) Debug.trap("Recursive type not found"); + }; - let value = switch (decode_value(bytes, state, options, recursive_map, variant_type)) { - case (#ok(value)) value; - case (#err(msg)) { - error := ?(msg); - #Empty; - }; - }; + return decode_value_from_iter(iter, options, recursive_map, recursive_type); + }; - #Variant(variant_key, value); - }; - case (#Recursive(pos)) { - let recursive_type = switch (Map.get(recursive_map, nhash, pos)) { - case (?recursive_type) recursive_type; - case (_) Debug.trap("Recursive type not found"); + case (val) Debug.trap(debug_show (val) # " decoding is not supported"); }; - return decode_value(bytes, state, options, recursive_map, recursive_type); - }; - - case (val) Debug.trap(debug_show (val) # " decoding is not supported"); + #ok(value); }; - #ok(value); - }; - - func formatVariantKey(key : Text) : Text { - let opt = Text.stripStart(key, #text("#")); - switch (opt) { - case (?stripped_text) stripped_text; - case (null) key; + func formatVariantKey(key : Text) : Text { + let opt = Text.stripStart(key, #text("#")); + switch (opt) { + case (?stripped_text) stripped_text; + case (null) key; + }; }; - }; }; diff --git a/src/Candid/Blob/Encoder.mo b/src/Candid/Blob/Encoder.mo index dd50d1e..54f2749 100644 --- a/src/Candid/Blob/Encoder.mo +++ b/src/Candid/Blob/Encoder.mo @@ -30,7 +30,9 @@ import Tag "mo:candid/Tag"; import Itertools "mo:itertools/Iter"; import PeekableIter "mo:itertools/PeekableIter"; import Map "mo:map/Map"; +import MapConst "mo:map/Map/const"; import FloatX "mo:xtended-numbers/FloatX"; +import ByteUtils "mo:byte-utils"; import { hashName = hash_record_key } "mo:candid/Tag"; @@ -40,1648 +42,1932 @@ import Utils "../../Utils"; import CandidUtils "CandidUtils"; module { - type Arg = Arg.Arg; - type Type = Type.Type; - type Tag = Tag.Tag; - type Value = Value.Value; - type RecordFieldType = Type.RecordFieldType; - type RecordFieldValue = Value.RecordFieldValue; - type TrieMap = TrieMap.TrieMap; - type Result = Result.Result; - type Buffer = Buffer.Buffer; - type Iter = Iter.Iter; - type Hash = Nat32; - type Map = Map.Map; - type Order = Order.Order; - - type Candid = T.Candid; - type CandidType = T.CandidType; - type KeyValuePair = T.KeyValuePair; - let { thash } = Map; - let { unsigned_leb128; signed_leb128_64 } = Utils; - - public func encode(candid_values : [Candid], options : ?T.Options) : Result { - one_shot(candid_values, options); + type Arg = Arg.Arg; + type Type = Type.Type; + type Tag = Tag.Tag; + type Value = Value.Value; + type RecordFieldType = Type.RecordFieldType; + type RecordFieldValue = Value.RecordFieldValue; + type TrieMap = TrieMap.TrieMap; + type Result = Result.Result; + type Buffer = Buffer.Buffer; + type Iter = Iter.Iter; + type Hash = Nat32; + type Map = Map.Map; + type Order = Order.Order; + + type Candid = T.Candid; + type CandidType = T.CandidType; + type KeyValuePair = T.KeyValuePair; + let { thash } = Map; + let { unsigned_leb128; signed_leb128_64 } = Utils; + + // public func encode(candid_values : [Candid], options : ?T.Options) : Result { + // let renaming_map = Map.new(); + // for ((k, v) in Option.get(options, T.defaultOptions).renameKeys.vals()) { + // ignore Map.put(renaming_map, thash, k, v); + // }; + // let (candid_types) = switch (infer_candid_types(candid_values, renaming_map)) { + // case (#ok(inferred_types)) Array.map( + // inferred_types, + // func(candid_type : CandidType) : CandidType = CandidUtils.format_candid_type(candid_type, renaming_map), + // ); + // case (#err(e)) return #err(e); + // }; + // let encoder = Encoder.new(candid_types, options); + // Encoder.encode(encoder, candid_values, options); + // }; + public func encode(candid_values : [Candid], options : ?T.Options) : Result { + one_shot(candid_values, options); + }; + + public func encodeOne(candid : Candid, options : ?T.Options) : Result { + encode([candid], options); + }; + + func infer_candid_types(candid_values : [Candid], renaming_map : Map) : Result<[CandidType], Text> { + let buffer = Buffer.Buffer(candid_values.size()); + + for (candid in candid_values.vals()) { + let candid_type = to_candid_types(candid, renaming_map); + + let rows = Buffer.Buffer<[InternalCandidTypeNode]>(8); + + let node : InternalCandidTypeNode = { + type_ = candid_type; + height = 0; + parent_index = 0; + key = null; + }; + + rows.add([node]); + + order_candid_types_by_height_bfs(rows); + + let res = merge_candid_variants_and_array_types(rows); + let #ok(merged_type) = res else return Utils.send_error(res); + + buffer.add(merged_type); }; - public func encodeOne(candid : Candid, options : ?T.Options) : Result { - encode([candid], options); + #ok(Buffer.toArray(buffer)); + }; + + let C = { + COUNTER = { + COMPOUND_TYPE = 0; + PRIMITIVE_TYPE = 1; + VALUE = 2; }; + }; - func infer_candid_types(candid_values : [Candid], renaming_map : Map) : Result<[CandidType], Text> { - let buffer = Buffer.Buffer(candid_values.size()); + public func one_shot(candid_values : [Candid], _options : ?T.Options) : Result { - for (candid in candid_values.vals()) { - let candid_type = to_candid_types(candid, renaming_map); + let renaming_map = Map.new(); - let rows = Buffer.Buffer<[InternalCandidTypeNode]>(8); + let compound_type_buffer = Buffer.Buffer(200); + let candid_type_buffer = Buffer.Buffer(200); + let value_buffer = Buffer.Buffer(400); - let node : InternalCandidTypeNode = { - type_ = candid_type; - height = 0; - parent_index = 0; - key = null; + let counter = [var 0]; + + let options = Option.get(_options, T.defaultOptions); + + for ((k, v) in options.renameKeys.vals()) { + ignore Map.put(renaming_map, thash, k, v); + }; + + var candid_types : [CandidType] = switch (options.types) { + case (?types) { types }; + case (_) switch (infer_candid_types(candid_values, renaming_map)) { + case (#ok(inferred_types)) Array.map( + inferred_types, + func(candid_type : CandidType) : CandidType = CandidUtils.format_candid_type(candid_type, renaming_map), + ); + case (#err(e)) return #err(e); + }; + }; + + one_shot_encode( + candid_types, + candid_values, + compound_type_buffer, + candid_type_buffer, + value_buffer, + counter, + renaming_map, + ); + + let candid_magic_bytes_buffer = Buffer.fromArray([0x44, 0x49, 0x44, 0x4C]); // 'DIDL' magic bytes + + // add compound type to the buffer + let compound_type_size_bytes = Buffer.Buffer(8); + // ByteUtils.Buffer.addLEB128_64(compound_type_size_bytes, Nat64.fromNat(counter[C.COUNTER.COMPOUND_TYPE])); + unsigned_leb128(compound_type_size_bytes, counter[C.COUNTER.COMPOUND_TYPE]); + + // add primitive type to the buffer + let candid_type_size_bytes = Buffer.Buffer(8); + // ByteUtils.Buffer.addLEB128_64(candid_type_size_bytes, Nat64.fromNat(candid_values.size())); + unsigned_leb128(candid_type_size_bytes, candid_values.size()); + + let total_size = candid_magic_bytes_buffer.size() + compound_type_size_bytes.size() + compound_type_buffer.size() + candid_type_size_bytes.size() + candid_type_buffer.size() + value_buffer.size(); + + let sequence = [ + candid_magic_bytes_buffer, + compound_type_size_bytes, + compound_type_buffer, + candid_type_size_bytes, + candid_type_buffer, + value_buffer, + ]; + + // Print each buffer in the sequence as a Blob + // for (buf in sequence.vals()) { + // Debug.print(debug_show Blob.fromArray(Buffer.toArray(buf))); + // }; + + var i = 0; + var j = 0; + + #ok( + Blob.fromArray( + Array.tabulate( + total_size, + func(_ : Nat) : Nat8 { + var buffer = sequence[i]; + while (j >= buffer.size()) { + j := 0; + i += 1; + buffer := sequence[i]; }; - rows.add([node]); + let byte = buffer.get(j); + j += 1; + byte; + }, + ) + ) + ); + }; - order_candid_types_by_height_bfs(rows); + func check_is_tuple(candid_types : [(Text, Any)]) : Bool { + let n = candid_types.size(); // 0-based index + var sum_of_n : Int = 0; - let res = merge_candid_variants_and_array_types(rows); - let #ok(merged_type) = res else return Utils.send_error(res); + var i = 0; + label tuple_check while (i < candid_types.size()) { + let record_key = candid_types[i].0; - buffer.add(merged_type); - }; + if (Utils.text_is_number(record_key)) { + sum_of_n += (Utils.text_to_nat(record_key) + 1); + } else break tuple_check; - #ok(Buffer.toArray(buffer)); + i += 1; }; - let C = { - COUNTER = { - COMPOUND_TYPE = 0; - PRIMITIVE_TYPE = 1; - VALUE = 2; + sum_of_n == (n * (n + 1)) / 2; + }; + + func tuple_type_to_record(tuple_types : [CandidType]) : [(Text, CandidType)] { + Array.tabulate<(Text, CandidType)>( + tuple_types.size(), + func(i : Nat) : (Text, CandidType) { + (debug_show (i), tuple_types[i]); + }, + ); + }; + + func tuple_value_to_record(tuple_values : [Candid]) : [(Text, Candid)] { + Array.tabulate<(Text, Candid)>( + tuple_values.size(), + func(i : Nat) : (Text, Candid) { + (debug_show i, tuple_values[i]); + }, + ); + }; + + public func one_shot_encode( + candid_types : [CandidType], + candid_values : [Candid], + compound_type_buffer : Buffer, + candid_type_buffer : Buffer, + value_buffer : Buffer, + counter : [var Nat], + renaming_map : Map, + ) { + assert candid_values.size() == candid_types.size(); + + // include size of candid values + // unsigned_leb128(type_buffer, candid_values.size()); + + let unique_compound_type_map = Map.new(); + let recursive_map = Map.new(); + + var i = 0; + + while (i < candid_values.size()) { + + ignore encode_candid( + candid_types[i], + candid_values[i], + compound_type_buffer, + candid_type_buffer, + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + false, + false, + ); + + i += 1; + }; + + }; + + /// Encodes only the value part, without encoding any type information. + public func encode_value_only( + candid_type : CandidType, + candid_value : Candid, + value_buffer : Buffer, + renaming_map : Map, + unique_compound_type_map : Map, + recursive_map : Map, + counter : [var Nat], + is_nested_child_of_compound_type : Bool, + ) : ?Hash { + let candid_is_compound_type = switch candid_type { + case (#Option(_) or #Array(_) or #Record(_) or #Map(_) or #Tuple(_) or #Variant(_) or #Recursive(_) or #Blob(_)) true; + case (_) false; + }; + + if (candid_is_compound_type) { + switch (candid_type, candid_value) { + case (#Option(opt_type), #Option(opt_value)) { + if (opt_value == #Null and opt_type != #Null) { + value_buffer.add(0); // no value + } else { + value_buffer.add(1); // has value + ignore encode_value_only( + opt_type, + opt_value, + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + true, + ); + }; + }; + case (#Option(opt_type), #Null) { + value_buffer.add(0); // no value + }; + case (#Array(arr_type), #Array(arr_values)) { + unsigned_leb128(value_buffer, arr_values.size()); + var i = 0; + while (i < arr_values.size()) { + ignore encode_value_only( + arr_type, + arr_values[i], + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + true, + ); + i += 1; + }; + }; + case (#Blob, #Blob(blob)) { + let bytes = Array.map(Blob.toArray(blob), func(n : Nat8) : Candid = #Nat8(n)); + ignore encode_value_only( + #Array(#Nat8), + #Array(bytes), + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + is_nested_child_of_compound_type, + ); + }; + case (#Array(#Nat8), #Blob(blob)) { + let bytes = Array.map(Blob.toArray(blob), func(n : Nat8) : Candid = #Nat8(n)); + ignore encode_value_only( + #Array(#Nat8), + #Array(bytes), + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + is_nested_child_of_compound_type, + ); + }; + case (#Blob, #Array(bytes)) { + if (bytes.size() > 0) { + switch (bytes[0]) { + case (#Nat8(_)) {}; + case (_) return Debug.trap("invalid blob value: expected array of Nat8, got array of " # debug_show bytes[0]); + }; + }; + ignore encode_value_only( + #Array(#Nat8), + #Array(bytes), + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + is_nested_child_of_compound_type, + ); + }; + case (#Record(record_types) or #Map(record_types), #Record(record_entries) or #Map(record_entries)) { + let is_tuple = switch candid_type { + case (#Record(rts) or #Map(rts)) check_is_tuple(rts); + case _ false; + }; + + let record_entry_cache : Map.Map = Utils.create_map(record_entries.size()); + + for ((k, v) in record_entries.vals()) { + let field_value_key = get_renamed_key(renaming_map, k); + ignore Map.put(record_entry_cache, thash, field_value_key, v); + }; + + var i = 0; + while (i < record_types.size()) { + let field_type = record_types[i].1; + let field_type_key = get_renamed_key(renaming_map, record_types[i].0); + let field_value = switch (Map.get(record_entry_cache, Map.thash, field_type_key)) { + case (?field_value) field_value; + case (_) Debug.trap("unable to find field key in field types: " # debug_show field_type_key # "in " # debug_show record_entries); + }; + ignore encode_value_only( + field_type, + field_value, + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + true, + ); + i += 1; + }; + }; + + case (#Tuple(tuple_types), #Tuple(tuple_values)) { + ignore encode_value_only( + #Record(tuple_type_to_record(tuple_types)), + #Record(tuple_value_to_record(tuple_values)), + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + is_nested_child_of_compound_type, + ); }; + case (#Tuple(tuple_types), #Record(tuple_values)) { + var i = 0; + assert Itertools.all( + tuple_values.vals(), + func((k, v) : (Text, Any)) : Bool { + i += 1; + Utils.text_is_number(k) and Utils.text_to_nat(k) == (i - 1 : Nat); + }, + ); + ignore encode_value_only( + #Record(tuple_type_to_record(tuple_types)), + #Record(tuple_values), + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + is_nested_child_of_compound_type, + ); + }; + case (#Record(record_types), #Tuple(tuple_values)) { + var i = 0; + assert Itertools.all( + record_types.vals(), + func((k, v) : (Text, Any)) : Bool { + i += 1; + Utils.text_is_number(k) and Utils.text_to_nat(k) == (i - 1 : Nat); + }, + ); + ignore encode_value_only( + #Record(record_types), + #Record(tuple_value_to_record(tuple_values)), + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + is_nested_child_of_compound_type, + ); + }; + case (#Variant(variant_types), #Variant(variant)) { + let variant_key = get_renamed_key(renaming_map, variant.0); + let variant_value = variant.1; + let variant_index_res = Array.indexOf<(Text, CandidType)>( + (variant_key, #Empty), + variant_types, + func((a, _) : (Text, CandidType), (b, _) : (Text, CandidType)) : Bool = a == b, + ); + let variant_index = switch (variant_index_res) { + case (?index) index; + case (_) Debug.trap("unable to find variant key in variant types"); + }; + unsigned_leb128(value_buffer, variant_index); + ignore encode_value_only( + variant_types[variant_index].1, + variant_value, + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + true, + ); + }; + case (_) Debug.trap("invalid (type, value) pair for encode_value_only: " # debug_show { candid_type; candid_value }); + }; + + } else { + // primitive types + switch (candid_type, candid_value) { + case (#Nat, #Nat(n)) { + unsigned_leb128(value_buffer, n); + }; + case (#Nat8, #Nat8(n)) { + value_buffer.add(n); + }; + case (#Nat16, #Nat16(n)) { + ByteUtils.Buffer.LE.addNat16(value_buffer, n); + }; + case (#Nat32, #Nat32(n)) { + ByteUtils.Buffer.LE.addNat32(value_buffer, n); + }; + case (#Nat64, #Nat64(n)) { + ByteUtils.Buffer.LE.addNat64(value_buffer, n); + }; + case (#Int, #Int(n)) { + signed_leb128_64(value_buffer, n); + }; + case (#Int8, #Int8(i8)) { + value_buffer.add(Int8.toNat8(i8)); + }; + case (#Int16, #Int16(i16)) { + ByteUtils.Buffer.LE.addInt16(value_buffer, i16); + }; + case (#Int32, #Int32(i32)) { + ByteUtils.Buffer.LE.addInt32(value_buffer, i32); + }; + case (#Int64, #Int64(i64)) { + ByteUtils.Buffer.LE.addInt64(value_buffer, i64); + }; + case (#Float, #Float(f64)) { + ByteUtils.Buffer.LE.addFloat(value_buffer, f64); + }; + case (#Bool, #Bool(b)) { + value_buffer.add(if (b) (1) else (0)); + }; + case (#Null, #Null) {}; + case (#Empty, #Empty) {}; + case (#Text, #Text(t)) { + let utf8_blob = Text.encodeUtf8(t); + unsigned_leb128(value_buffer, utf8_blob.size()); + var i = 0; + while (i < utf8_blob.size()) { + value_buffer.add(utf8_blob[i]); + i += 1; + }; + }; + case (#Principal, #Principal(p)) { + value_buffer.add(0x01); // indicate transparency state + let blob = Principal.toBlob(p); + unsigned_leb128(value_buffer, blob.size()); + var i = 0; + while (i < blob.size()) { + value_buffer.add(blob[i]); + i += 1; + }; + }; + case (_) Debug.trap("unknown (type, value) pair for encode_value_only: " # debug_show (candid_type, candid_value)); + }; }; + null; + }; - public func one_shot(candid_values : [Candid], _options : ?T.Options) : Result { + func is_compound_type(candid_type : CandidType) : Bool { + switch (candid_type) { + case (#Option(_) or #Array(_) or #Record(_) or #Map(_) or #Tuple(_) or #Variant(_) or #Recursive(_) or #Blob(_)) true; + case (_) false; + }; + }; + + func encode_primitive_type_only( + candid_type : CandidType, + compound_type_buffer : Buffer, + candid_type_buffer : Buffer, + is_nested_child_of_compound_type : Bool, + ) { + let ref_candid_type_buffer = if (is_nested_child_of_compound_type) { + compound_type_buffer; + } else { + candid_type_buffer; + }; - let renaming_map = Map.new(); + switch (candid_type) { + case (#Nat) ref_candid_type_buffer.add(T.TypeCode.Nat); + case (#Nat8) ref_candid_type_buffer.add(T.TypeCode.Nat8); + case (#Nat16) ref_candid_type_buffer.add(T.TypeCode.Nat16); + case (#Nat32) ref_candid_type_buffer.add(T.TypeCode.Nat32); + case (#Nat64) ref_candid_type_buffer.add(T.TypeCode.Nat64); + + case (#Int) ref_candid_type_buffer.add(T.TypeCode.Int); + case (#Int8) ref_candid_type_buffer.add(T.TypeCode.Int8); + case (#Int16) ref_candid_type_buffer.add(T.TypeCode.Int16); + case (#Int32) ref_candid_type_buffer.add(T.TypeCode.Int32); + case (#Int64) ref_candid_type_buffer.add(T.TypeCode.Int64); + + case (#Float) ref_candid_type_buffer.add(T.TypeCode.Float); + case (#Bool) ref_candid_type_buffer.add(T.TypeCode.Bool); + case (#Text) ref_candid_type_buffer.add(T.TypeCode.Text); + case (#Principal) ref_candid_type_buffer.add(T.TypeCode.Principal); + case (#Null) ref_candid_type_buffer.add(T.TypeCode.Null); + case (#Empty) ref_candid_type_buffer.add(T.TypeCode.Empty); + + case (_) Debug.trap("encode_primitive_type_only(): unknown primitive type " # debug_show candid_type); + }; + }; + + func encode_compound_type_only( + candid_type : CandidType, + compound_type_buffer : Buffer, + candid_type_buffer : Buffer, + renaming_map : Map, + unique_compound_type_map : Map, + counter : [var Nat], + is_nested_child_of_compound_type : Bool, + ) { + let type_info = get_type_info(candid_type); + let compound_type_exists = Map.has(unique_compound_type_map, thash, type_info); + if (compound_type_exists) return; + // Debug.print("encode_compound_type_only(): " # debug_show type_info); + switch (candid_type) { + case (#Option(opt_type)) { + let opt_type_is_compound = is_compound_type(opt_type); + + if (not opt_type_is_compound) { + compound_type_buffer.add(T.TypeCode.Option); + }; - let compound_type_buffer = Buffer.Buffer(200); - let primitive_type_buffer = Buffer.Buffer(200); - let value_buffer = Buffer.Buffer(400); + encode_type_only( + opt_type, + compound_type_buffer, + candid_type_buffer, + renaming_map, + unique_compound_type_map, + counter, + true, + ); - let counter = [var 0]; + if (opt_type_is_compound) { + compound_type_buffer.add(T.TypeCode.Option); + let opt_type_info = get_type_info(opt_type); + let pos = switch (Map.get(unique_compound_type_map, thash, opt_type_info)) { + case (?pos) pos; + case (_) Debug.trap("unable to find compound type pos to store in primitive type sequence for " # debug_show (type_info)); + }; + unsigned_leb128(compound_type_buffer, pos); + }; - let options = Option.get(_options, T.defaultOptions); + }; - for ((k, v) in options.renameKeys.vals()) { - ignore Map.put(renaming_map, thash, k, v); + case (#Array(arr_type)) { + let arr_type_is_compound = is_compound_type(arr_type); + if (not arr_type_is_compound) { + compound_type_buffer.add(T.TypeCode.Array); }; - var candid_types : [CandidType] = switch (options.types) { - case (?types) { types }; - case (_) switch (infer_candid_types(candid_values, renaming_map)) { - case (#ok(inferred_types)) Array.map( - inferred_types, - func(candid_type : CandidType) : CandidType = CandidUtils.format_candid_type(candid_type, renaming_map), - ); - case (#err(e)) return #err(e); - }; + encode_type_only( + arr_type, + compound_type_buffer, + candid_type_buffer, + renaming_map, + unique_compound_type_map, + counter, + true, + ); + + if (arr_type_is_compound) { + compound_type_buffer.add(T.TypeCode.Array); + let arr_type_info = get_type_info(arr_type); + let pos = switch (Map.get(unique_compound_type_map, thash, arr_type_info)) { + case (?pos) pos; + case (_) Debug.trap("unable to find compound type pos to store in primitive type sequence for " # debug_show (type_info)); + }; + unsigned_leb128(compound_type_buffer, pos); }; + }; + + case (#Blob) return encode_compound_type_only( + #Array(#Nat8), + compound_type_buffer, + candid_type_buffer, + renaming_map, + unique_compound_type_map, + counter, + is_nested_child_of_compound_type, + ); + + case (#Record(record_types) or #Map(record_types)) { + let is_tuple = check_is_tuple(record_types); - one_shot_encode( - candid_types, - candid_values, + var i = 0; + while (i < record_types.size()) { + let field_type = record_types[i].1; + + let value_type_is_compound = is_compound_type(field_type); + + if (value_type_is_compound) encode_type_only( + field_type, compound_type_buffer, - primitive_type_buffer, - value_buffer, - counter, + candid_type_buffer, renaming_map, - ); + unique_compound_type_map, + counter, + true, + ); - let candid_buffer = Buffer.fromArray([0x44, 0x49, 0x44, 0x4C]); // 'DIDL' magic bytes + i += 1; + }; - // add compound type to the buffer - let compound_type_size_bytes = Buffer.Buffer(8); - unsigned_leb128(compound_type_size_bytes, counter[C.COUNTER.COMPOUND_TYPE]); + compound_type_buffer.add(T.TypeCode.Record); + unsigned_leb128(compound_type_buffer, record_types.size()); - // add primitive type to the buffer - let primitive_type_size_bytes = Buffer.Buffer(8); - unsigned_leb128(primitive_type_size_bytes, candid_values.size()); + i := 0; + while (i < record_types.size()) { + let field_type = record_types[i].1; - let total_size = candid_buffer.size() + compound_type_size_bytes.size() + compound_type_buffer.size() + primitive_type_size_bytes.size() + primitive_type_buffer.size() + value_buffer.size(); + let value_type_is_compound = is_compound_type(field_type); - let sequence = [ - candid_buffer, - compound_type_size_bytes, - compound_type_buffer, - primitive_type_size_bytes, - primitive_type_buffer, - value_buffer, - ]; + if (is_tuple) { + unsigned_leb128(compound_type_buffer, i); + } else { + let record_key = get_renamed_key(renaming_map, record_types[i].0); - var i = 0; - var j = 0; - - #ok( - Blob.fromArray( - Array.tabulate( - total_size, - func(_ : Nat) : Nat8 { - var buffer = sequence[i]; - while (j >= buffer.size()) { - j := 0; - i += 1; - buffer := sequence[i]; - }; - - let byte = buffer.get(j); - j += 1; - byte; - }, - ) - ) + let hash_key = hash_record_key(record_key); + unsigned_leb128(compound_type_buffer, Nat32.toNat(hash_key)); + }; + + if (value_type_is_compound) { + let value_type_info = get_type_info(field_type); + let pos = switch (Map.get(unique_compound_type_map, thash, value_type_info)) { + case (?pos) pos; + case (_) Debug.trap("unable to find compound type pos to store in primitive type sequence for " # debug_show (type_info)); + }; + unsigned_leb128(compound_type_buffer, pos); + } else { + encode_primitive_type_only( + field_type, + compound_type_buffer, + candid_type_buffer, + true, + ); + }; + + i += 1; + }; + }; + + case (#Tuple(tuple_types)) { + return encode_compound_type_only( + #Record(tuple_type_to_record(tuple_types)), + compound_type_buffer, + candid_type_buffer, + renaming_map, + unique_compound_type_map, + counter, + true, ); - }; + }; - func check_is_tuple(candid_types : [(Text, Any)]) : Bool { - let n = candid_types.size(); // 0-based index - var sum_of_n : Int = 0; + case (#Variant(variant_types)) { var i = 0; - label tuple_check while (i < candid_types.size()) { - let record_key = candid_types[i].0; + while (i < variant_types.size()) { + let variant_type = variant_types[i].1; - if (Utils.text_is_number(record_key)) { - sum_of_n += (Utils.text_to_nat(record_key) + 1); - } else break tuple_check; + let variant_type_is_compound = is_compound_type(variant_type); - i += 1; + if (variant_type_is_compound) { + encode_compound_type_only( + variant_type, + compound_type_buffer, + candid_type_buffer, + renaming_map, + unique_compound_type_map, + counter, + true, + ); + }; + + i += 1; }; - sum_of_n == (n * (n + 1)) / 2; - }; + compound_type_buffer.add(T.TypeCode.Variant); + unsigned_leb128(compound_type_buffer, variant_types.size()); - func tuple_type_to_record(tuple_types : [CandidType]) : [(Text, CandidType)] { - Array.tabulate<(Text, CandidType)>( - tuple_types.size(), - func(i : Nat) : (Text, CandidType) { - (debug_show (i), tuple_types[i]); - }, - ); - }; + i := 0; + while (i < variant_types.size()) { + let variant_key = get_renamed_key(renaming_map, variant_types[i].0); + let variant_type = variant_types[i].1; + let variant_type_is_compound = is_compound_type(variant_type); - func tuple_value_to_record(tuple_values : [Candid]) : [(Text, Candid)] { - Array.tabulate<(Text, Candid)>( - tuple_values.size(), - func(i : Nat) : (Text, Candid) { - (debug_show i, tuple_values[i]); - }, - ); + let hash_key = hash_record_key(variant_key); + unsigned_leb128(compound_type_buffer, Nat32.toNat(hash_key)); + + if (variant_type_is_compound) { + let variant_type_info = get_type_info(variant_type); + let pos = switch (Map.get(unique_compound_type_map, thash, variant_type_info)) { + case (?pos) pos; + case (_) Debug.trap("unable to find compound type pos to store in primitive type sequence for " # debug_show (type_info)); + }; + unsigned_leb128(compound_type_buffer, pos); + } else { + encode_primitive_type_only( + variant_type, + compound_type_buffer, + candid_type_buffer, + true, + ); + }; + + i += 1; + }; + }; + + case (_) Debug.trap("encode_compound_type_only(): unknown compound type " # debug_show candid_type); }; - public func one_shot_encode( - candid_types : [CandidType], - candid_values : [Candid], - compound_type_buffer : Buffer, - primitive_type_buffer : Buffer, - value_buffer : Buffer, - counter : [var Nat], - renaming_map : Map, - ) { - assert candid_values.size() == candid_types.size(); + ignore Map.put(unique_compound_type_map, thash, type_info, counter[C.COUNTER.COMPOUND_TYPE]); + counter[C.COUNTER.COMPOUND_TYPE] += 1; + + }; + + public func encode_type_only( + candid_type : CandidType, + compound_type_buffer : Buffer, + candid_type_buffer : Buffer, + renaming_map : Map, + unique_compound_type_map : Map, + counter : [var Nat], + is_nested_child_of_compound_type : Bool, + ) { + if (is_compound_type(candid_type)) { + encode_compound_type_only( + candid_type, + compound_type_buffer, + candid_type_buffer, + renaming_map, + unique_compound_type_map, + counter, + is_nested_child_of_compound_type, + ); + + // Add compound type reference to primitive type buffer for top-level types + if (not is_nested_child_of_compound_type) { + let type_info = get_type_info(candid_type); + let pos = switch (Map.get(unique_compound_type_map, thash, type_info)) { + case (?pos) pos; + case (_) Debug.trap("unable to find compound type pos to store in primitive type sequence for " # debug_show (type_info)); + }; + unsigned_leb128(candid_type_buffer, pos); + }; + } else { + encode_primitive_type_only( + candid_type, + compound_type_buffer, + candid_type_buffer, + is_nested_child_of_compound_type, + ); + }; + }; + func get_type_info(_candid_type : CandidType) : Text { + let candid_type = switch (_candid_type) { + case (#Map(records)) #Record(records); + case (#Blob) #Array(#Nat8); + case (#Tuple(tuple_types)) #Record(tuple_type_to_record(tuple_types)); + case (candid_type) candid_type; + }; - // include size of candid values - // unsigned_leb128(type_buffer, candid_values.size()); + debug_show candid_type; + }; + + func encode_primitive_type( + candid_type : CandidType, + candid_value : Candid, + compound_type_buffer : Buffer, + candid_type_buffer : Buffer, + value_buffer : Buffer, + renaming_map : Map, + unique_compound_type_map : Map, + recursive_map : Map, + is_nested_child_of_compound_type : Bool, + ignore_type : Bool, + ) { + let ref_candid_type_buffer = if (ignore_type) { + object { + public func add(_ : Nat8) {}; // do nothing + }; + } else if (is_nested_child_of_compound_type) { + compound_type_buffer; + } else { + candid_type_buffer; + }; - let unique_compound_type_map = Map.new(); - let recursive_map = Map.new(); + switch (candid_type, candid_value) { + case (#Nat, #Nat(n)) { + // Debug.print("start encoding Nat: " # debug_show n); + ref_candid_type_buffer.add(T.TypeCode.Nat); + // Debug.print("encoded type codde"); + unsigned_leb128(value_buffer, n); + + }; + case (#Nat8, #Nat8(n)) { + ref_candid_type_buffer.add(T.TypeCode.Nat8); + value_buffer.add(n); + }; + case (#Nat16, #Nat16(n)) { + ref_candid_type_buffer.add(T.TypeCode.Nat16); + ByteUtils.Buffer.LE.addNat16(value_buffer, n); + }; + case (#Nat32, #Nat32(n)) { + ref_candid_type_buffer.add(T.TypeCode.Nat32); + ByteUtils.Buffer.LE.addNat32(value_buffer, n); + }; + case (#Nat64, #Nat64(n)) { + ref_candid_type_buffer.add(T.TypeCode.Nat64); + ByteUtils.Buffer.LE.addNat64(value_buffer, n); + }; + case (#Int, #Int(n)) { + ref_candid_type_buffer.add(T.TypeCode.Int); + signed_leb128_64(value_buffer, n); + }; + case (#Int8, #Int8(i8)) { + ref_candid_type_buffer.add(T.TypeCode.Int8); + value_buffer.add(Int8.toNat8(i8)); + }; + case (#Int16, #Int16(i16)) { + ref_candid_type_buffer.add(T.TypeCode.Int16); + ByteUtils.Buffer.LE.addInt16(value_buffer, i16); + }; + case (#Int32, #Int32(i32)) { + ref_candid_type_buffer.add(T.TypeCode.Int32); + ByteUtils.Buffer.LE.addInt32(value_buffer, i32); + }; + case (#Int64, #Int64(i64)) { + ref_candid_type_buffer.add(T.TypeCode.Int64); + ByteUtils.Buffer.LE.addInt64(value_buffer, i64); + }; + case (#Float, #Float(f64)) { + ref_candid_type_buffer.add(T.TypeCode.Float); + ByteUtils.Buffer.LE.addFloat(value_buffer, f64); + }; + case (#Bool, #Bool(b)) { + ref_candid_type_buffer.add(T.TypeCode.Bool); + value_buffer.add(if (b) (1) else (0)); + }; + case (#Null, #Null) { + ref_candid_type_buffer.add(T.TypeCode.Null); + }; + case (#Empty, #Empty) { + ref_candid_type_buffer.add(T.TypeCode.Empty); + }; + case (#Text, #Text(t)) { + ref_candid_type_buffer.add(T.TypeCode.Text); + + let utf8_blob = Text.encodeUtf8(t); + unsigned_leb128(value_buffer, utf8_blob.size()); var i = 0; + while (i < utf8_blob.size()) { + value_buffer.add(utf8_blob[i]); + i += 1; + }; - while (i < candid_values.size()) { + }; + case (#Principal, #Principal(p)) { + ref_candid_type_buffer.add(T.TypeCode.Principal); - ignore encode_candid( - candid_types[i], - candid_values[i], - compound_type_buffer, - primitive_type_buffer, - value_buffer, - renaming_map, - unique_compound_type_map, - recursive_map, - counter, - false, - false, - ); + value_buffer.add(0x01); // indicate transparency state + let blob = Principal.toBlob(p); + unsigned_leb128(value_buffer, blob.size()); - i += 1; + var i = 0; + while (i < blob.size()) { + value_buffer.add(blob[i]); + i += 1; }; + }; + case (_) Debug.trap("unknown (type, value) pair: " # debug_show (candid_type, candid_value)); }; + }; + + func encode_compound_type( + candid_type : CandidType, + candid_value : Candid, + compound_type_buffer : Buffer, + candid_type_buffer : Buffer, + value_buffer : Buffer, + renaming_map : Map, + unique_compound_type_map : Map, + recursive_map : Map, + counter : [var Nat], + is_nested_child_of_compound_type : Bool, + _type_exists : Bool, + ) { + + // Debug.print("encode_compound_type(): " # debug_show (candid_type, candid_value)); + + // ----------------- Compound Types ----------------- // + + // encode_candid type only + // case (candid_type, #Null) { + // encode_nested_type(candid_type, compound_type_buffer); + // }; + + let type_info = get_type_info(candid_type); - func is_compound_type(candid_type : CandidType) : Bool { - switch (candid_type) { - case (#Option(_) or #Array(_) or #Record(_) or #Map(_) or #Tuple(_) or #Variant(_) or #Recursive(_) or #Blob(_)) true; - case (_) false; + // type_exists_in_compound_type_sequence + let type_exists = _type_exists or Map.has(unique_compound_type_map, thash, type_info); + + switch (candid_type, candid_value) { + + case (#Option(opt_type), #Option(opt_value)) { + + let opt_type_is_compound = is_compound_type(opt_type); + + if (not type_exists and not opt_type_is_compound) { + compound_type_buffer.add(T.TypeCode.Option); }; - }; - func encode_primitive_type_only( - candid_type : CandidType, - compound_type_buffer : Buffer, - primitive_type_buffer : Buffer, - is_nested_child_of_compound_type : Bool, - ) { - let ref_primitive_type_buffer = if (is_nested_child_of_compound_type) { - compound_type_buffer; + if (opt_value == #Null and opt_type != #Null) { + // a result of being able to set #Null at any point in an #Option type + // for instance, type #Option(#Nat) with value #Null + + value_buffer.add(0); // no value + + if (not type_exists) encode_type_only( + opt_type, + compound_type_buffer, + candid_type_buffer, + renaming_map, + unique_compound_type_map, + counter, + true, + ); + } else { - primitive_type_buffer; + value_buffer.add(1); // has value + + ignore encode_candid( + opt_type, + opt_value, + compound_type_buffer, + candid_type_buffer, + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + true, + type_exists, + ); }; - switch (candid_type) { - case (#Nat) ref_primitive_type_buffer.add(T.TypeCode.Nat); - case (#Nat8) ref_primitive_type_buffer.add(T.TypeCode.Nat8); - case (#Nat16) ref_primitive_type_buffer.add(T.TypeCode.Nat16); - case (#Nat32) ref_primitive_type_buffer.add(T.TypeCode.Nat32); - case (#Nat64) ref_primitive_type_buffer.add(T.TypeCode.Nat64); - - case (#Int) ref_primitive_type_buffer.add(T.TypeCode.Int); - case (#Int8) ref_primitive_type_buffer.add(T.TypeCode.Int8); - case (#Int16) ref_primitive_type_buffer.add(T.TypeCode.Int16); - case (#Int32) ref_primitive_type_buffer.add(T.TypeCode.Int32); - case (#Int64) ref_primitive_type_buffer.add(T.TypeCode.Int64); - - case (#Float) ref_primitive_type_buffer.add(T.TypeCode.Float); - case (#Bool) ref_primitive_type_buffer.add(T.TypeCode.Bool); - case (#Text) ref_primitive_type_buffer.add(T.TypeCode.Text); - case (#Principal) ref_primitive_type_buffer.add(T.TypeCode.Principal); - case (#Null) ref_primitive_type_buffer.add(T.TypeCode.Null); - case (#Empty) ref_primitive_type_buffer.add(T.TypeCode.Empty); - - case (_) Debug.trap("encode_primitive_type_only(): unknown primitive type " # debug_show candid_type); + if ( + not type_exists and opt_type_is_compound + ) { + // let prev_start = get_prev_compound_type_start_index(compound_type_buffer); + compound_type_buffer.add(T.TypeCode.Option); + let opt_type_info = get_type_info(opt_type); + let pos = switch (Map.get(unique_compound_type_map, thash, opt_type_info)) { + case (?pos) pos; + case (_) Debug.trap("unable to find compound type pos to store in primitive type sequence for " # debug_show (type_info)); + }; + unsigned_leb128(compound_type_buffer, pos); }; - }; + }; - func encode_compound_type_only( - candid_type : CandidType, - compound_type_buffer : Buffer, - primitive_type_buffer : Buffer, - renaming_map : Map, - unique_compound_type_map : Map, - counter : [var Nat], - is_nested_child_of_compound_type : Bool, - ) { - let type_info = debug_show candid_type; - let compound_type_exists = Map.has(unique_compound_type_map, thash, type_info); - if (compound_type_exists) return; - // Debug.print("encode_compound_type_only(): " # debug_show type_info); - switch (candid_type) { - case (#Option(opt_type)) { - let opt_type_is_compound = is_compound_type(opt_type); - - if (not opt_type_is_compound) { - compound_type_buffer.add(T.TypeCode.Option); - }; - - encode_type_only( - opt_type, - compound_type_buffer, - primitive_type_buffer, - renaming_map, - unique_compound_type_map, - counter, - true, - ); - - if (opt_type_is_compound) { - compound_type_buffer.add(T.TypeCode.Option); - let opt_type_info = debug_show opt_type; - let pos = switch (Map.get(unique_compound_type_map, thash, opt_type_info)) { - case (?pos) pos; - case (_) Debug.trap("unable to find compound type pos to store in primitive type sequence for " # debug_show (type_info)); - }; - unsigned_leb128(compound_type_buffer, pos); - }; + // a result of being able to set #Null at any point in an #Option type + // for instance, type #Option(#Nat) with value #Null + case (#Option(opt_type), #Null) { + value_buffer.add(0); // no value - }; + let opt_type_is_compound = is_compound_type(opt_type); - case (#Array(arr_type)) { - let arr_type_is_compound = is_compound_type(arr_type); - if (not arr_type_is_compound) { - compound_type_buffer.add(T.TypeCode.Array); - }; - - encode_type_only( - arr_type, - compound_type_buffer, - primitive_type_buffer, - renaming_map, - unique_compound_type_map, - counter, - true, - ); - - if (arr_type_is_compound) { - compound_type_buffer.add(T.TypeCode.Array); - let arr_type_info = debug_show arr_type; - let pos = switch (Map.get(unique_compound_type_map, thash, arr_type_info)) { - case (?pos) pos; - case (_) Debug.trap("unable to find compound type pos to store in primitive type sequence for " # debug_show (type_info)); - }; - unsigned_leb128(compound_type_buffer, pos); - }; - }; + if (not type_exists and not opt_type_is_compound) { + compound_type_buffer.add(T.TypeCode.Option); + }; - case (#Blob) return encode_compound_type_only( - #Array(#Nat8), - compound_type_buffer, - primitive_type_buffer, - renaming_map, - unique_compound_type_map, - counter, - true, - ); + if (not type_exists) encode_type_only( + opt_type, + compound_type_buffer, + candid_type_buffer, + renaming_map, + unique_compound_type_map, + counter, + true, + ); - case (#Record(record_types) or #Map(record_types)) { - let is_tuple = check_is_tuple(record_types); - - var i = 0; - while (i < record_types.size()) { - let value_type = record_types[i].1; - - let value_type_is_compound = is_compound_type(value_type); - - if (value_type_is_compound) encode_type_only( - value_type, - compound_type_buffer, - primitive_type_buffer, - renaming_map, - unique_compound_type_map, - counter, - true, - ); - - i += 1; - }; - - compound_type_buffer.add(T.TypeCode.Record); - unsigned_leb128(compound_type_buffer, record_types.size()); - - i := 0; - while (i < record_types.size()) { - let value_type = record_types[i].1; - - let value_type_is_compound = is_compound_type(value_type); - - if (is_tuple) { - unsigned_leb128(compound_type_buffer, i); - } else { - let record_key = get_renamed_key(renaming_map, record_types[i].0); - - let hash_key = hash_record_key(record_key); - unsigned_leb128(compound_type_buffer, Nat32.toNat(hash_key)); - }; - - if (value_type_is_compound) { - let value_type_info = debug_show value_type; - let pos = switch (Map.get(unique_compound_type_map, thash, value_type_info)) { - case (?pos) pos; - case (_) Debug.trap("unable to find compound type pos to store in primitive type sequence for " # debug_show (type_info)); - }; - unsigned_leb128(compound_type_buffer, pos); - } else { - encode_primitive_type_only( - value_type, - compound_type_buffer, - primitive_type_buffer, - true, - ); - }; - - i += 1; - }; - }; + if ( + not type_exists and opt_type_is_compound + ) { + compound_type_buffer.add(T.TypeCode.Option); + let opt_type_info = get_type_info(opt_type); + let pos = switch (Map.get(unique_compound_type_map, thash, opt_type_info)) { + case (?pos) pos; + case (_) Debug.trap("unable to find compound type pos to store in primitive type sequence for " # debug_show (type_info, opt_type)); + }; + unsigned_leb128(compound_type_buffer, pos); + }; - case (#Tuple(tuple_types)) { - return encode_compound_type_only( - #Record(tuple_type_to_record(tuple_types)), - compound_type_buffer, - primitive_type_buffer, - renaming_map, - unique_compound_type_map, - counter, - true, - ); - }; + }; - case (#Variant(variant_types)) { - - var i = 0; - while (i < variant_types.size()) { - let variant_type = variant_types[i].1; - - let variant_type_is_compound = is_compound_type(variant_type); - - if (variant_type_is_compound) { - encode_compound_type_only( - variant_type, - compound_type_buffer, - primitive_type_buffer, - renaming_map, - unique_compound_type_map, - counter, - true, - ); - }; - - i += 1; - }; - - compound_type_buffer.add(T.TypeCode.Variant); - unsigned_leb128(compound_type_buffer, variant_types.size()); - - i := 0; - while (i < variant_types.size()) { - let variant_key = get_renamed_key(renaming_map, variant_types[i].0); - let variant_type = variant_types[i].1; - let variant_type_is_compound = is_compound_type(variant_type); - - let hash_key = hash_record_key(variant_key); - unsigned_leb128(compound_type_buffer, Nat32.toNat(hash_key)); - - if (variant_type_is_compound) { - let variant_type_info = debug_show variant_type; - let pos = switch (Map.get(unique_compound_type_map, thash, variant_type_info)) { - case (?pos) pos; - case (_) Debug.trap("unable to find compound type pos to store in primitive type sequence for " # debug_show (type_info)); - }; - unsigned_leb128(compound_type_buffer, pos); - } else { - encode_primitive_type_only( - variant_type, - compound_type_buffer, - primitive_type_buffer, - true, - ); - }; - - i += 1; - }; - }; + case (#Array(arr_type), #Array(arr_values)) { + let arr_type_is_compound = is_compound_type(arr_type); - case (_) Debug.trap("encode_compound_type_only(): unknown compound type " # debug_show candid_type); + if (not type_exists and not arr_type_is_compound) { + compound_type_buffer.add(T.TypeCode.Array); }; - ignore Map.put(unique_compound_type_map, thash, type_info, counter[C.COUNTER.COMPOUND_TYPE]); - counter[C.COUNTER.COMPOUND_TYPE] += 1; + // if (not type_exists) - }; + unsigned_leb128(value_buffer, arr_values.size()); - func encode_type_only( - candid_type : CandidType, - compound_type_buffer : Buffer, - primitive_type_buffer : Buffer, - renaming_map : Map, - unique_compound_type_map : Map, - counter : [var Nat], - is_nested_child_of_compound_type : Bool, - ) { - if (is_compound_type(candid_type)) { - encode_compound_type_only( - candid_type, - compound_type_buffer, - primitive_type_buffer, - renaming_map, - unique_compound_type_map, - counter, - is_nested_child_of_compound_type, - ); - } else { - encode_primitive_type_only( - candid_type, - compound_type_buffer, - primitive_type_buffer, - is_nested_child_of_compound_type, - ); + var i = 0; + + if (arr_values.size() == 0 and not type_exists) { + encode_type_only( + arr_type, + compound_type_buffer, + candid_type_buffer, + renaming_map, + unique_compound_type_map, + counter, + true, + ); + } else while (i < arr_values.size()) { + let val = arr_values[i]; + + ignore encode_candid( + arr_type, + val, + compound_type_buffer, + candid_type_buffer, + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + true, + type_exists or i > 0, + ); + + i += 1; }; - }; - func get_type_info(_candid_type : CandidType) : Text { - let candid_type = switch (_candid_type) { - case (#Map(records)) #Record(records); - case (#Blob) #Array(#Nat8); - case (#Tuple(tuple_types)) #Record(tuple_type_to_record(tuple_types)); - case (candid_type) candid_type; + + if (not type_exists and arr_type_is_compound) { + compound_type_buffer.add(T.TypeCode.Array); + + let arr_type_info = get_type_info(arr_type); + let pos = switch (Map.get(unique_compound_type_map, thash, arr_type_info)) { + case (?pos) pos; + case (_) Debug.trap("unable to find compound type pos to store in primitive type sequence for " # debug_show (type_info, arr_type)); + }; + unsigned_leb128(compound_type_buffer, pos); + }; - debug_show candid_type; - }; + }; + case (#Blob, #Blob(blob)) { + let bytes = Array.map(Blob.toArray(blob), func(n : Nat8) : Candid = #Nat8(n)); + + return encode_compound_type( + #Array(#Nat8), + #Array(bytes), + compound_type_buffer, + candid_type_buffer, + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + is_nested_child_of_compound_type, + type_exists, + ); + }; + + case (#Array(#Nat8), #Blob(blob)) { + let bytes = Array.map(Blob.toArray(blob), func(n : Nat8) : Candid = #Nat8(n)); + + return encode_compound_type( + #Array(#Nat8), + #Array(bytes), + compound_type_buffer, + candid_type_buffer, + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + is_nested_child_of_compound_type, + type_exists, + ); + }; + case (#Blob, #Array(bytes)) { + if (bytes.size() > 0) { + switch (bytes[0]) { + case (#Nat8(_)) {}; + case (_) return Debug.trap("invalid blob value: expected array of Nat8, got array of " # debug_show bytes[0]); + }; + }; - func encode_primitive_type( - candid_type : CandidType, - candid_value : Candid, - compound_type_buffer : Buffer, - primitive_type_buffer : Buffer, - value_buffer : Buffer, - renaming_map : Map, - unique_compound_type_map : Map, - recursive_map : Map, - is_nested_child_of_compound_type : Bool, - ignore_type : Bool, - ) { - let ref_primitive_type_buffer = if (ignore_type) { - object { - public func add(_ : Nat8) {}; // do nothing - }; - } else if (is_nested_child_of_compound_type) { - compound_type_buffer; + return encode_compound_type( + #Array(#Nat8), + #Array(bytes), + compound_type_buffer, + candid_type_buffer, + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + is_nested_child_of_compound_type, + type_exists, + ); + }; + + case (#Record(record_types) or #Map(record_types), #Record(record_entries) or #Map(record_entries)) { + assert record_types.size() >= record_entries.size(); + + let is_tuple = check_is_tuple(record_types); + + let cache_size = if (record_entries.size() % 2 == 0) { + record_entries.size() + 2; } else { - primitive_type_buffer; + record_entries.size() + 3; }; - switch (candid_type, candid_value) { - case (#Nat, #Nat(n)) { - // Debug.print("start encoding Nat: " # debug_show n); - ref_primitive_type_buffer.add(T.TypeCode.Nat); - // Debug.print("encoded type codde"); - unsigned_leb128(value_buffer, n); + let record_entry_cache : Map.Map = Utils.create_map(cache_size); - }; - case (#Nat8, #Nat8(n)) { - ref_primitive_type_buffer.add(T.TypeCode.Nat8); - value_buffer.add(n); - }; - case (#Nat16, #Nat16(n)) { - ref_primitive_type_buffer.add(T.TypeCode.Nat16); - value_buffer.add((n & 0xFF) |> Nat16.toNat8(_)); - value_buffer.add((n >> 8) |> Nat16.toNat8(_)); - }; - case (#Nat32, #Nat32(n)) { - ref_primitive_type_buffer.add(T.TypeCode.Nat32); - value_buffer.add((n & 0xFF) |> Nat32.toNat16(_) |> Nat16.toNat8(_)); - value_buffer.add(((n >> 8) & 0xFF) |> Nat32.toNat16(_) |> Nat16.toNat8(_)); - value_buffer.add(((n >> 16) & 0xFF) |> Nat32.toNat16(_) |> Nat16.toNat8(_)); - value_buffer.add((n >> 24) |> Nat32.toNat16(_) |> Nat16.toNat8(_)); - }; - case (#Nat64, #Nat64(n)) { - ref_primitive_type_buffer.add(T.TypeCode.Nat64); - value_buffer.add((n & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - value_buffer.add(((n >> 8) & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - value_buffer.add(((n >> 16) & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - value_buffer.add(((n >> 24) & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - value_buffer.add(((n >> 32) & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - value_buffer.add(((n >> 40) & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - value_buffer.add(((n >> 48) & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - value_buffer.add((n >> 56) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - }; - case (#Int, #Int(n)) { - ref_primitive_type_buffer.add(T.TypeCode.Int); - signed_leb128_64(value_buffer, n); - }; - case (#Int8, #Int8(i8)) { - ref_primitive_type_buffer.add(T.TypeCode.Int8); - value_buffer.add(Int8.toNat8(i8)); - }; - case (#Int16, #Int16(i16)) { - ref_primitive_type_buffer.add(T.TypeCode.Int16); - let n16 = Int16.toNat16(i16); - value_buffer.add((n16 & 0xFF) |> Nat16.toNat8(_)); - value_buffer.add((n16 >> 8) |> Nat16.toNat8(_)); - }; - case (#Int32, #Int32(i32)) { - ref_primitive_type_buffer.add(T.TypeCode.Int32); - let n = Int32.toNat32(i32); - - value_buffer.add((n & 0xFF) |> Nat32.toNat16(_) |> Nat16.toNat8(_)); - value_buffer.add(((n >> 8) & 0xFF) |> Nat32.toNat16(_) |> Nat16.toNat8(_)); - value_buffer.add(((n >> 16) & 0xFF) |> Nat32.toNat16(_) |> Nat16.toNat8(_)); - value_buffer.add((n >> 24) |> Nat32.toNat16(_) |> Nat16.toNat8(_)); - }; - case (#Int64, #Int64(i64)) { - ref_primitive_type_buffer.add(T.TypeCode.Int64); - let n = Int64.toNat64(i64); - - value_buffer.add((n & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - value_buffer.add(((n >> 8) & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - value_buffer.add(((n >> 16) & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - value_buffer.add(((n >> 24) & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - value_buffer.add(((n >> 32) & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - value_buffer.add(((n >> 40) & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - value_buffer.add(((n >> 48) & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - value_buffer.add((n >> 56) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - }; - case (#Float, #Float(f64)) { - ref_primitive_type_buffer.add(T.TypeCode.Float); - let floatX : FloatX.FloatX = FloatX.fromFloat(f64, #f64); - FloatX.encode(value_buffer, floatX, #lsb); - }; - case (#Bool, #Bool(b)) { - ref_primitive_type_buffer.add(T.TypeCode.Bool); - value_buffer.add(if (b) (1) else (0)); - }; - case (#Null, #Null) { - ref_primitive_type_buffer.add(T.TypeCode.Null); - }; - case (#Empty, #Empty) { - ref_primitive_type_buffer.add(T.TypeCode.Empty); - }; - case (#Text, #Text(t)) { - ref_primitive_type_buffer.add(T.TypeCode.Text); + for ((k, v) in record_entries.vals()) { + let field_value_key = get_renamed_key(renaming_map, k); + ignore Map.put(record_entry_cache, thash, field_value_key, v); + }; - let utf8_bytes = Blob.toArray(Text.encodeUtf8(t)); - unsigned_leb128(value_buffer, utf8_bytes.size()); + var i = 0; + while (i < record_types.size()) { + let field_type = record_types[i].1; - var i = 0; - while (i < utf8_bytes.size()) { - value_buffer.add(utf8_bytes[i]); - i += 1; - }; + let field_type_key = get_renamed_key(renaming_map, record_types[i].0); - }; - case (#Principal, #Principal(p)) { - ref_primitive_type_buffer.add(T.TypeCode.Principal); - - value_buffer.add(0x01); // indicate transparency state - let bytes = Blob.toArray(Principal.toBlob(p)); - unsigned_leb128(value_buffer, bytes.size()); - - var i = 0; - while (i < bytes.size()) { - value_buffer.add(bytes[i]); - i += 1; - }; - }; + let field_value = switch (Map.get(record_entry_cache, Map.thash, field_type_key)) { + case (?field_value) field_value; + case (_) Debug.trap("unable to find field key in field types: " # debug_show field_type_key # "in " # debug_show record_entries); + }; - case (_) Debug.trap("unknown (type, value) pair: " # debug_show (candid_type, candid_value)); + let value_type_is_compound = is_compound_type(field_type); + + ignore encode_candid( + field_type, + field_value, + compound_type_buffer, + candid_type_buffer, + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + true, + type_exists or not value_type_is_compound, + ); + + i += 1; }; - }; - func encode_compound_type( - candid_type : CandidType, - candid_value : Candid, - compound_type_buffer : Buffer, - primitive_type_buffer : Buffer, - value_buffer : Buffer, - renaming_map : Map, - unique_compound_type_map : Map, - recursive_map : Map, - counter : [var Nat], - is_nested_child_of_compound_type : Bool, - _type_exists : Bool, - ) { - - // Debug.print("encode_compound_type(): " # debug_show (candid_type, candid_value)); - - // ----------------- Compound Types ----------------- // - - // encode_candid type only - // case (candid_type, #Null) { - // encode_nested_type(candid_type, compound_type_buffer); - // }; + if (not type_exists) { + compound_type_buffer.add(T.TypeCode.Record); + unsigned_leb128(compound_type_buffer, record_types.size()); - let type_info = get_type_info(candid_type); + i := 0; - // type_exists_in_compound_type_sequence - let type_exists = _type_exists or Map.has(unique_compound_type_map, thash, type_info); - - switch (candid_type, candid_value) { - - case (#Option(opt_type), #Option(opt_value)) { - - let opt_type_is_compound = is_compound_type(opt_type); - - if (not type_exists and not opt_type_is_compound) { - compound_type_buffer.add(T.TypeCode.Option); - }; - - if (opt_value == #Null and opt_type != #Null) { - // a result of being able to set #Null at any point in an #Option type - // for instance, type #Option(#Nat) with value #Null - - value_buffer.add(0); // no value - - if (not type_exists) encode_type_only( - opt_type, - compound_type_buffer, - primitive_type_buffer, - renaming_map, - unique_compound_type_map, - counter, - true, - ); - - } else { - value_buffer.add(1); // has value - - ignore encode_candid( - opt_type, - opt_value, - compound_type_buffer, - primitive_type_buffer, - value_buffer, - renaming_map, - unique_compound_type_map, - recursive_map, - counter, - true, - type_exists, - ); - }; - - if ( - not type_exists and opt_type_is_compound - ) { - // let prev_start = get_prev_compound_type_start_index(compound_type_buffer); - compound_type_buffer.add(T.TypeCode.Option); - let opt_type_info = get_type_info(opt_type); - let pos = switch (Map.get(unique_compound_type_map, thash, opt_type_info)) { - case (?pos) pos; - case (_) Debug.trap("unable to find compound type pos to store in primitive type sequence for " # debug_show (type_info)); - }; - unsigned_leb128(compound_type_buffer, pos); - }; - }; + while (i < record_types.size()) { + let field_type = record_types[i].1; - // a result of being able to set #Null at any point in an #Option type - // for instance, type #Option(#Nat) with value #Null - case (#Option(opt_type), #Null) { - value_buffer.add(0); // no value - - let opt_type_is_compound = is_compound_type(opt_type); - - if (not type_exists and not opt_type_is_compound) { - compound_type_buffer.add(T.TypeCode.Option); - }; - - if (not type_exists) encode_type_only( - opt_type, - compound_type_buffer, - primitive_type_buffer, - renaming_map, - unique_compound_type_map, - counter, - true, - ); - - if ( - not type_exists and opt_type_is_compound - ) { - compound_type_buffer.add(T.TypeCode.Option); - let opt_type_info = get_type_info(opt_type); - let pos = switch (Map.get(unique_compound_type_map, thash, opt_type_info)) { - case (?pos) pos; - case (_) Debug.trap("unable to find compound type pos to store in primitive type sequence for " # debug_show (type_info, opt_type)); - }; - unsigned_leb128(compound_type_buffer, pos); - }; + let value_type_is_compound = is_compound_type(field_type); + if (is_tuple) { + unsigned_leb128(compound_type_buffer, i); + } else { + let record_key = get_renamed_key(renaming_map, record_types[i].0); + let hash_key = hash_record_key(record_key); + unsigned_leb128(compound_type_buffer, Nat32.toNat(hash_key)); }; - case (#Array(arr_type), #Array(arr_values)) { - let arr_type_is_compound = is_compound_type(arr_type); - - if (not type_exists and not arr_type_is_compound) { - compound_type_buffer.add(T.TypeCode.Array); - }; - - // if (not type_exists) - - unsigned_leb128(value_buffer, arr_values.size()); - - var i = 0; - - if (arr_values.size() == 0 and not type_exists) { - encode_type_only( - arr_type, - compound_type_buffer, - primitive_type_buffer, - renaming_map, - unique_compound_type_map, - counter, - true, - ); - } else while (i < arr_values.size()) { - let val = arr_values[i]; - - ignore encode_candid( - arr_type, - val, - compound_type_buffer, - primitive_type_buffer, - value_buffer, - renaming_map, - unique_compound_type_map, - recursive_map, - counter, - true, - type_exists or i > 0, - ); - - i += 1; - }; - - if (not type_exists and arr_type_is_compound) { - compound_type_buffer.add(T.TypeCode.Array); - - let arr_type_info = get_type_info(arr_type); - let pos = switch (Map.get(unique_compound_type_map, thash, arr_type_info)) { - case (?pos) pos; - case (_) Debug.trap("unable to find compound type pos to store in primitive type sequence for " # debug_show (type_info, arr_type)); - }; - unsigned_leb128(compound_type_buffer, pos); - - }; + if (value_type_is_compound) { + let value_type_info = get_type_info(field_type); + let pos = switch (Map.get(unique_compound_type_map, thash, value_type_info)) { + case (?pos) pos; + case (_) Debug.trap("unable to find compound type pos to store in primitive type sequence for " # debug_show (type_info, field_type)); + }; - }; - case (#Blob, #Blob(blob)) { - let bytes = Array.map(Blob.toArray(blob), func(n : Nat8) : Candid = #Nat8(n)); - - return encode_compound_type( - #Array(#Nat8), - #Array(bytes), - compound_type_buffer, - primitive_type_buffer, - value_buffer, - renaming_map, - unique_compound_type_map, - recursive_map, - counter, - is_nested_child_of_compound_type, - type_exists, - ); + unsigned_leb128(compound_type_buffer, pos); + } else { + encode_primitive_type_only( + field_type, + compound_type_buffer, + candid_type_buffer, + true, + ); }; - case (#Array(#Nat8), #Blob(blob)) { - let bytes = Array.map(Blob.toArray(blob), func(n : Nat8) : Candid = #Nat8(n)); - - return encode_compound_type( - #Array(#Nat8), - #Array(bytes), - compound_type_buffer, - primitive_type_buffer, - value_buffer, - renaming_map, - unique_compound_type_map, - recursive_map, - counter, - is_nested_child_of_compound_type, - type_exists, - ); - }; - case (#Blob, #Array(bytes)) { - if (bytes.size() > 0) { - switch (bytes[0]) { - case (#Nat8(_)) {}; - case (_) return Debug.trap("invalid blob value: expected array of Nat8, got array of " # debug_show bytes[0]); - }; - }; - - return encode_compound_type( - #Array(#Nat8), - #Array(bytes), - compound_type_buffer, - primitive_type_buffer, - value_buffer, - renaming_map, - unique_compound_type_map, - recursive_map, - counter, - is_nested_child_of_compound_type, - type_exists, - ); - }; + i += 1; + }; + }; - case (#Record(record_types) or #Map(record_types), #Record(record_entries) or #Map(record_entries)) { - assert record_types.size() >= record_entries.size(); - - let is_tuple = check_is_tuple(record_types); - - let sorted_record_entries = Array.tabulate<(Text, Candid)>( - record_types.size(), - func(i : Nat) : (Text, Candid) { - let field_type_key = get_renamed_key(renaming_map, record_types[i].0); - let res = Array.find<(Text, Candid)>( - record_entries, - func((field_value_key, _) : (Text, Candid)) : Bool { - get_renamed_key(renaming_map, field_value_key) == field_type_key; - }, - ); - - switch (res) { - case (?(_, field_value)) (field_type_key, field_value); - case (_) Debug.trap("unable to find field key in field types: " # debug_show field_type_key # "in " # debug_show record_entries); - }; - }, - ); - - var i = 0; - while (i < record_types.size()) { - let value_type = record_types[i].1; - let field_value = sorted_record_entries[i].1; - - let value_type_is_compound = is_compound_type(value_type); - - ignore encode_candid( - value_type, - field_value, - compound_type_buffer, - primitive_type_buffer, - value_buffer, - renaming_map, - unique_compound_type_map, - recursive_map, - counter, - true, - type_exists or not value_type_is_compound, - ); - - i += 1; - }; - - if (not type_exists) { - compound_type_buffer.add(T.TypeCode.Record); - unsigned_leb128(compound_type_buffer, record_entries.size()); - - i := 0; - - while (i < record_types.size()) { - let value_type = record_types[i].1; - - let value_type_is_compound = is_compound_type(value_type); - - if (is_tuple) { - unsigned_leb128(compound_type_buffer, i); - } else { - let record_key = get_renamed_key(renaming_map, record_types[i].0); - let hash_key = hash_record_key(record_key); - unsigned_leb128(compound_type_buffer, Nat32.toNat(hash_key)); - }; - - if (value_type_is_compound) { - let value_type_info = get_type_info(value_type); - let pos = switch (Map.get(unique_compound_type_map, thash, value_type_info)) { - case (?pos) pos; - case (_) Debug.trap("unable to find compound type pos to store in primitive type sequence for " # debug_show (type_info, value_type)); - }; - - unsigned_leb128(compound_type_buffer, pos); - } else { - encode_primitive_type_only( - value_type, - compound_type_buffer, - primitive_type_buffer, - true, - ); - }; - - i += 1; - }; - }; + }; + case (#Tuple(tuple_types), #Tuple(tuple_values)) { + return encode_compound_type( + #Record(tuple_type_to_record(tuple_types)), + #Record(tuple_value_to_record(tuple_values)), + compound_type_buffer, + candid_type_buffer, + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + is_nested_child_of_compound_type, + type_exists, + ); + }; + case (#Tuple(tuple_types), #Record(tuple_values)) { + var i = 0; + assert Itertools.all( + tuple_values.vals(), + func((k, v) : (Text, Any)) : Bool { + i += 1; + Utils.text_is_number(k) and Utils.text_to_nat(k) == (i - 1 : Nat); + }, + ); - }; - case (#Tuple(tuple_types), #Tuple(tuple_values)) { - return encode_compound_type( - #Record(tuple_type_to_record(tuple_types)), - #Record(tuple_value_to_record(tuple_values)), - compound_type_buffer, - primitive_type_buffer, - value_buffer, - renaming_map, - unique_compound_type_map, - recursive_map, - counter, - is_nested_child_of_compound_type, - type_exists, - ); - }; - case (#Tuple(tuple_types), #Record(tuple_values)) { - var i = 0; - assert Itertools.all( - tuple_values.vals(), - func((k, v) : (Text, Any)) : Bool { - i += 1; - Utils.text_is_number(k) and Utils.text_to_nat(k) == (i - 1 : Nat); - }, - ); - - return encode_compound_type( - #Record(tuple_type_to_record(tuple_types)), - #Record(tuple_values), - compound_type_buffer, - primitive_type_buffer, - value_buffer, - renaming_map, - unique_compound_type_map, - recursive_map, - counter, - is_nested_child_of_compound_type, - type_exists, - ); - }; + return encode_compound_type( + #Record(tuple_type_to_record(tuple_types)), + #Record(tuple_values), + compound_type_buffer, + candid_type_buffer, + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + is_nested_child_of_compound_type, + type_exists, + ); + }; - case (#Record(record_types), #Tuple(tuple_values)) { - var i = 0; - assert Itertools.all( - record_types.vals(), - func((k, v) : (Text, Any)) : Bool { - i += 1; - Utils.text_is_number(k) and Utils.text_to_nat(k) == (i - 1 : Nat); - }, - ); - - return encode_compound_type( - #Record(record_types), - #Record(tuple_value_to_record(tuple_values)), - compound_type_buffer, - primitive_type_buffer, - value_buffer, - renaming_map, - unique_compound_type_map, - recursive_map, - counter, - is_nested_child_of_compound_type, - type_exists, - ); - }; + case (#Record(record_types), #Tuple(tuple_values)) { + var i = 0; + assert Itertools.all( + record_types.vals(), + func((k, v) : (Text, Any)) : Bool { + i += 1; + Utils.text_is_number(k) and Utils.text_to_nat(k) == (i - 1 : Nat); + }, + ); - case (#Variant(variant_types), #Variant(variant)) { - let variant_key = get_renamed_key(renaming_map, variant.0); - let variant_value = variant.1; - - let variant_index_res = Array.indexOf<(Text, CandidType)>( - (variant_key, #Empty), // attach #Empty variant type to satisfy the type checker - variant_types, - func((a, _) : (Text, CandidType), (b, _) : (Text, CandidType)) : Bool = a == b, - ); - - let variant_index = switch (variant_index_res) { - case (?index) index; - case (_) Debug.trap("unable to find variant key in variant types"); - }; - - var i = 0; - while (i < variant_types.size()) { - let variant_key = variant_types[i].0; - let variant_type = variant_types[i].1; - - let variant_type_is_compound = is_compound_type(variant_type); - - if (i == variant_index) { - unsigned_leb128(value_buffer, i); - - ignore encode_candid( - variant_type, - variant_value, - compound_type_buffer, - primitive_type_buffer, - value_buffer, - renaming_map, - unique_compound_type_map, - recursive_map, - counter, - true, - type_exists or not variant_type_is_compound, - ); - } else if (variant_type_is_compound) { - encode_compound_type_only( - variant_type, - compound_type_buffer, - primitive_type_buffer, - renaming_map, - unique_compound_type_map, - counter, - true, - ); - }; - - i += 1; - }; - - if (not type_exists) { - compound_type_buffer.add(T.TypeCode.Variant); - unsigned_leb128(compound_type_buffer, variant_types.size()); - - i := 0; - while (i < variant_types.size()) { - let variant_key = get_renamed_key(renaming_map, variant_types[i].0); - let variant_type = variant_types[i].1; - let variant_type_is_compound = is_compound_type(variant_type); - - let hash_key = hash_record_key(variant_key); - unsigned_leb128(compound_type_buffer, Nat32.toNat(hash_key)); - - if (variant_type_is_compound) { - let variant_type_info = get_type_info(variant_type); - let pos = switch (Map.get(unique_compound_type_map, thash, variant_type_info)) { - case (?pos) pos; - case (_) Debug.trap("unable to find compound type pos to store in primitive type sequence for " # debug_show (type_info)); - }; - unsigned_leb128(compound_type_buffer, pos); - } else { - encode_primitive_type_only( - variant_type, - compound_type_buffer, - primitive_type_buffer, - true, - ); - }; - - i += 1; - }; - }; + return encode_compound_type( + #Record(record_types), + #Record(tuple_value_to_record(tuple_values)), + compound_type_buffer, + candid_type_buffer, + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + is_nested_child_of_compound_type, + type_exists, + ); + }; - }; + case (#Variant(variant_types), #Variant(variant)) { + let variant_key = get_renamed_key(renaming_map, variant.0); + let variant_value = variant.1; - case (_) Debug.trap("invalid (type, value) pair: " # debug_show { candid_type; candid_value }); + let variant_index_res = Array.indexOf<(Text, CandidType)>( + (variant_key, #Empty), // attach #Empty variant type to satisfy the type checker + variant_types, + func((a, _) : (Text, CandidType), (b, _) : (Text, CandidType)) : Bool = a == b, + ); + + let variant_index = switch (variant_index_res) { + case (?index) index; + case (_) Debug.trap("unable to find variant key in variant types"); }; - if (not type_exists) { - var pos = counter[C.COUNTER.COMPOUND_TYPE]; - counter[C.COUNTER.COMPOUND_TYPE] += 1; + var i = 0; + while (i < variant_types.size()) { + let variant_key = variant_types[i].0; + let variant_type = variant_types[i].1; - ignore Map.put(unique_compound_type_map, thash, type_info, pos); + let variant_type_is_compound = is_compound_type(variant_type); + + if (i == variant_index) { + unsigned_leb128(value_buffer, i); + + ignore encode_candid( + variant_type, + variant_value, + compound_type_buffer, + candid_type_buffer, + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + true, + type_exists or not variant_type_is_compound, + ); + } else if (variant_type_is_compound) { + encode_compound_type_only( + variant_type, + compound_type_buffer, + candid_type_buffer, + renaming_map, + unique_compound_type_map, + counter, + true, + ); + }; + + i += 1; }; - // if it is the top level parent and not one of the nested children - if (not is_nested_child_of_compound_type) { - let pos = switch (Map.get(unique_compound_type_map, thash, type_info)) { + if (not type_exists) { + compound_type_buffer.add(T.TypeCode.Variant); + unsigned_leb128(compound_type_buffer, variant_types.size()); + + i := 0; + while (i < variant_types.size()) { + let variant_key = get_renamed_key(renaming_map, variant_types[i].0); + let variant_type = variant_types[i].1; + let variant_type_is_compound = is_compound_type(variant_type); + + let hash_key = hash_record_key(variant_key); + unsigned_leb128(compound_type_buffer, Nat32.toNat(hash_key)); + + if (variant_type_is_compound) { + let variant_type_info = get_type_info(variant_type); + let pos = switch (Map.get(unique_compound_type_map, thash, variant_type_info)) { case (?pos) pos; case (_) Debug.trap("unable to find compound type pos to store in primitive type sequence for " # debug_show (type_info)); + }; + unsigned_leb128(compound_type_buffer, pos); + } else { + encode_primitive_type_only( + variant_type, + compound_type_buffer, + candid_type_buffer, + true, + ); }; - unsigned_leb128(primitive_type_buffer, pos); + + i += 1; + }; }; + + }; + + case (_) Debug.trap("invalid (type, value) pair: " # debug_show { candid_type; candid_value }); }; - func encode_candid( - candid_type : CandidType, - candid_value : Candid, - compound_type_buffer : Buffer, - primitive_type_buffer : Buffer, - value_buffer : Buffer, - renaming_map : Map, - unique_compound_type_map : Map, - recursive_map : Map, - counter : [var Nat], - is_nested_child_of_compound_type : Bool, - ignore_type : Bool, - ) : ?Hash { - - let candid_is_compound_type = is_compound_type(candid_type); - - if (candid_is_compound_type) { - encode_compound_type( - candid_type, - candid_value, - compound_type_buffer, - primitive_type_buffer, - value_buffer, - renaming_map, - unique_compound_type_map, - recursive_map, - counter, - is_nested_child_of_compound_type, - ignore_type, - ); - } else { - encode_primitive_type( - candid_type, - candid_value, - compound_type_buffer, - primitive_type_buffer, - value_buffer, - renaming_map, - unique_compound_type_map, - recursive_map, - is_nested_child_of_compound_type, - ignore_type, - ); - }; + if (not type_exists) { + var pos = counter[C.COUNTER.COMPOUND_TYPE]; + counter[C.COUNTER.COMPOUND_TYPE] += 1; + + ignore Map.put(unique_compound_type_map, thash, type_info, pos); + }; - null; + // if it is the top level parent and not one of the nested children + if (not is_nested_child_of_compound_type) { + let pos = switch (Map.get(unique_compound_type_map, thash, type_info)) { + case (?pos) pos; + case (_) Debug.trap("unable to find compound type pos to store in primitive type sequence for " # debug_show (type_info)); + }; + unsigned_leb128(candid_type_buffer, pos); }; - type InternalCandidTypes = { - #Int; - #Int8; - #Int16; - #Int32; - #Int64; - - #Nat; - #Nat8; - #Nat16; - #Nat32; - #Nat64; - #Bool; - #Float; - #Text; - #Blob; - #Null; - #Empty; - #Principal; - - #Option : InternalCandidTypes; - #Array : [InternalCandidTypes]; - #Record : [(Text, InternalCandidTypes)]; - // #Map : [(Text, InternalCandidTypes)]; - #Tuple : [InternalCandidTypes]; - #Variant : [(Text, InternalCandidTypes)]; - #Recursive : (Nat, InternalCandidTypes); + }; + + func encode_candid( + candid_type : CandidType, + candid_value : Candid, + compound_type_buffer : Buffer, + candid_type_buffer : Buffer, + value_buffer : Buffer, + renaming_map : Map, + unique_compound_type_map : Map, + recursive_map : Map, + counter : [var Nat], + is_nested_child_of_compound_type : Bool, + ignore_type : Bool, + ) : ?Hash { + + let candid_is_compound_type = is_compound_type(candid_type); + + if (candid_is_compound_type) { + encode_compound_type( + candid_type, + candid_value, + compound_type_buffer, + candid_type_buffer, + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + is_nested_child_of_compound_type, + ignore_type, + ); + } else { + encode_primitive_type( + candid_type, + candid_value, + compound_type_buffer, + candid_type_buffer, + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + is_nested_child_of_compound_type, + ignore_type, + ); }; - type InternalCandidTypeNode = { - type_ : InternalCandidTypes; - height : Nat; - parent_index : Nat; - key : ?Text; + null; + }; + type InternalCandidTypes = { + #Int; + #Int8; + #Int16; + #Int32; + #Int64; + + #Nat; + #Nat8; + #Nat16; + #Nat32; + #Nat64; + #Bool; + #Float; + #Text; + #Blob; + #Null; + #Empty; + #Principal; + + #Option : InternalCandidTypes; + #Array : [InternalCandidTypes]; + #Record : [(Text, InternalCandidTypes)]; + // #Map : [(Text, InternalCandidTypes)]; + #Tuple : [InternalCandidTypes]; + #Variant : [(Text, InternalCandidTypes)]; + #Recursive : (Nat, InternalCandidTypes); + }; + + type InternalCandidTypeNode = { + type_ : InternalCandidTypes; + height : Nat; + parent_index : Nat; + key : ?Text; + }; + + type CandidTypeNode = { + type_ : CandidType; + height : Nat; + parent_index : Nat; + key : ?Text; + }; + + func to_candid_types(candid : Candid, renaming_map : Map) : (InternalCandidTypes) { + switch (candid) { + case (#Nat(_)) (#Nat); + case (#Nat8(_)) (#Nat8); + case (#Nat16(_)) (#Nat16); + case (#Nat32(_)) (#Nat32); + case (#Nat64(_)) (#Nat64); + + case (#Int(_)) (#Int); + case (#Int8(_)) (#Int8); + case (#Int16(_)) (#Int16); + case (#Int32(_)) (#Int32); + case (#Int64(_)) (#Int64); + + case (#Float(_)) (#Float); + + case (#Bool(_)) (#Bool); + + case (#Principal(_)) (#Principal); + + case (#Text(_)) (#Text); + + case (#Null) (#Null); + case (#Empty) (#Empty); + + case (#Blob(_)) { #Array([#Nat8]) }; + + case (#Option(optType)) { + let inner_type = to_candid_types(optType, renaming_map); + #Option(inner_type); + }; + case (#Array(arr)) { + let inner_types = Buffer.Buffer(arr.size()); + + for (item in arr.vals()) { + let (inner_type) = to_candid_types(item, renaming_map); + inner_types.add(inner_type); + }; + + let types = Buffer.toArray(inner_types); + + (#Array(types)); + }; + + case (#Record(records) or #Map(records)) { + let types_buffer = Buffer.Buffer<(Text, InternalCandidTypes)>(records.size()); + + for ((record_key, record_val) in records.vals()) { + let (inner_type) = to_candid_types(record_val, renaming_map); + + types_buffer.add((record_key, inner_type)); + }; + + let types = Buffer.toArray(types_buffer); + + #Record(types); + }; + + case (#Tuple(tuple_values)) { + let tuple_types = Array.map( + tuple_values, + func(c : Candid) : InternalCandidTypes { + to_candid_types(c, renaming_map); + }, + ); + + #Tuple(tuple_types); + }; + + case (#Variant((key, val))) { + let (inner_type) = to_candid_types(val, renaming_map); + + #Variant([(key, inner_type)]); + }; }; - type CandidTypeNode = { - type_ : CandidType; - height : Nat; - parent_index : Nat; - key : ?Text; + }; + + func internal_to_candid_type(internal_type : InternalCandidTypes, vec_index : ?Nat) : CandidType { + switch (internal_type, vec_index) { + case (#Array(vec_types), ?vec_index) #Array(internal_to_candid_type(vec_types[vec_index], null)); + case (#Array(vec_types), _) #Array(internal_to_candid_type(vec_types[0], null)); + case (#Option(opt_type), _) #Option(internal_to_candid_type(opt_type, null)); + case (#Record(record_types), _) { + let new_record_types = Array.map<(Text, InternalCandidTypes), (Text, CandidType)>( + record_types, + func((key, field_type) : (Text, InternalCandidTypes)) : (Text, CandidType) { + let inner_type = internal_to_candid_type(field_type, null); + (key, inner_type); + }, + ); + + #Record(new_record_types); + }; + case (#Tuple(tuple_types), _) { + let new_tuple_types = Array.map( + tuple_types, + func(inner_type : InternalCandidTypes) : CandidType { + internal_to_candid_type(inner_type, null); + }, + ); + + #Tuple(new_tuple_types); + }; + case (#Variant(variant_types), _) { + let new_variant_types = Array.map<(Text, InternalCandidTypes), (Text, CandidType)>( + variant_types, + func((key, variant_type) : (Text, InternalCandidTypes)) : (Text, CandidType) { + let inner_type = internal_to_candid_type(variant_type, null); + (key, inner_type); + }, + ); + + #Variant(new_variant_types); + }; + case (#Recursive(n, _), _) #Recursive(n); + + case (#Int, _) #Int; + case (#Int8, _) #Int8; + case (#Int16, _) #Int16; + case (#Int32, _) #Int32; + case (#Int64, _) #Int64; + + case (#Nat, _) #Nat; + case (#Nat8, _) #Nat8; + case (#Nat16, _) #Nat16; + case (#Nat32, _) #Nat32; + case (#Nat64, _) #Nat64; + + case (#Bool, _) #Bool; + case (#Float, _) #Float; + case (#Text, _) #Text; + case (#Blob, _) #Blob; + case (#Null, _) #Null; + case (#Empty, _) #Empty; + case (#Principal, _) #Principal; + }; + }; + + func to_candid_record_field_type(node : CandidTypeNode) : (Text, CandidType) { + let ?key = node.key else return Debug.trap("to_candid_record_field_type: key is null"); + return (key, node.type_); + }; - func to_candid_types(candid : Candid, renaming_map : Map) : (InternalCandidTypes) { - switch (candid) { - case (#Nat(_)) (#Nat); - case (#Nat8(_)) (#Nat8); - case (#Nat16(_)) (#Nat16); - case (#Nat32(_)) (#Nat32); - case (#Nat64(_)) (#Nat64); + func merge_candid_variants_and_array_types(rows : Buffer<[InternalCandidTypeNode]>) : Result { + let buffer = Buffer.Buffer(8); - case (#Int(_)) (#Int); - case (#Int8(_)) (#Int8); - case (#Int16(_)) (#Int16); - case (#Int32(_)) (#Int32); - case (#Int64(_)) (#Int64); + func calc_height(parent : Nat, child : Nat) : Nat = parent + child; - case (#Float(_)) (#Float); + let ?_bottom = rows.removeLast() else return #err("trying to pop bottom but rows is empty"); - case (#Bool(_)) (#Bool); + var bottom = Array.map( + _bottom, + func(node : InternalCandidTypeNode) : CandidTypeNode = { + type_ = internal_to_candid_type(node.type_, null); + height = node.height; + parent_index = node.parent_index; + key = node.key; + }, + ); - case (#Principal(_)) (#Principal); + while (rows.size() > 0) { - case (#Text(_)) (#Text); + let ?above_bottom = rows.removeLast() else return #err("trying to pop above_bottom but rows is empty"); - case (#Null) (#Null); - case (#Empty) (#Empty); + var bottom_iter = Itertools.peekable(bottom.vals()); - case (#Blob(_)) { #Array([#Nat8]) }; + let variants = Buffer.Buffer<(Text, CandidType)>(bottom.size()); + let variant_indexes = Buffer.Buffer(bottom.size()); - case (#Option(optType)) { - let inner_type = to_candid_types(optType, renaming_map); - #Option(inner_type); + for ((index, parent_node) in Itertools.enumerate(above_bottom.vals())) { + let tmp_bottom_iter = PeekableIter.takeWhile(bottom_iter, func({ parent_index; key } : CandidTypeNode) : Bool = index == parent_index); + let { parent_index; key = parent_key } = parent_node; + + switch (parent_node.type_) { + case (#Option(_)) { + let ?child_node = tmp_bottom_iter.next() else return #err(" #Option error: no item in tmp_bottom_iter"); + + let merged_node : CandidTypeNode = { + type_ = #Option(child_node.type_); + height = calc_height(parent_node.height, child_node.height); + parent_index; + key = parent_key; + }; + buffer.add(merged_node); + }; + case (#Array(_)) { + let vec_nodes = Iter.toArray(tmp_bottom_iter); + + let max = { + var height = 0; + var type_ : CandidType = #Empty; + }; + + for (node in vec_nodes.vals()) { + if (max.height < node.height) { + max.height := node.height; + max.type_ := node.type_; + }; }; - case (#Array(arr)) { - let inner_types = Buffer.Buffer(arr.size()); - for (item in arr.vals()) { - let (inner_type) = to_candid_types(item, renaming_map); - inner_types.add(inner_type); - }; + let best_node : CandidTypeNode = { + type_ = #Array(max.type_); + height = calc_height(parent_node.height, max.height); + parent_index; + key = parent_key; + }; - let types = Buffer.toArray(inner_types); + buffer.add(best_node); + }; + case (#Record(_)) { + var height = 0; - (#Array(types)); + func get_max_height(item : CandidTypeNode) { + height := Nat.max(height, item.height); }; - case (#Record(records) or #Map(records)) { - let types_buffer = Buffer.Buffer<(Text, InternalCandidTypes)>(records.size()); + var record_fields : [(Text, CandidType)] = Iter.toArray( + Iter.map( + tmp_bottom_iter, + func(node : CandidTypeNode) : (Text, CandidType) { + get_max_height(node); + to_candid_record_field_type(node); + }, + ) + ); - for ((record_key, record_val) in records.vals()) { - let (inner_type) = to_candid_types(record_val, renaming_map); + let merged_node : CandidTypeNode = { + type_ = #Record(record_fields); + height = calc_height(parent_node.height, height); + parent_index; + key = parent_key; + }; + buffer.add(merged_node); + }; + case (#Variant(_)) { + var height = 0; - types_buffer.add((record_key, inner_type)); - }; + func get_max_height(item : CandidTypeNode) { + height := Nat.max(height, item.height); + }; - let types = Buffer.toArray(types_buffer); + var variant_types : [(Text, CandidType)] = Iter.toArray( + Iter.map( + tmp_bottom_iter, + func(node : CandidTypeNode) : (Text, CandidType) { + get_max_height(node); + to_candid_record_field_type(node); + }, + ) + ); - #Record(types); + for (variant_type in variant_types.vals()) { + variants.add(variant_type); }; - case (#Tuple(tuple_values)) { - let tuple_types = Array.map( - tuple_values, - func(c : Candid) : InternalCandidTypes { - to_candid_types(c, renaming_map); - }, - ); + variant_indexes.add(buffer.size()); - #Tuple(tuple_types); + let merged_node : CandidTypeNode = { + type_ = #Variant(variant_types); + height = calc_height(parent_node.height, height); + parent_index; + key = parent_key; }; - case (#Variant((key, val))) { - let (inner_type) = to_candid_types(val, renaming_map); + buffer.add(merged_node); - #Variant([(key, inner_type)]); + }; + case (_) { + let new_parent_node : CandidTypeNode = { + type_ = internal_to_candid_type(parent_node.type_, null); + height = parent_node.height; + parent_index; + key = parent_key; }; + + buffer.add(new_parent_node); + }; }; + }; - }; + if (variants.size() > 0) { + let full_variant_type : CandidType = #Variant(Buffer.toArray(variants)); - func internal_to_candid_type(internal_type : InternalCandidTypes, vec_index : ?Nat) : CandidType { - switch (internal_type, vec_index) { - case (#Array(vec_types), ?vec_index) #Array(internal_to_candid_type(vec_types[vec_index], null)); - case (#Array(vec_types), _) #Array(internal_to_candid_type(vec_types[0], null)); - case (#Option(opt_type), _) #Option(internal_to_candid_type(opt_type, null)); - case (#Record(record_types), _) { - let new_record_types = Array.map<(Text, InternalCandidTypes), (Text, CandidType)>( - record_types, - func((key, field_type) : (Text, InternalCandidTypes)) : (Text, CandidType) { - let inner_type = internal_to_candid_type(field_type, null); - (key, inner_type); - }, - ); - - #Record(new_record_types); - }; - case (#Tuple(tuple_types), _) { - let new_tuple_types = Array.map( - tuple_types, - func(inner_type : InternalCandidTypes) : CandidType { - internal_to_candid_type(inner_type, null); - }, - ); - - #Tuple(new_tuple_types); - }; - case (#Variant(variant_types), _) { - let new_variant_types = Array.map<(Text, InternalCandidTypes), (Text, CandidType)>( - variant_types, - func((key, variant_type) : (Text, InternalCandidTypes)) : (Text, CandidType) { - let inner_type = internal_to_candid_type(variant_type, null); - (key, inner_type); - }, - ); - - #Variant(new_variant_types); - }; - case (#Recursive(n, _), _) #Recursive(n); - - case (#Int, _) #Int; - case (#Int8, _) #Int8; - case (#Int16, _) #Int16; - case (#Int32, _) #Int32; - case (#Int64, _) #Int64; - - case (#Nat, _) #Nat; - case (#Nat8, _) #Nat8; - case (#Nat16, _) #Nat16; - case (#Nat32, _) #Nat32; - case (#Nat64, _) #Nat64; - - case (#Bool, _) #Bool; - case (#Float, _) #Float; - case (#Text, _) #Text; - case (#Blob, _) #Blob; - case (#Null, _) #Null; - case (#Empty, _) #Empty; - case (#Principal, _) #Principal; + for (index in variant_indexes.vals()) { + let prev_node = buffer.get(index); + let new_node : CandidTypeNode = { + type_ = full_variant_type; + height = prev_node.height; + parent_index = prev_node.parent_index; + key = prev_node.key; + }; + buffer.put(index, new_node); }; - }; + }; - func to_candid_record_field_type(node : CandidTypeNode) : (Text, CandidType) { - let ?key = node.key else return Debug.trap("to_candid_record_field_type: key is null"); - return (key, node.type_); + bottom := Buffer.toArray(buffer); + buffer.clear(); }; - func merge_candid_variants_and_array_types(rows : Buffer<[InternalCandidTypeNode]>) : Result { - let buffer = Buffer.Buffer(8); + let merged_type = bottom[0].type_; + #ok(merged_type); + }; - func calc_height(parent : Nat, child : Nat) : Nat = parent + child; + func get_candid_height_value(type_ : InternalCandidTypes) : Nat { + switch (type_) { + case (#Empty or #Null) 0; + case (_) 1; + }; + }; - let ?_bottom = rows.removeLast() else return #err("trying to pop bottom but rows is empty"); + // for most of the typs we can easily retrieve it from the value, but for an array it becomes a bit tricky + // because of optional values we can have seemingly different types in the array + // for example type [?Nat] with values [null, ?1], for each values will have a inferred type of [#Option(#Null), #Option(#Nat)] + // We need a way to choose #Option(#Nat) over #Option(#Null) in this case - var bottom = Array.map( - _bottom, - func(node : InternalCandidTypeNode) : CandidTypeNode = { - type_ = internal_to_candid_type(node.type_, null); - height = node.height; - parent_index = node.parent_index; - key = node.key; - }, - ); + func order_candid_types_by_height_bfs(rows : Buffer<[InternalCandidTypeNode]>) { - while (rows.size() > 0) { - - let ?above_bottom = rows.removeLast() else return #err("trying to pop above_bottom but rows is empty"); - - var bottom_iter = Itertools.peekable(bottom.vals()); - - let variants = Buffer.Buffer<(Text, CandidType)>(bottom.size()); - let variant_indexes = Buffer.Buffer(bottom.size()); - - for ((index, parent_node) in Itertools.enumerate(above_bottom.vals())) { - let tmp_bottom_iter = PeekableIter.takeWhile(bottom_iter, func({ parent_index; key } : CandidTypeNode) : Bool = index == parent_index); - let { parent_index; key = parent_key } = parent_node; - - switch (parent_node.type_) { - case (#Option(_)) { - let ?child_node = tmp_bottom_iter.next() else return #err(" #Option error: no item in tmp_bottom_iter"); - - let merged_node : CandidTypeNode = { - type_ = #Option(child_node.type_); - height = calc_height(parent_node.height, child_node.height); - parent_index; - key = parent_key; - }; - buffer.add(merged_node); - }; - case (#Array(_)) { - let vec_nodes = Iter.toArray(tmp_bottom_iter); - - let max = { - var height = 0; - var type_ : CandidType = #Empty; - }; - - for (node in vec_nodes.vals()) { - if (max.height < node.height) { - max.height := node.height; - max.type_ := node.type_; - }; - }; - - let best_node : CandidTypeNode = { - type_ = #Array(max.type_); - height = calc_height(parent_node.height, max.height); - parent_index; - key = parent_key; - }; - - buffer.add(best_node); - }; - case (#Record(_)) { - var height = 0; - - func get_max_height(item : CandidTypeNode) { - height := Nat.max(height, item.height); - }; - - var record_fields : [(Text, CandidType)] = Iter.toArray( - Iter.map( - tmp_bottom_iter, - func(node : CandidTypeNode) : (Text, CandidType) { - get_max_height(node); - to_candid_record_field_type(node); - }, - ) - ); - - let merged_node : CandidTypeNode = { - type_ = #Record(record_fields); - height = calc_height(parent_node.height, height); - parent_index; - key = parent_key; - }; - buffer.add(merged_node); - }; - case (#Variant(_)) { - var height = 0; - - func get_max_height(item : CandidTypeNode) { - height := Nat.max(height, item.height); - }; - - var variant_types : [(Text, CandidType)] = Iter.toArray( - Iter.map( - tmp_bottom_iter, - func(node : CandidTypeNode) : (Text, CandidType) { - get_max_height(node); - to_candid_record_field_type(node); - }, - ) - ); - - for (variant_type in variant_types.vals()) { - variants.add(variant_type); - }; - - variant_indexes.add(buffer.size()); - - let merged_node : CandidTypeNode = { - type_ = #Variant(variant_types); - height = calc_height(parent_node.height, height); - parent_index; - key = parent_key; - }; - - buffer.add(merged_node); - - }; - case (_) { - let new_parent_node : CandidTypeNode = { - type_ = internal_to_candid_type(parent_node.type_, null); - height = parent_node.height; - parent_index; - key = parent_key; - }; - - buffer.add(new_parent_node); - }; - }; - }; + label while_loop while (rows.size() > 0) { + let candid_values = Buffer.last(rows) else return Prelude.unreachable(); + let buffer = Buffer.Buffer(8); - if (variants.size() > 0) { - let full_variant_type : CandidType = #Variant(Buffer.toArray(variants)); + var has_compound_type = false; - for (index in variant_indexes.vals()) { - let prev_node = buffer.get(index); - let new_node : CandidTypeNode = { - type_ = full_variant_type; - height = prev_node.height; - parent_index = prev_node.parent_index; - key = prev_node.key; - }; + for ((index, parent_node) in Itertools.enumerate(candid_values.vals())) { - buffer.put(index, new_node); - }; + switch (parent_node.type_) { + case (#Option(inner_type)) { + has_compound_type := true; + let child_node : InternalCandidTypeNode = { + type_ = inner_type; + height = get_candid_height_value(inner_type); + parent_index = index; + key = null; }; - bottom := Buffer.toArray(buffer); - buffer.clear(); - }; - - let merged_type = bottom[0].type_; - #ok(merged_type); - }; + buffer.add(child_node); + }; + case (#Array(vec_types)) { + has_compound_type := true; - func get_candid_height_value(type_ : InternalCandidTypes) : Nat { - switch (type_) { - case (#Empty or #Null) 0; - case (_) 1; - }; - }; + for (vec_type in vec_types.vals()) { + let child_node : InternalCandidTypeNode = { + type_ = vec_type; + height = get_candid_height_value(vec_type); + parent_index = index; + key = null; + }; - // for most of the typs we can easily retrieve it from the value, but for an array it becomes a bit tricky - // because of optional values we can have seemingly different types in the array - // for example type [?Nat] with values [null, ?1], for each values will have a inferred type of [#Option(#Null), #Option(#Nat)] - // We need a way to choose #Option(#Nat) over #Option(#Null) in this case - - func order_candid_types_by_height_bfs(rows : Buffer<[InternalCandidTypeNode]>) { - - label while_loop while (rows.size() > 0) { - let candid_values = Buffer.last(rows) else return Prelude.unreachable(); - let buffer = Buffer.Buffer(8); - - var has_compound_type = false; - - for ((index, parent_node) in Itertools.enumerate(candid_values.vals())) { - - switch (parent_node.type_) { - case (#Option(inner_type)) { - has_compound_type := true; - let child_node : InternalCandidTypeNode = { - type_ = inner_type; - height = get_candid_height_value(inner_type); - parent_index = index; - key = null; - }; - - buffer.add(child_node); - }; - case (#Array(vec_types)) { - has_compound_type := true; - - for (vec_type in vec_types.vals()) { - let child_node : InternalCandidTypeNode = { - type_ = vec_type; - height = get_candid_height_value(vec_type); - parent_index = index; - key = null; - }; - - buffer.add(child_node); - }; - - }; - case (#Record(records)) { - - for ((key, field_type) in records.vals()) { - has_compound_type := true; - let child_node : InternalCandidTypeNode = { - type_ = field_type; - height = get_candid_height_value(field_type); - parent_index = index; - key = ?key; - }; - buffer.add(child_node); - }; - }; - case (#Variant(variants)) { - has_compound_type := true; - - for ((key, variant_type) in variants.vals()) { - has_compound_type := true; - let child_node : InternalCandidTypeNode = { - type_ = variant_type; - height = get_candid_height_value(variant_type); - parent_index = index; - key = ?key; - }; - buffer.add(child_node); - }; - }; - case (_) {}; - }; + buffer.add(child_node); }; - if (has_compound_type) { - rows.add(Buffer.toArray(buffer)); - } else { - return; + }; + case (#Record(records)) { + + for ((key, field_type) in records.vals()) { + has_compound_type := true; + let child_node : InternalCandidTypeNode = { + type_ = field_type; + height = get_candid_height_value(field_type); + parent_index = index; + key = ?key; + }; + buffer.add(child_node); + }; + }; + case (#Variant(variants)) { + has_compound_type := true; + + for ((key, variant_type) in variants.vals()) { + has_compound_type := true; + let child_node : InternalCandidTypeNode = { + type_ = variant_type; + height = get_candid_height_value(variant_type); + parent_index = index; + key = ?key; + }; + buffer.add(child_node); }; + }; + case (_) {}; }; + }; + + if (has_compound_type) { + rows.add(Buffer.toArray(buffer)); + } else { + return; + }; }; + }; - func get_renamed_key(renaming_map : Map, key : Text) : Text { - switch (Map.get(renaming_map, thash, key)) { - case (?v) v; - case (_) key; - }; + func get_renamed_key(renaming_map : Map, key : Text) : Text { + switch (Map.get(renaming_map, thash, key)) { + case (?v) v; + case (_) key; }; + }; }; diff --git a/src/Candid/Blob/TypedSerializer.mo b/src/Candid/Blob/TypedSerializer.mo new file mode 100644 index 0000000..e76ae6a --- /dev/null +++ b/src/Candid/Blob/TypedSerializer.mo @@ -0,0 +1,459 @@ +import Array "mo:base/Array"; +import Blob "mo:base/Blob"; +import Buffer "mo:base/Buffer"; +import Result "mo:base/Result"; +import Nat64 "mo:base/Nat64"; +import Nat8 "mo:base/Nat8"; +import Nat32 "mo:base/Nat32"; +import Nat "mo:base/Nat"; +import Iter "mo:base/Iter"; +import Text "mo:base/Text"; +import Order "mo:base/Order"; +import TrieMap "mo:base/TrieMap"; +import Option "mo:base/Option"; +import Debug "mo:base/Debug"; + +import Map "mo:map/Map"; +import Set "mo:map/Set"; +import ByteUtils "mo:byte-utils"; + +import { hashName = hash_record_key } "mo:candid/Tag"; + +import T "../Types"; +import CandidUtils "CandidUtils"; +import Encoder "Encoder"; +import Decoder "Decoder"; + +module TypedSerializer { + + type Iter = Iter.Iter; + type Result = Result.Result; + + type TrieMap = TrieMap.TrieMap; + type Candid = T.Candid; + type KeyValuePair = T.KeyValuePair; + + type Buffer = Buffer.Buffer; + type Hash = Nat32; + type Map = Map.Map; + type Set = Set.Set; + type Order = Order.Order; + + type CandidType = T.CandidType; + type ShallowCandidTypes = T.ShallowCandidTypes; + + let { n32hash; thash } = Map; + let { nhash } = Set; + + // Constants + let C = { + COUNTER = { + COMPOUND_TYPE = 0; + PRIMITIVE_TYPE = 1; + VALUE = 2; + }; + BYTES_INDEX = 0; + }; + + public type TypedSerializer = { + // Encoder types: keep original field names and use renaming_map during encoding + // to convert from old names to new names in the output + encoder_candid_types : [CandidType]; + + // Decoder types: store new field names so decoding can convert + // from new names back to old names using record_key_map + decoder_candid_types : [CandidType]; + + encoded_type_header : [Nat8]; + renaming_map : Map; + record_key_map : Map; + options : T.Options; + compound_types : [ShallowCandidTypes]; + recursive_types_map : Map; + }; + + func read(bytes : Blob, state : [var Nat]) : Nat8 { + let byte = bytes.get(state[C.BYTES_INDEX]); + state[C.BYTES_INDEX] += 1; + byte; + }; + + // Helper function + func formatVariantKey(key : Text) : Text { + let opt = Text.stripStart(key, #text("#")); + switch (opt) { + case (?stripped_text) stripped_text; + case (null) key; + }; + }; + + // https://en.wikipedia.org/wiki/LEB128 + func decode_leb128(bytes : Blob, state : [var Nat]) : Nat { + var n64 : Nat64 = 0; + var shift : Nat64 = 0; + + label decoding_leb loop { + let byte = read(bytes, state); + + n64 |= (Nat64.fromNat(Nat8.toNat(byte & 0x7f)) << shift); + + if (byte & 0x80 == 0) break decoding_leb; + shift += 7; + }; + + Nat64.toNat(n64); + }; + + // Local encoder function to replace Encoder.Encoder.new + func create_encoded_type_header( + _candid_types : [CandidType], + _options : T.Options, + renaming_map : Map, + ) : [Nat8] { + let options = _options; + + let candid_types = switch (options.types) { + case (?types) types; + case (_) Array.map( + _candid_types, + func(candid_type : CandidType) : CandidType { + CandidUtils.format_candid_type(candid_type, renaming_map); + }, + ); + }; + + let compound_type_buffer = Buffer.Buffer(200); + let candid_type_buffer = Buffer.Buffer(200); + + let counter = [var 0]; + + // Only encode the types, not the values + var i = 0; + let unique_compound_type_map = Map.new(); + + while (i < candid_types.size()) { + Encoder.encode_type_only( + candid_types[i], + compound_type_buffer, + candid_type_buffer, + renaming_map, + unique_compound_type_map, + counter, + false, + ); + i += 1; + }; + + let candid_magic_bytes_buffer = Buffer.fromArray([0x44, 0x49, 0x44, 0x4C]); // 'DIDL' magic bytes + + // add compound type to the buffer + let compound_type_size_bytes = Buffer.Buffer(8); + ByteUtils.Buffer.addLEB128_64(compound_type_size_bytes, Nat64.fromNat(counter[C.COUNTER.COMPOUND_TYPE])); + + // add primitive type to the buffer + let candid_type_size_bytes = Buffer.Buffer(8); + ByteUtils.Buffer.addLEB128_64(candid_type_size_bytes, Nat64.fromNat(candid_types.size())); + + let total_size = candid_magic_bytes_buffer.size() + compound_type_size_bytes.size() + compound_type_buffer.size() + candid_type_size_bytes.size() + candid_type_buffer.size(); + + let sequence = [ + candid_magic_bytes_buffer, + compound_type_size_bytes, + compound_type_buffer, + candid_type_size_bytes, + candid_type_buffer, + ]; + + i := 0; + var j = 0; + + Array.tabulate( + total_size, + func(_ : Nat) : Nat8 { + var buffer = sequence[i]; + while (j >= buffer.size()) { + j := 0; + i += 1; + buffer := sequence[i]; + }; + + let byte = buffer.get(j); + j += 1; + byte; + }, + ); + }; + + func extract_record_or_variant_keys(schema : T.CandidType) : [Text] { + let buffer = Buffer.Buffer(8); + + func extract(schema : T.CandidType) { + switch (schema) { + case (#Record(fields) or #Map(fields)) { + for ((name, value) in fields.vals()) { + buffer.add(name); + extract(value); + }; + }; + case (#Variant(variants)) { + for ((name, value) in variants.vals()) { + buffer.add(name); + extract(value); + }; + }; + case (#Tuple(types)) { + for (tuple_type in types.vals()) { + extract(tuple_type); + }; + }; + case (#Option(inner)) { extract(inner) }; + case (#Array(inner)) { extract(inner) }; + case (_) {}; + }; + }; + + extract(schema); + + Buffer.toArray(buffer); + }; + + func is_map_equal(map1 : Map, map2 : Map, hasher : Map.HashUtils, is_value_equal : (V, V) -> Bool) : Bool { + if (Map.size(map1) != Map.size(map2)) return false; + + for ((k, v) in Map.entries(map1)) { + switch (Map.get(map2, hasher, k)) { + case (?v2) if (is_value_equal(v, v2)) {}; + case (_) return false; + }; + }; + + true; + }; + + public func equal(self : TypedSerializer, other : TypedSerializer) : Bool { + if (self.encoder_candid_types != other.encoder_candid_types) return false; + if (self.decoder_candid_types != other.decoder_candid_types) return false; + if (self.encoded_type_header != other.encoded_type_header) return false; + if (not is_map_equal(self.renaming_map, other.renaming_map, thash, Text.equal)) return false; + if (not is_map_equal(self.record_key_map, other.record_key_map, n32hash, Text.equal)) return false; + if (self.options != other.options) return false; + if (self.compound_types != other.compound_types) return false; + + // Define a function to compare CandidType values + func candid_type_equal(t1 : CandidType, t2 : CandidType) : Bool { + t1 == t2; + }; + if (not is_map_equal(self.recursive_types_map, other.recursive_types_map, Map.nhash, candid_type_equal)) return false; + + true; + }; + + // add a print fn + public func toText(self : TypedSerializer) : Text { + "TypedSerializer {\n" + # " encoder_candid_types: " # debug_show (self.encoder_candid_types) # "\n" + # " decoder_candid_types: " # debug_show (self.decoder_candid_types) # "\n" + # " encoded_type_header: " # debug_show (self.encoded_type_header) # "\n" + # " renaming_map: " # debug_show (Map.toArray(self.renaming_map)) # "\n" + # " record_key_map: " # debug_show (Map.toArray(self.record_key_map)) # "\n" + # " options: " # debug_show (self.options) # "\n" + # " compound_types: " # debug_show (self.compound_types) # "\n" + # " recursive_types_map: " # debug_show (Map.toArray(self.recursive_types_map)) # "\n" + # "}"; + }; + + /// Creates a new TypedSerializer from Candid types + public func new(_candid_types : [CandidType], _options : ?T.Options) : TypedSerializer { + let options = Option.get(_options, T.defaultOptions); + + let renaming_map = Map.new(); + for ((k, v) in options.renameKeys.vals()) { + ignore Map.put(renaming_map, thash, k, v); + }; + + // Encoder types: original types that will use renaming_map during encoding + let encoder_candid_types = switch (options.types) { + case (?types) types; + case (_) Array.map( + _candid_types, + func(candid_type : CandidType) : CandidType { + CandidUtils.format_candid_type(candid_type, renaming_map); + }, + ); + }; + + // Decoder types: types with renamed fields for decoding + + // Collect record keys from all encoder_candid_types entries + let record_keys_buffer = Buffer.Buffer(8); + for (candid_type in encoder_candid_types.vals()) { + let keys = extract_record_or_variant_keys(candid_type); + for (key in keys.vals()) { + record_keys_buffer.add(key); + }; + }; + let record_keys = Buffer.toArray(record_keys_buffer); + + let record_key_map = Map.new(); + + var i = 0; + + while (i < record_keys.size()) { + let key = formatVariantKey(record_keys[i]); + let hash = hash_record_key(key); + ignore Map.put(record_key_map, n32hash, hash, key); + i += 1; + }; + + ignore do ? { + let key_pairs_to_rename = options.renameKeys; + + var j = 0; + while (j < key_pairs_to_rename.size()) { + let original_key = formatVariantKey(key_pairs_to_rename[j].0); + let new_key = formatVariantKey(key_pairs_to_rename[j].1); + + let hash = hash_record_key(original_key); + ignore Map.put(record_key_map, n32hash, hash, new_key); + + j += 1; + }; + }; + + // Use encoder types for the encoded type header + let encoded_type_header = create_encoded_type_header(encoder_candid_types, options, renaming_map); + + // Decoder types are the same as encoder types - renaming happens during value decoding + let decoder_candid_types = encoder_candid_types; + + { + encoded_type_header; + encoder_candid_types; + decoder_candid_types; + renaming_map; + record_key_map; + options; + compound_types = []; // Will be populated if needed + recursive_types_map = Map.new(); // Initialize empty map + } : TypedSerializer; + }; + + /// Creates a new TypedSerializer from a blob (extracts types from the blob) + public func fromBlob(blob : Blob, record_keys : [Text], _options : ?T.Options) : TypedSerializer { + let options = Option.get(_options, T.defaultOptions); + + let record_key_map = Map.new(); + + var i = 0; + while (i < record_keys.size()) { + let key = formatVariantKey(record_keys[i]); + let hash = hash_record_key(key); + ignore Map.put(record_key_map, n32hash, hash, key); + i += 1; + }; + + let renaming_map = Map.new(); + + ignore do ? { + let key_pairs_to_rename = options.renameKeys; + + var j = 0; + while (j < key_pairs_to_rename.size()) { + let original_key = formatVariantKey(key_pairs_to_rename[j].0); + let new_key = formatVariantKey(key_pairs_to_rename[j].1); + + let hash = hash_record_key(original_key); + ignore Map.put(record_key_map, n32hash, hash, new_key); + + ignore Map.put(renaming_map, thash, original_key, new_key); + + j += 1; + }; + }; + + let bytes = blob; + let state : [var Nat] = [var 0]; + + let magic = Array.tabulate(4, func(_ : Nat) : Nat8 = read(bytes, state)); + assert (magic == [0x44, 0x49, 0x44, 0x4c]); + + let total_compound_types = decode_leb128(bytes, state); + let compound_types = Decoder.extract_compound_types(bytes, state, total_compound_types, record_key_map); + let recursive_types_map = Map.new(); + let extracted_candid_types = Decoder.build_types(bytes, state, compound_types, recursive_types_map); + + let type_header_size = state[C.BYTES_INDEX]; + let encoded_type_header = Array.tabulate(type_header_size, func(i : Nat) : Nat8 = blob.get(i)); + + // Generate encoded_type_header using the same logic as new() to ensure consistency + let _encoded_type_header2 = create_encoded_type_header(extracted_candid_types, options, renaming_map); + // assert (encoded_type_header == _encoded_type_header2); + + { + encoder_candid_types = extracted_candid_types; + decoder_candid_types = extracted_candid_types; + record_key_map; + options; + compound_types = []; // Keep consistent with new() function + encoded_type_header; + renaming_map; + recursive_types_map; + }; + }; + + /// Encodes values using the precomputed types in this TypedSerializer + public func encode(self : TypedSerializer, candid_values : [Candid]) : Result { + if (candid_values.size() != self.encoder_candid_types.size()) { + return #err("encode: candid_values size does not match encoder_candid_types size"); + }; + + let value_buffer = Buffer.Buffer(400); + let recursive_map = Map.new(); + let counter = [var 0]; + let unique_compound_type_map = Map.new(); + + var i = 0; + while (i < candid_values.size()) { + ignore Encoder.encode_value_only( + self.encoder_candid_types[i], + candid_values[i], + value_buffer, + self.renaming_map, + unique_compound_type_map, + recursive_map, + counter, + false, + ); + i += 1; + }; + + #ok( + Blob.fromArray( + Array.append( + self.encoded_type_header, + Buffer.toArray(value_buffer), + ) + ) + ); + }; + + /// Decodes values from a full candid blob using precomputed types + public func decode(self : TypedSerializer, candid_blob : Blob) : Result<[Candid], Text> { + let bytes = candid_blob; + let state : [var Nat] = [var 0]; + + // Verify magic number + let magic = Array.tabulate(4, func(_ : Nat) : Nat8 = read(bytes, state)); + if (magic != [0x44, 0x49, 0x44, 0x4c]) { + return #err("Invalid Magic Number"); + }; + + // Since we have the precomputed encoded_type_header, we can directly jump to the values section + // instead of parsing and skipping the type section + state[C.BYTES_INDEX] := self.encoded_type_header.size(); + + // Use precomputed types and recursive types map for decoding values + Decoder.decode_candid_values(bytes, self.decoder_candid_types, state, self.options, self.recursive_types_map); + }; + +}; diff --git a/src/Candid/lib.mo b/src/Candid/lib.mo index 53c82e9..be5496e 100644 --- a/src/Candid/lib.mo +++ b/src/Candid/lib.mo @@ -3,10 +3,11 @@ import Array "mo:base/Array"; import Text "mo:base/Text"; -import Encoder "Blob/Encoder"; +import CandidEncoder "Blob/Encoder"; import Decoder "Blob/Decoder"; import RepIndyHash "Blob/RepIndyHash"; import CandidUtils "Blob/CandidUtils"; +import TypedSerializerModule "Blob/TypedSerializer"; import Parser "Text/Parser"; import ToText "Text/ToText"; @@ -25,10 +26,13 @@ module { public type Options = T.Options; public let defaultOptions = T.defaultOptions; + public let TypedSerializer = TypedSerializerModule; + public type TypedSerializer = TypedSerializerModule.TypedSerializer; + public type CandidType = T.CandidType; /// Converts a motoko value to a [Candid](#Candid) value - public let { encode; encodeOne } = Encoder; + public let { encode; encodeOne } = CandidEncoder; public let repIndyHash = RepIndyHash.hash; diff --git a/src/Utils.mo b/src/Utils.mo index 1fde2bd..2126eec 100644 --- a/src/Utils.mo +++ b/src/Utils.mo @@ -15,232 +15,243 @@ import Prelude "mo:base/Prelude"; import Nat32 "mo:base/Nat32"; import Debug "mo:base/Debug"; import Itertools "mo:itertools/Iter"; +import Map "mo:map/Map"; +import MapConst "mo:map/Map/const"; import ByteUtils "mo:byte-utils"; module { - type Iter = Iter.Iter; - type Buffer = Buffer.Buffer; - type Result = Result.Result; - - public func reverse_order(fn : (A, A) -> Order.Order) : (A, A) -> Order.Order { - func(a : A, b : A) : Order.Order { - switch (fn(a, b)) { - case (#less) #greater; - case (#equal) #equal; - case (#greater) #less; - }; - }; - }; - - public type ArrayLike = { - size : () -> Nat; - get : (Nat) -> A; - }; - - public func array_slice(arr : ArrayLike, start : Nat, end : Nat) : [A] { - Array.tabulate( - end - start, - func(i : Nat) = arr.get(start + i), - ); + type Iter = Iter.Iter; + type Buffer = Buffer.Buffer; + type Result = Result.Result; + + public func create_map(map_size : Nat) : Map.Map = [ + var ?( + Array.init(map_size, null), + Array.init(map_size, null), + Array.init(map_size * 2, MapConst.NULL), + Array.init(3, 0), + ) + ]; + + public func reverse_order(fn : (A, A) -> Order.Order) : (A, A) -> Order.Order { + func(a : A, b : A) : Order.Order { + switch (fn(a, b)) { + case (#less) #greater; + case (#equal) #equal; + case (#greater) #less; + }; }; - - public func blob_slice(blob : Blob, start : Nat, end : Nat) : [Nat8] { - Array.tabulate( - end - start, - func(i : Nat) = blob.get(start + i), - ); + }; + + public type ArrayLike = { + size : () -> Nat; + get : (Nat) -> A; + }; + + public func array_slice(arr : ArrayLike, start : Nat, end : Nat) : [A] { + Array.tabulate( + end - start, + func(i : Nat) = arr.get(start + i), + ); + }; + + public func blob_slice(blob : Blob, start : Nat, end : Nat) : [Nat8] { + Array.tabulate( + end - start, + func(i : Nat) = blob.get(start + i), + ); + }; + + public func concatKeys(keys : [[Text]]) : [Text] { + Iter.toArray( + Itertools.flattenArray(keys) + ); + }; + + public func sized_iter_to_array(iter : Iter, size : Nat) : [A] { + Array.tabulate( + size, + func(i : Nat) { + switch (iter.next()) { + case (?x) x; + case (_) Prelude.unreachable(); + }; + }, + ); + }; + + public func send_error(res : Result) : Result { + switch (res) { + case (#ok(_)) Prelude.unreachable(); + case (#err(errorMsg)) #err(errorMsg); }; - - public func concatKeys(keys : [[Text]]) : [Text] { - Iter.toArray( - Itertools.flattenArray(keys) - ); + }; + + public func subText(text : Text, start : Nat, end : Nat) : Text { + Itertools.toText( + Itertools.skip( + Itertools.take(text.chars(), end), + start, + ) + ); + }; + + public func cmpRecords(a : (Text, Any), b : (Text, Any)) : Order.Order { + Text.compare(a.0, b.0); + }; + + public func stripStart(text : Text, prefix : Text.Pattern) : Text { + switch (Text.stripStart(text, prefix)) { + case (?t) t; + case (_) text; }; - - public func sized_iter_to_array(iter : Iter, size : Nat) : [A] { - Array.tabulate( - size, - func(i : Nat) { - switch (iter.next()) { - case (?x) x; - case (_) Prelude.unreachable(); - }; - }, - ); - }; - - public func send_error(res : Result) : Result { - switch (res) { - case (#ok(_)) Prelude.unreachable(); - case (#err(errorMsg)) #err(errorMsg); + }; + + public func log2(n : Float) : Float { + Float.log(n) / Float.log(2); + }; + + public func isHash(key : Text) : Bool { + Itertools.all( + key.chars(), + func(c : Char) : Bool { + c == '_' or Char.isDigit(c); + }, + ); + }; + + public func text_to_nat32(text : Text) : Nat32 { + Itertools.fold( + text.chars(), + 0 : Nat32, + func(acc : Nat32, c : Char) : Nat32 { + if (c == '_') { + acc; + } else { + acc * 10 + Char.toNat32(c) - Char.toNat32('0'); + }; + }, + ); + }; + + public func text_to_nat(text : Text) : Nat { + Itertools.fold( + text.chars(), + 0 : Nat, + func(acc : Nat, c : Char) : Nat { + if (c == '_') { + acc; + } else { + acc * 10 + Nat32.toNat(Char.toNat32(c) - Char.toNat32('0')); }; + }, + ); + }; + + public func text_is_number(text : Text) : Bool { + Itertools.all( + text.chars(), + func(c : Char) : Bool { + Char.isDigit(c) or c == '_'; + }, + ); + }; + + type AddToBuffer = { + add : (A) -> (); + }; + + // https://en.wikipedia.org/wiki/LEB128 + // limited to 64-bit unsigned integers + // more performant than the general unsigned_leb128 + public func unsigned_leb128_64(buffer : ByteUtils.BufferLike, n : Nat) { + var value = Nat64.fromNat(n); + while (value >= 0x80) { + buffer.add(Nat8.fromNat(Nat64.toNat(value & 0x7F)) | 0x80); + value >>= 7; }; - - public func subText(text : Text, start : Nat, end : Nat) : Text { - Itertools.toText( - Itertools.skip( - Itertools.take(text.chars(), end), - start, - ) - ); + buffer.add(Nat8.fromNat(Nat64.toNat(value))); + }; + + public func unsigned_leb128(buffer : ByteUtils.BufferLike, n : Nat) { + var value = Nat64.fromNat(n); + while (value >= 0x80) { + buffer.add(Nat8.fromNat(Nat64.toNat(value & 0x7F)) | 0x80); + value >>= 7; }; + buffer.add(Nat8.fromNat(Nat64.toNat(value))); + }; - public func cmpRecords(a : (Text, Any), b : (Text, Any)) : Order.Order { - Text.compare(a.0, b.0); - }; + public func signed_leb128_64(buffer : ByteUtils.BufferLike, num : Int) { + ByteUtils.Buffer.addSLEB128_64(buffer, Int64.fromInt(num)); + }; - public func stripStart(text : Text, prefix : Text.Pattern) : Text { - switch (Text.stripStart(text, prefix)) { - case (?t) t; - case (_) text; - }; - }; + // public func signed_leb128(buffer : AddToBuffer, num : Int) { + // let nat64_bound = 18_446_744_073_709_551_616; - public func log2(n : Float) : Float { - Float.log(n) / Float.log(2); - }; + // if (num < nat64_bound and num > -nat64_bound) return signed_leb128_64(buffer, num); - public func isHash(key : Text) : Bool { - Itertools.all( - key.chars(), - func(c : Char) : Bool { - c == '_' or Char.isDigit(c); - }, - ); - }; + // var n = num; + // let is_negative = n < 0; - public func text_to_nat32(text : Text) : Nat32 { - Itertools.fold( - text.chars(), - 0 : Nat32, - func(acc : Nat32, c : Char) : Nat32 { - if (c == '_') { - acc; - } else { - acc * 10 + Char.toNat32(c) - Char.toNat32('0'); - }; - }, - ); - }; + // }; - public func text_to_nat(text : Text) : Nat { - Itertools.fold( - text.chars(), - 0 : Nat, - func(acc : Nat, c : Char) : Nat { - if (c == '_') { - acc; - } else { - acc * 10 + Nat32.toNat(Char.toNat32(c) - Char.toNat32('0')); - }; - }, - ); - }; + public class ReusableBuffer(init_capacity : Nat) { + var elems : [var ?A] = Array.init(init_capacity, null); + var count : Nat = 0; + + public func size() : Nat = count; - public func text_is_number(text : Text) : Bool { - Itertools.all( - text.chars(), - func(c : Char) : Bool { - Char.isDigit(c) or c == '_'; - }, + public func add(elem : A) { + if (count == elems.size()) { + elems := Array.tabulateVar( + elems.size() * 2, + func(i : Nat) : ?A { + if (i < count) { + elems[i]; + } else { + null; + }; + }, ); - }; + }; - type AddToBuffer = { - add : (A) -> (); + elems[count] := ?elem; + count += 1; }; - // https://en.wikipedia.org/wiki/LEB128 - // limited to 64-bit unsigned integers - // more performant than the general unsigned_leb128 - public func unsigned_leb128_64(buffer : ByteUtils.BufferLike, n : Nat) { - var value = Nat64.fromNat(n); - while (value >= 0x80) { - buffer.add(Nat8.fromNat(Nat64.toNat(value & 0x7F)) | 0x80); - value >>= 7; - }; - buffer.add(Nat8.fromNat(Nat64.toNat(value))); + public func clear() { + count := 0; }; - public func unsigned_leb128(buffer : ByteUtils.BufferLike, n : Nat) { - var value = Nat64.fromNat(n); - while (value >= 0x80) { - buffer.add(Nat8.fromNat(Nat64.toNat(value & 0x7F)) | 0x80); - value >>= 7; - }; - buffer.add(Nat8.fromNat(Nat64.toNat(value))); + public func get(i : Nat) : A { + switch (elems[i]) { + case (?elem) elem; + case (null) Debug.trap "Index out of bounds"; + }; }; - public func signed_leb128_64(buffer : ByteUtils.BufferLike, num : Int) { - ByteUtils.Buffer.addSLEB128_64(buffer, Int64.fromInt(num)); + public func put(i : Nat, elem : A) { + if (i >= count) Debug.trap "Index out of bounds"; + elems[i] := ?elem; }; - // public func signed_leb128(buffer : AddToBuffer, num : Int) { - // let nat64_bound = 18_446_744_073_709_551_616; - - // if (num < nat64_bound and num > -nat64_bound) return signed_leb128_64(buffer, num); - - // var n = num; - // let is_negative = n < 0; - - // }; - - public class ReusableBuffer(init_capacity : Nat) { - var elems : [var ?A] = Array.init(init_capacity, null); - var count : Nat = 0; - - public func size() : Nat = count; - - public func add(elem : A) { - if (count == elems.size()) { - elems := Array.tabulateVar( - elems.size() * 2, - func(i : Nat) : ?A { - if (i < count) { - elems[i]; - } else { - null; - }; - }, - ); - }; - - elems[count] := ?elem; - count += 1; - }; - - public func clear() { - count := 0; - }; - - public func get(i : Nat) : A { - switch (elems[i]) { - case (?elem) elem; - case (null) Debug.trap "Index out of bounds"; - }; - }; - - public func put(i : Nat, elem : A) { - if (i >= count) Debug.trap "Index out of bounds"; - elems[i] := ?elem; - }; - - public func vals() : Iter.Iter { - var i = 0; - - object { - public func next() : ?A { - if (i < count) { - let res = elems[i]; - i += 1; - res; - } else { - null; - }; - }; - }; + public func vals() : Iter.Iter { + var i = 0; + + object { + public func next() : ?A { + if (i < count) { + let res = elems[i]; + i += 1; + res; + } else { + null; + }; }; + }; }; + }; }; diff --git a/tests/Candid.Large.test.mo b/tests/Candid.Large.test.mo deleted file mode 100644 index 800b3e5..0000000 --- a/tests/Candid.Large.test.mo +++ /dev/null @@ -1,236 +0,0 @@ -// @testmode wasi -import Iter "mo:base/Iter"; -import Debug "mo:base/Debug"; -import Prelude "mo:base/Prelude"; -import Text "mo:base/Text"; -import Char "mo:base/Char"; -import Buffer "mo:base/Buffer"; -import Nat64 "mo:base/Nat64"; - -import Fuzz "mo:fuzz"; -import Itertools "mo:itertools/Iter"; -import { test; suite } "mo:test"; - -import Serde "../src"; -import CandidEncoder "../src/Candid/Blob/Encoder"; -import CandidDecoder "../src/Candid/Blob/Decoder"; -import LegacyCandidDecoder "../src/Candid/Blob/Decoder"; -import LegacyCandidEncoder "../src/Candid/Blob/Encoder"; - -func createGenerator(seed : Nat) : { next() : Nat } { - // Pure bitwise xorshift64 - no multiplication or addition! - var state : Nat64 = Nat64.fromNat(seed); - if (state == 0) state := 1; // Avoid zero state - - { - next = func() : Nat { - // Only XOR and bit shifts - fastest possible - state ^= state << 13 : Nat64; - state ^= state >> 7 : Nat64; - state ^= state << 17 : Nat64; - Nat64.toNat(state); - }; - }; -}; - -let fuzz = Fuzz.create(createGenerator(42)); - -let limit = 1_000; - -let candify_store_item = { - from_blob = func(blob : Blob) : StoreItem { - let ?c : ?StoreItem = from_candid (blob); - c; - }; - to_blob = func(c : StoreItem) : Blob { to_candid (c) }; -}; - -type X = { name : Text; x : X }; - -// let x: X = { -// name = "yo"; -// x = { -// name = "yo"; -// x = {}; -// }; -// }; - -// let x : Serde.Candid = #Record([ -// ("name", #Text("yo")), -// ("x", x) -// ]); -type CustomerReview = { - username : Text; - rating : Nat; - comment : Text; -}; - -let CustomerReview = #Record([ - ("username", #Text), - ("rating", #Nat), - ("comment", #Text), -]); - -type AvailableSizes = { #xs; #s; #m; #l; #xl }; - -let AvailableSizes = #Variant([ - ("xs", #Null), - ("s", #Null), - ("m", #Null), - ("l", #Null), - ("xl", #Null), -]); - -type ColorOption = { - name : Text; - hex : Text; -}; - -let ColorOption = #Record([ - ("name", #Text), - ("hex", #Text), -]); - -type StoreItem = { - name : Text; - store : Text; - customer_reviews : [CustomerReview]; - available_sizes : AvailableSizes; - color_options : [ColorOption]; - price : Float; - in_stock : Bool; - address : (Text, Text, Text, Text); - contact : { - email : Text; - phone : ?Text; - }; -}; - -let StoreItem : Serde.Candid.CandidType = #Record([ - ("name", #Text), - ("store", #Text), - ("customer_reviews", #Array(CustomerReview)), - ("available_sizes", AvailableSizes), - ("color_options", #Array(ColorOption)), - ("price", #Float), - ("in_stock", #Bool), - ("address", #Tuple([#Text, #Text, #Text, #Text])), - ("contact", #Record([("email", #Text), ("phone", #Option(#Text))])), -]); - -let FormattedStoreItem = Serde.Candid.formatCandidType([StoreItem], null); - -let cities = ["Toronto", "Ottawa", "New York", "Los Angeles", "Chicago", "Houston", "Phoenix", "Philadelphia", "San Antonio", "San Diego", "Dallas", "San Jose"]; -let states = ["ON", "QC", "NY", "CA", "IL", "TX", "AZ", "PA", "TX", "CA", "TX", "CA"]; -let streets = ["King St", "Queen St", "Yonge St", "Bay St", "Bloor St", "Dundas St", "College St", "Spadina Ave", "St Clair Ave", "Danforth Ave", "Eglinton Ave", "Lawrence Ave"]; - -let stores = ["h&m", "zara", "gap", "old navy", "forever 21", "uniqlo", "urban outfitters", "american eagle", "aeropostale", "abercrombie & fitch", "hollister", "express"]; -let email_terminator = ["gmail.com", "yahoo.com", "outlook.com"]; - -let cs_starter_kid = ["black hoodie", "M1 macbook", "white hoodie", "air forces", "Algorithms textbook", "c the hard way", "Udemy subscription", "Nvidea RTX"]; - -let available_sizes = [#xs, #s, #m, #l, #xl]; - -func new_item() : StoreItem { - let store_name = fuzz.array.randomEntry(stores).1; - let store_item : StoreItem = { - name = fuzz.array.randomEntry(cs_starter_kid).1; - store = store_name; - customer_reviews = [ - { - username = "user1"; - rating = fuzz.nat.randomRange(0, 5); - comment = "good"; - }, - { - username = "user2"; - rating = fuzz.nat.randomRange(0, 5); - comment = "ok"; - }, - ]; - available_sizes = fuzz.array.randomEntry(available_sizes).1; - color_options = [ - { name = "red"; hex = "#ff0000" }, - { name = "blue"; hex = "#0000ff" }, - ]; - price = fuzz.float.randomRange(19.99, 399.99); - in_stock = fuzz.bool.random(); - address = ( - fuzz.array.randomEntry(streets).1, - fuzz.array.randomEntry(cities).1, - fuzz.array.randomEntry(states).1, - fuzz.text.randomAlphanumeric(6), - ); - contact = { - email = store_name # "@" # fuzz.array.randomEntry(email_terminator).1; - phone = if (fuzz.nat.randomRange(0, 100) % 3 == 0) { null } else { - ?Text.fromIter( - fuzz.array.randomArray(10, func() : Char { Char.fromNat32(fuzz.nat32.randomRange(0, 9) + Char.toNat32('0')) }).vals() : Iter.Iter - ); - }; - }; - }; -}; - -let store_item_keys = ["name", "store", "customer_reviews", "username", "rating", "comment", "available_sizes", "xs", "s", "m", "l", "xl", "color_options", "name", "hex", "price", "in_stock", "address", "contact", "email", "phone"]; - -let candid_buffer = Buffer.Buffer<[Serde.Candid]>(limit); -let store_items = Buffer.Buffer(limit); - -let candid_buffer_with_types = Buffer.Buffer<[Serde.Candid]>(limit); -let store_items_with_types = Buffer.Buffer(limit); - -suite( - "Serde.Candid", - func() { - test( - "decode()", - func() { - for (i in Itertools.range(0, limit)) { - let item = new_item(); - store_items.add(item); - let candid_blob = candify_store_item.to_blob(item); - let #ok(candid) = CandidDecoder.one_shot(candid_blob, store_item_keys, null); - candid_buffer.add(candid); - }; - }, - ); - test( - "encode()", - func() { - for (i in Itertools.range(0, limit)) { - let candid = candid_buffer.get(i); - let res = LegacyCandidEncoder.encode(candid, null); - let #ok(blob) = res; - let item = candify_store_item.from_blob(blob); - assert item == store_items.get(i); - }; - }, - ); - test( - "decode() with types", - func() { - for (i in Itertools.range(0, limit)) { - let item = new_item(); - store_items_with_types.add(item); - let candid_blob = candify_store_item.to_blob(item); - let #ok(split_blob) = CandidDecoder.split(candid_blob, null); - let #ok(candid) = CandidDecoder.one_shot(candid_blob, store_item_keys, ?{ Serde.Candid.defaultOptions with types = ?FormattedStoreItem }); - candid_buffer_with_types.add(candid); - }; - }, - ); - test( - "encode() with types", - func() { - for (i in Itertools.range(0, limit)) { - let candid = candid_buffer_with_types.get(i); - let res = LegacyCandidEncoder.encode(candid, ?{ Serde.Candid.defaultOptions with types = ?FormattedStoreItem }); - let #ok(blob) = res; - let item = candify_store_item.from_blob(blob); - assert item == store_items_with_types.get(i); - }; - }, - ); - }, -); diff --git a/tests/Candid.Test.mo b/tests/Candid.Test.mo index edd1b9b..9ea269a 100644 --- a/tests/Candid.Test.mo +++ b/tests/Candid.Test.mo @@ -4,6 +4,7 @@ import Debug "mo:base/Debug"; import Iter "mo:base/Iter"; import Nat "mo:base/Nat"; import Principal "mo:base/Principal"; +import Result "mo:base/Result"; import { test; suite } "mo:test"; @@ -11,8 +12,22 @@ import Serde "../src"; import Candid "../src/Candid"; import Encoder "../src/Candid/Blob/Encoder"; +import CandidTestUtils "CandidTestUtils"; + type Candid = Candid.Candid; +func encode(vals : [Candid], options : ?Candid.Options) : Result.Result { + Candid.encode(vals, options); +}; + +func encodeOne(val : Candid, options : ?Candid.Options) : Result.Result { + Candid.encodeOne(val, options); +}; + +func decode(blob : Blob, keys : [Text], options : ?Candid.Options) : Result.Result<[Candid], Text> { + Candid.decode(blob, keys, options); +}; + suite( "Candid decode()", func() { @@ -99,7 +114,15 @@ suite( let records : [Record] = [null_record, empty_record, admin_record, user_record, base_record]; let blob = to_candid (records); - let #ok(candid) = Candid.decode(blob, Serde.concatKeys([PermissionKeys, UserKeys, RecordKeys]), null); + + let RecordsType : Candid.CandidType = #Array( + #Record([ + ("group", #Text), + ("users", #Option(#Array(#Record([("name", #Text), ("age", #Nat), ("permission", #Variant([("read", #Array(#Text)), ("write", #Array(#Text)), ("read_all", #Null), ("write_all", #Null), ("admin", #Null)]))])))), + ]) + ); + + let #ok(candid) = CandidTestUtils.decode_with_types([RecordsType], Serde.concatKeys([PermissionKeys, UserKeys, RecordKeys]), blob, null) else return assert false; let expected = [#Array([#Record([("group", #Text("null")), ("users", #Null)]), #Record([("group", #Text("empty")), ("users", #Option(#Array([])))]), #Record([("group", #Text("admins")), ("users", #Option(#Array([#Record([("age", #Nat(32)), ("permission", #Variant("admin", #Null)), ("name", #Text("John"))])])))]), #Record([("group", #Text("users")), ("users", #Option(#Array([#Record([("age", #Nat(28)), ("permission", #Variant("read_all", #Null)), ("name", #Text("Ali"))]), #Record([("age", #Nat(40)), ("permission", #Variant("write_all", #Null)), ("name", #Text("James"))])])))]), #Record([("group", #Text("base")), ("users", #Option(#Array([#Record([("age", #Nat(32)), ("permission", #Variant("read", #Array([#Text("posts"), #Text("comments")]))), ("name", #Text("Henry"))]), #Record([("age", #Nat(32)), ("permission", #Variant("write", #Array([#Text("posts"), #Text("comments")]))), ("name", #Text("Steven"))])])))])])]; @@ -135,14 +158,20 @@ suite( }; let record_blob = to_candid (record); - let candid = Candid.decode(record_blob, ["first", "second", "name", "age"], null); - assert candid == #ok([ + let RecordType : Candid.CandidType = #Record([ + ("first", #Record([("name", #Text), ("age", #Nat)])), + ("second", #Record([("name", #Text), ("age", #Nat)])), + ]); + + let #ok(candid) = CandidTestUtils.decode_with_types([RecordType], ["first", "second", "name", "age"], record_blob, null) else return assert false; + + assert candid == [ #Record([ ("first", #Record([("age", #Nat(23)), ("name", #Text("James"))])), ("second", #Record([("age", #Nat(32)), ("name", #Text("Steven"))])), ]), - ]); + ]; }, ); @@ -191,9 +220,19 @@ suite( Candid.defaultOptions with renameKeys = [("arr", "array"), ("name", "username")]; }; - let candid = Candid.decode(blob, ["name", "arr"], ?options); - assert candid == #ok([ + Debug.print("blob: " # debug_show blob); + Debug.print("keys: " # debug_show ["name", "arr"]); + + // Test regular decode first + let regular_decode_result = Candid.decode(blob, ["name", "arr"], ?options); + Debug.print("regular decode result: " # debug_show regular_decode_result); + + let ArrayType : Candid.CandidType = #Array(#Record([("name", #Text), ("arr", #Array(#Nat))])); + + let #ok(candid) = CandidTestUtils.decode_with_types([ArrayType], ["name", "arr"], blob, ?options) else return assert false; + + assert candid == [ #Array([ #Record([ ("array", #Array([#Nat(1), #Nat(2), #Nat(3), #Nat(4)])), @@ -208,7 +247,7 @@ suite( ("username", #Text("rust")), ]), ]) - ]); + ]; }, ); test( @@ -216,9 +255,12 @@ suite( func() { let motoko = { name = "candid" }; let blob = to_candid (motoko); - let candid = Candid.decode(blob, ["name"], null); - assert candid == #ok([#Record([("name", #Text("candid"))])]); + let RecordType : Candid.CandidType = #Record([("name", #Text)]); + + let #ok(candid) = CandidTestUtils.decode_with_types([RecordType], ["name"], blob, null) else return assert false; + + assert candid == [#Record([("name", #Text("candid"))])]; }, ); test( @@ -226,9 +268,12 @@ suite( func() { let arr = [1, 2, 3, 4]; let blob = to_candid (arr); - let candid = Candid.decode(blob, [], null); - assert candid == #ok([#Array([#Nat(1), #Nat(2), #Nat(3), #Nat(4)])]); + let ArrayType : Candid.CandidType = #Array(#Nat); + + let #ok(candid) = CandidTestUtils.decode_with_types([ArrayType], [], blob, null) else return assert false; + + assert candid == [#Array([#Nat(1), #Nat(2), #Nat(3), #Nat(4)])]; }, ); test( @@ -242,8 +287,14 @@ suite( [["hello", "world"], ["foo", "bar"]], ]; - assert (Candid.decode(to_candid (arr2), [], null) == #ok([#Array([#Array([#Nat(1), #Nat(2), #Nat(3), #Nat(4)]), #Array([#Nat(5), #Nat(6), #Nat(7), #Nat(8)]), #Array([#Nat(9), #Nat(10), #Nat(11)])])])); - assert (Candid.decode(to_candid (arr3), [], null) == #ok([#Array([#Array([#Array([#Text("hello"), #Text("world")]), #Array([#Text("foo"), #Text("bar")])]), #Array([#Array([#Text("hello"), #Text("world")]), #Array([#Text("foo"), #Text("bar")])]), #Array([#Array([#Text("hello"), #Text("world")]), #Array([#Text("foo"), #Text("bar")])])])])); + let Arr2Type : Candid.CandidType = #Array(#Array(#Nat)); + let Arr3Type : Candid.CandidType = #Array(#Array(#Array(#Text))); + + let #ok(arr2_candid) = CandidTestUtils.decode_with_types([Arr2Type], [], to_candid (arr2), null) else return assert false; + let #ok(arr3_candid) = CandidTestUtils.decode_with_types([Arr3Type], [], to_candid (arr3), null) else return assert false; + + assert (arr2_candid == [#Array([#Array([#Nat(1), #Nat(2), #Nat(3), #Nat(4)]), #Array([#Nat(5), #Nat(6), #Nat(7), #Nat(8)]), #Array([#Nat(9), #Nat(10), #Nat(11)])])]); + assert (arr3_candid == [#Array([#Array([#Array([#Text("hello"), #Text("world")]), #Array([#Text("foo"), #Text("bar")])]), #Array([#Array([#Text("hello"), #Text("world")]), #Array([#Text("foo"), #Text("bar")])]), #Array([#Array([#Text("hello"), #Text("world")]), #Array([#Text("foo"), #Text("bar")])])])]); }, ); test( @@ -255,15 +306,17 @@ suite( let bytes_array = to_candid (motoko_blob); let bytes_blob = to_candid (motoko_blob); - let candid_array = Candid.decode(bytes_array, [], null); - let candid_blob = Candid.decode(bytes_blob, [], null); + let BlobType : Candid.CandidType = #Blob; + + let #ok(candid_array) = CandidTestUtils.decode_with_types([BlobType], [], bytes_array, null) else return assert false; + let #ok(candid_blob) = CandidTestUtils.decode_with_types([BlobType], [], bytes_blob, null) else return assert false; assert ( // All [Nat8] types are decoded as #Blob - candid_array != #ok([#Array([#Nat8(1), #Nat8(2), #Nat8(3), #Nat8(4)])]), + candid_array != [#Array([#Nat8(1), #Nat8(2), #Nat8(3), #Nat8(4)])], ); - assert (candid_array == #ok([#Blob(motoko_blob)])); - assert (candid_blob == #ok([#Blob(motoko_blob)])); + assert (candid_array == [#Blob(motoko_blob)]); + assert (candid_blob == [#Blob(motoko_blob)]); }, ); test( @@ -289,17 +342,25 @@ suite( let record_blob = to_candid (record); let array_blob = to_candid (array); - let text_candid = Candid.decode(text_blob, ["text"], null); - let nat_candid = Candid.decode(nat_blob, ["nat"], null); - let bool_candid = Candid.decode(bool_blob, ["bool"], null); - let record_candid = Candid.decode(record_blob, ["record", "site"], null); - let array_candid = Candid.decode(array_blob, ["array"], null); + let VariantType : Candid.CandidType = #Variant([ + ("text", #Text), + ("nat", #Nat), + ("bool", #Bool), + ("record", #Record([("site", #Text)])), + ("array", #Array(#Nat)), + ]); + + let #ok(text_candid) = CandidTestUtils.decode_with_types([VariantType], ["text"], text_blob, null) else return assert false; + let #ok(nat_candid) = CandidTestUtils.decode_with_types([VariantType], ["nat"], nat_blob, null) else return assert false; + let #ok(bool_candid) = CandidTestUtils.decode_with_types([VariantType], ["bool"], bool_blob, null) else return assert false; + let #ok(record_candid) = CandidTestUtils.decode_with_types([VariantType], ["record", "site"], record_blob, null) else return assert false; + let #ok(array_candid) = CandidTestUtils.decode_with_types([VariantType], ["array"], array_blob, null) else return assert false; - assert (text_candid == #ok([#Variant("text", #Text("hello"))])); - assert (nat_candid == #ok([#Variant("nat", #Nat(123))])); - assert (bool_candid == #ok([#Variant("bool", #Bool(true))])); - assert (record_candid == #ok([#Variant("record", #Record([("site", #Text("github"))]))])); - assert (array_candid == #ok([#Variant("array", #Array([#Nat(1), #Nat(2), #Nat(3)]))])); + assert (text_candid == [#Variant("text", #Text("hello"))]); + assert (nat_candid == [#Variant("nat", #Nat(123))]); + assert (bool_candid == [#Variant("bool", #Bool(true))]); + assert (record_candid == [#Variant("record", #Record([("site", #Text("github"))]))]); + assert (array_candid == [#Variant("array", #Array([#Nat(1), #Nat(2), #Nat(3)]))]); }, ); @@ -335,9 +396,12 @@ suite( ]; let blob = to_candid (users); - let candid = Candid.decode(blob, record_keys, null); + + let UsersType : Candid.CandidType = #Array(#Record([("name", #Text), ("age", #Nat8), ("email", #Option(#Text)), ("registered", #Bool)])); + + let #ok(candid) = CandidTestUtils.decode_with_types([UsersType], record_keys, blob, null) else return assert false; Debug.print("candid" # debug_show candid); - assert candid == #ok([ + assert candid == [ #Array([ #Record([ ("age", #Nat8(32)), @@ -358,7 +422,7 @@ suite( ("registered", #Bool(true)), ]), ]), - ]); + ]; }, ); }, @@ -475,7 +539,11 @@ suite( base_record_candid, ]); - let #ok(blob) = Candid.encodeOne(records, null); + let RecordsType : Candid.CandidType = #Array( + #Record([("group", #Text), ("users", #Option(#Array(#Record([("age", #Nat), ("name", #Text), ("permission", #Variant([("read", #Array(#Text)), ("write", #Array(#Text)), ("read_all", #Null), ("write_all", #Null), ("admin", #Null)]))]))))]) + ); + + let #ok(blob) = CandidTestUtils.encode_with_types([RecordsType], [records], null) else return assert false; let motoko : ?[Record] = from_candid (blob); assert motoko == ?[ @@ -511,12 +579,16 @@ suite( daily_downloads : [Nat]; }; + let Data : Candid.CandidType = #Array( + #Record([("array", #Array(#Nat)), ("name", #Text)]) + ); + let options = { Candid.defaultOptions with renameKeys = [("array", "daily_downloads"), ("name", "language")]; }; - let #ok(blob) = Candid.encodeOne(candid, ?options); + let #ok(blob) = CandidTestUtils.encode_with_types([Data], [candid], ?options) else return assert false; let motoko : ?[Data] = from_candid (blob); assert motoko == ?[{ language = "candid"; daily_downloads = [1, 2, 3, 4] }, { language = "motoko"; daily_downloads = [5, 6, 7, 8] }, { language = "rust"; daily_downloads = [9, 10, 11, 12] }]; }, @@ -529,7 +601,9 @@ suite( name : Text; }; - let #ok(blob) = Candid.encodeOne(candid, null); + let UserType : Candid.CandidType = #Record([("name", #Text)]); + + let #ok(blob) = CandidTestUtils.encode_with_types([UserType], [candid], null) else return assert false; let user : ?User = from_candid (blob); assert user == ?{ name = "candid" }; @@ -550,8 +624,11 @@ suite( #Array([#Array([#Text("hello"), #Text("world")]), #Array([#Text("foo"), #Text("bar")])]), ]); - let #ok(arr2_blob) = Candid.encodeOne(arr2, null); - let #ok(arr3_blob) = Candid.encodeOne(arr3, null); + let Arr2Type : Candid.CandidType = #Array(#Array(#Nat)); + let Arr3Type : Candid.CandidType = #Array(#Array(#Array(#Text))); + + let #ok(arr2_blob) = CandidTestUtils.encode_with_types([Arr2Type], [arr2], null) else return assert false; + let #ok(arr3_blob) = CandidTestUtils.encode_with_types([Arr3Type], [arr3], null) else return assert false; let arr2_encoded : ?[[Nat]] = from_candid (arr2_blob); let arr3_encoded : ?[[[Text]]] = from_candid (arr3_blob); @@ -574,8 +651,11 @@ suite( let candid_1 = #Array([#Nat8(1 : Nat8), #Nat8(2 : Nat8), #Nat8(3 : Nat8), #Nat8(4 : Nat8)]); let candid_2 = #Blob(motoko_blob); - let #ok(serialized_1) = Candid.encodeOne(candid_1, null); - let #ok(serialized_2) = Candid.encodeOne(candid_2, null); + let BlobType : Candid.CandidType = #Blob; + let Nat8ArrayType : Candid.CandidType = #Array(#Nat8); + + let #ok(serialized_1) = CandidTestUtils.encode_with_types([Nat8ArrayType], [candid_1], null) else return assert false; + let #ok(serialized_2) = CandidTestUtils.encode_with_types([BlobType], [candid_2], null) else return assert false; let blob_1 : ?Blob = from_candid (serialized_1); let blob_2 : ?Blob = from_candid (serialized_2); @@ -607,11 +687,13 @@ suite( let record = #Variant("record", #Record([("site", #Text("github"))])); let array = #Variant("array", #Array([#Nat(1), #Nat(2), #Nat(3)])); - let #ok(text_blob) = Candid.encodeOne(text, null); - let #ok(nat_blob) = Candid.encodeOne(nat, null); - let #ok(bool_blob) = Candid.encodeOne(bool, null); - let #ok(record_blob) = Candid.encodeOne(record, null); - let #ok(array_blob) = Candid.encodeOne(array, null); + let VariantType : Candid.CandidType = #Variant([("text", #Text), ("nat", #Nat), ("bool", #Bool), ("record", #Record([("site", #Text)])), ("array", #Array(#Nat))]); + + let #ok(text_blob) = CandidTestUtils.encode_with_types([VariantType], [text], null) else return assert false; + let #ok(nat_blob) = CandidTestUtils.encode_with_types([VariantType], [nat], null) else return assert false; + let #ok(bool_blob) = CandidTestUtils.encode_with_types([VariantType], [bool], null) else return assert false; + let #ok(record_blob) = CandidTestUtils.encode_with_types([VariantType], [record], null) else return assert false; + let #ok(array_blob) = CandidTestUtils.encode_with_types([VariantType], [array], null) else return assert false; let text_val : ?Variant = from_candid (text_blob); let nat_val : ?Variant = from_candid (nat_blob); @@ -888,9 +970,14 @@ suite( let motoko = { name = "candid"; age = 32 }; let blob = to_candid (motoko); - let #ok(candid) = Candid.decode(blob, ["name", "age"], null); // decode without keys - let #ok(blob_2) = Candid.encode(candid, null); + let UserDecodeType : Candid.CandidType = #Record([("name", #Text), ("age", #Nat)]); + + let #ok(candid) = CandidTestUtils.decode_with_types([UserDecodeType], ["name", "age"], blob, null) else return assert false; // decode without keys + + let UserType : Candid.CandidType = #Record([("name", #Text), ("age", #Nat)]); + + let #ok(blob_2) = CandidTestUtils.encode_with_types([UserType], candid, null) else return assert false; let motoko_2 : ?User = from_candid (blob_2); assert motoko_2 == ?motoko; @@ -914,7 +1001,9 @@ suite( ("details", #Record([("age", #Nat(32)), ("email", #Option(#Text("example@gmail.com"))), ("registered", #Bool(true))])), ]); - let #ok(blob) = Candid.encodeOne(candid, null); + let UserType : Candid.CandidType = #Record([("name", #Text), ("details", #Record([("age", #Nat), ("email", #Option(#Text)), ("registered", #Bool)]))]); + + let #ok(blob) = CandidTestUtils.encode_with_types([UserType], [candid], null) else return assert false; let mo : ?User = from_candid (blob); assert mo == ?{ diff --git a/tests/CandidTestUtils.mo b/tests/CandidTestUtils.mo new file mode 100644 index 0000000..b5ca31a --- /dev/null +++ b/tests/CandidTestUtils.mo @@ -0,0 +1,102 @@ +// @testmode wasi +import Blob "mo:base/Blob"; +import Debug "mo:base/Debug"; +import Iter "mo:base/Iter"; +import Nat "mo:base/Nat"; +import Principal "mo:base/Principal"; +import Result "mo:base/Result"; +import Option "mo:base/Option"; + +import { test; suite } "mo:test"; + +import Serde "../src"; +import Candid "../src/Candid"; +import Encoder "../src/Candid/Blob/Encoder"; + +module { + type Candid = Candid.Candid; + + public func encode_with_types(types : [Candid.CandidType], vals : [Candid], _options : ?Candid.Options) : Result.Result { + let options = Option.get(_options, Candid.defaultOptions); + + let single_function_encoding = switch (Candid.encode(vals, ?options)) { + case (#ok(blob)) blob; + case (#err(err)) { + Debug.print("encode function failed: " # err); + return #err("encode function failed: " #err); + }; + }; + + let encoder = Candid.TypedSerializer.new(types, ?options); + let encoded_blob_res = Candid.TypedSerializer.encode(encoder, vals); + let encoder_instance_blob = switch (encoded_blob_res) { + case (#ok(blob)) blob; + case (#err(err)) { + Debug.print("encoder instance failed: " # err); + return #err("encoder instance failed: " # err); + }; + }; + + if (encoder_instance_blob != single_function_encoding) { + + let single_function_encoding = switch (Candid.encode(vals, ?{ options with types = ?Candid.formatCandidType(types, ?options.renameKeys) })) { + case (#ok(blob)) blob; + case (#err(err)) { + Debug.print("encode function failed: " # err); + return #err("encode function failed: " #err); + }; + }; + + if (encoder_instance_blob != single_function_encoding) { + Debug.print("Encoded blob does not match the original encoding:"); + Debug.print("Single function: " # debug_show (Blob.toArray(single_function_encoding))); + Debug.print("Encoder instance: " # debug_show (Blob.toArray(encoder_instance_blob))); + return #err("Encoded blob does not match the original encoding: " # debug_show ({ single_function_encoding; encoder_instance_blob })); + }; + }; + + // assert Candid.TypedSerializer.equal(encoder, Candid.TypedSerializer.fromBlob(encoder_instance_blob, [], ?options)); + + return #ok(encoder_instance_blob); + }; + + public func decode_with_types(types : [Candid.CandidType], record_keys : [Text], blob : Blob, _options : ?Candid.Options) : Result.Result<[Candid], Text> { + let options = Option.get(_options, Candid.defaultOptions); + + let single_function_decoding = switch (Candid.decode(blob, record_keys, ?options)) { + case (#ok(vals)) vals; + case (#err(err)) { + Debug.print("decode function failed: " # err); + return #err("decode function failed: " # err); + }; + }; + + let decoder = Candid.TypedSerializer.fromBlob(blob, record_keys, ?options); + let decoded_vals_res = Candid.TypedSerializer.decode(decoder, blob); + let decoder_instance_vals = switch (decoded_vals_res) { + case (#ok(vals)) vals; + case (#err(err)) { + Debug.print("decoder instance failed: " # err); + return #err("decoder instance failed: " # err); + }; + }; + + if (decoder_instance_vals != single_function_decoding) { + Debug.print("Decoded values do not match the original decoding:"); + Debug.print("Single function: " # debug_show (single_function_decoding)); + Debug.print("Decoder instance: " # debug_show (decoder_instance_vals)); + return #err("Decoded values do not match the original decoding: " # debug_show ({ single_function_decoding; decoder_instance_vals })); + }; + + // !important: the decoder instance should be equal to a new TypedSerializer created with the same types and options + // if (not Candid.TypedSerializer.equal(decoder, Candid.TypedSerializer.new(types, ?options))) { + // Debug.print("Decoder and new TypedSerializer are not equal."); + // Debug.print("Decoder: " # Candid.TypedSerializer.toText(decoder)); + // Debug.print("New TypedSerializer: " # Candid.TypedSerializer.toText(Candid.TypedSerializer.new(types, ?options))); + // return #err("Decoder and new TypedSerializer are not equal."); + // }; + + return #ok(decoder_instance_vals); + }; + +}; diff --git a/tests/Candid.ICRC3.Test.mo b/tests/ICRC3.Test.mo similarity index 81% rename from tests/Candid.ICRC3.Test.mo rename to tests/ICRC3.Test.mo index 9371d87..6a04722 100644 --- a/tests/Candid.ICRC3.Test.mo +++ b/tests/ICRC3.Test.mo @@ -9,6 +9,7 @@ import Text "mo:base/Text"; import { test; suite } "mo:test"; import Serde "../src"; +import CandidTestUtils "CandidTestUtils"; let { Candid } = Serde; @@ -32,7 +33,9 @@ suite( ("b", #Nat(2)), ]); - let #ok(record_candid_blob2) = Candid.encode(record_candid, ?options); + let RecordType : Candid.CandidType = #Map([("a", #Nat), ("b", #Nat)]); + + let #ok(record_candid_blob2) = CandidTestUtils.encode_with_types([RecordType], record_candid, ?options) else return assert false; assert record_candid_blob == record_candid_blob2; @@ -81,7 +84,9 @@ suite( let candid_values = Candid.fromICRC3Value([icrc3]); - let #ok(blob) = Candid.encode(candid_values, null); + let UserType : Candid.CandidType = #Record([("name", #Text), ("id", #Nat)]); + + let #ok(blob) = CandidTestUtils.encode_with_types([UserType], candid_values, null) else return assert false; let user : ?User = from_candid (blob); assert user == ?{ name = "bar"; id = 112 }; diff --git a/tests/Candid.PrimitiveType.Test.mo b/tests/PrimitiveType.Test.mo similarity index 55% rename from tests/Candid.PrimitiveType.Test.mo rename to tests/PrimitiveType.Test.mo index a20931e..257ede6 100644 --- a/tests/Candid.PrimitiveType.Test.mo +++ b/tests/PrimitiveType.Test.mo @@ -12,6 +12,8 @@ import Candid "../src/Candid"; import Encoder "../src/Candid/Blob/Encoder"; import Fuzz "mo:fuzz"; +import CandidTestUtils "CandidTestUtils"; + let fuzz = Fuzz.fromSeed(0x12345678); let limit = 10_000; @@ -32,6 +34,13 @@ suite( // Debug.print("Candid mismatch for Int: " # debug_show (int, candid_variant)); assert candid_variant == #ok([#Int(int)]); + + let IntType : Candid.CandidType = #Int; + let #ok(blob) = CandidTestUtils.encode_with_types([IntType], [#Int(int)], null) else return assert false; + assert blob == candid_encoding; + + let decoded_int : ?Int = from_candid (candid_encoding); + assert decoded_int == ?int; }; }, ); @@ -47,6 +56,13 @@ suite( let candid_variant = Candid.decode(candid_encoding, [], null); assert candid_variant == #ok([#Nat8(nat8)]); + + let Nat8Type : Candid.CandidType = #Nat8; + let #ok(blob) = CandidTestUtils.encode_with_types([Nat8Type], [#Nat8(nat8)], null) else return assert false; + assert blob == candid_encoding; + + let decoded_nat8 : ?Nat8 = from_candid (candid_encoding); + assert decoded_nat8 == ?nat8; }; }, ); @@ -62,6 +78,13 @@ suite( let candid_variant = Candid.decode(candid_encoding, [], null); assert candid_variant == #ok([#Nat16(nat16)]); + + let Nat16Type : Candid.CandidType = #Nat16; + let #ok(blob) = CandidTestUtils.encode_with_types([Nat16Type], [#Nat16(nat16)], null) else return assert false; + assert blob == candid_encoding; + + let decoded_nat16 : ?Nat16 = from_candid (candid_encoding); + assert decoded_nat16 == ?nat16; }; }, ); @@ -77,6 +100,13 @@ suite( let candid_variant = Candid.decode(candid_encoding, [], null); assert candid_variant == #ok([#Nat32(nat32)]); + + let Nat32Type : Candid.CandidType = #Nat32; + let #ok(blob) = CandidTestUtils.encode_with_types([Nat32Type], [#Nat32(nat32)], null) else return assert false; + assert blob == candid_encoding; + + let decoded_nat32 : ?Nat32 = from_candid (candid_encoding); + assert decoded_nat32 == ?nat32; }; }, ); @@ -92,6 +122,13 @@ suite( let candid_variant = Candid.decode(candid_encoding, [], null); assert candid_variant == #ok([#Nat64(nat64)]); + + let Nat64Type : Candid.CandidType = #Nat64; + let #ok(blob) = CandidTestUtils.encode_with_types([Nat64Type], [#Nat64(nat64)], null) else return assert false; + assert blob == candid_encoding; + + let decoded_nat64 : ?Nat64 = from_candid (candid_encoding); + assert decoded_nat64 == ?nat64; }; }, ); @@ -107,6 +144,13 @@ suite( let candid_variant = Candid.decode(candid_encoding, [], null); assert candid_variant == #ok([#Int8(int8)]); + + let Int8Type : Candid.CandidType = #Int8; + let #ok(blob) = CandidTestUtils.encode_with_types([Int8Type], [#Int8(int8)], null) else return assert false; + assert blob == candid_encoding; + + let decoded_int8 : ?Int8 = from_candid (candid_encoding); + assert decoded_int8 == ?int8; }; }, ); @@ -122,6 +166,13 @@ suite( let candid_variant = Candid.decode(candid_encoding, [], null); assert candid_variant == #ok([#Int16(int16)]); + + let Int16Type : Candid.CandidType = #Int16; + let #ok(blob) = CandidTestUtils.encode_with_types([Int16Type], [#Int16(int16)], null) else return assert false; + assert blob == candid_encoding; + + let decoded_int16 : ?Int16 = from_candid (candid_encoding); + assert decoded_int16 == ?int16; }; }, ); @@ -137,6 +188,13 @@ suite( let candid_variant = Candid.decode(candid_encoding, [], null); assert candid_variant == #ok([#Int32(int32)]); + + let Int32Type : Candid.CandidType = #Int32; + let #ok(blob) = CandidTestUtils.encode_with_types([Int32Type], [#Int32(int32)], null) else return assert false; + assert blob == candid_encoding; + + let decoded_int32 : ?Int32 = from_candid (candid_encoding); + assert decoded_int32 == ?int32; }; }, ); @@ -152,6 +210,13 @@ suite( let candid_variant = Candid.decode(candid_encoding, [], null); assert candid_variant == #ok([#Int64(int64)]); + + let Int64Type : Candid.CandidType = #Int64; + let #ok(blob) = CandidTestUtils.encode_with_types([Int64Type], [#Int64(int64)], null) else return assert false; + assert blob == candid_encoding; + + let decoded_int64 : ?Int64 = from_candid (candid_encoding); + assert decoded_int64 == ?int64; }; }, ); @@ -167,6 +232,13 @@ suite( let candid_variant = Candid.decode(candid_encoding, [], null); assert candid_variant == #ok([#Text(text)]); + + let TextType : Candid.CandidType = #Text; + let #ok(blob) = CandidTestUtils.encode_with_types([TextType], [#Text(text)], null) else return assert false; + assert blob == candid_encoding; + + let decoded_text : ?Text = from_candid (candid_encoding); + assert decoded_text == ?text; }; }, ); @@ -182,6 +254,13 @@ suite( let candid_variant = Candid.decode(candid_encoding, [], null); assert candid_variant == #ok([#Principal(principal)]); + + let PrincipalType : Candid.CandidType = #Principal; + let #ok(blob) = CandidTestUtils.encode_with_types([PrincipalType], [#Principal(principal)], null) else return assert false; + assert blob == candid_encoding; + + let decoded_principal : ?Principal = from_candid (candid_encoding); + assert decoded_principal == ?principal; }; }, ); @@ -197,6 +276,15 @@ suite( let candid_variant = Candid.decode(candid_encoding, [], null); assert candid_variant == #ok([#Blob(blob)]); + + let BlobType : Candid.CandidType = #Blob; + let #ok(encoded_blob) = CandidTestUtils.encode_with_types([BlobType], [#Blob(blob)], null) else return assert false; + Debug.print("comparing encoded blob: " # debug_show (encoded_blob)); + Debug.print("with candid encoding: " # debug_show (candid_encoding)); + assert encoded_blob == candid_encoding; + + let decoded_blob : ?Blob = from_candid (candid_encoding); + assert decoded_blob == ?blob; }; }, ); diff --git a/tests/RepIndyHash.Test.mo b/tests/RepIndyHash.Test.mo index 4a726bf..42b0476 100644 --- a/tests/RepIndyHash.Test.mo +++ b/tests/RepIndyHash.Test.mo @@ -8,84 +8,83 @@ import { Candid } "../src"; import RepIndyHash "mo:rep-indy-hash"; func validate_hash(candid_record : Candid.Candid, icrc3_value_record : RepIndyHash.Value) : Bool { - let candid_hash = Candid.repIndyHash(candid_record); - let expected = RepIndyHash.hash_val(icrc3_value_record) |> Blob.fromArray(_); + let candid_hash = Candid.repIndyHash(candid_record); + let expected = RepIndyHash.hash_val(icrc3_value_record) |> Blob.fromArray(_); - candid_hash == expected; + candid_hash == expected; }; suite( - "Representation Independent Hash Test", - func() { - test( - "#Nat", - func() { - assert validate_hash(#Nat(1), #Nat(1)); - assert validate_hash(#Nat(22345), #Nat(22345)); - - }, - ); - - test( - "#Int", - func() { - let candid_record : Candid.Candid = #Int(42); - let icrc3_value_record : RepIndyHash.Value = #Int(42); - - assert validate_hash(candid_record, icrc3_value_record); - - }, - ); - - test( - "#Text", - func() { - let candid_record : Candid.Candid = #Text("hello"); - let icrc3_value_record : RepIndyHash.Value = #Text("hello"); - - assert validate_hash(candid_record, icrc3_value_record); - }, - ); - - test( - "#Blob", - func() { - let candid_record : Candid.Candid = #Blob("\00\01\02"); - let icrc3_value_record : RepIndyHash.Value = #Blob("\00\01\02"); - - assert validate_hash(candid_record, icrc3_value_record); - }, - ); - - test( - "#Array", - func() { - let candid_record : Candid.Candid = #Array([#Text("hello"), #Text("world")]); - let icrc3_value_record : RepIndyHash.Value = #Array([#Text("hello"), #Text("world")]); - - assert validate_hash(candid_record, icrc3_value_record); - }, - ); - - test( - "#Record/#Map", - func() { - let candid_record : Candid.Candid = #Map([ - ("a", #Nat(1)), - ("b", #Array([#Text("hello"), #Text("world")])), - ("c", #Blob("\00\01\02")), - ("d", #Int(42)), - ]); - let icrc3_value_record : RepIndyHash.Value = #Map([ - ("a", #Nat(1)), - ("b", #Array([#Text("hello"), #Text("world")])), - ("c", #Blob("\00\01\02")), - ("d", #Int(42)), - ]); - - assert validate_hash(candid_record, icrc3_value_record); - - }, - ); - }, + "Representation Independent Hash Test", + func() { + test( + "#Nat", + func() { + assert validate_hash(#Nat(1), #Nat(1)); + assert validate_hash(#Nat(22345), #Nat(22345)); + }, + ); + + test( + "#Int", + func() { + let candid_record : Candid.Candid = #Int(42); + let icrc3_value_record : RepIndyHash.Value = #Int(42); + + assert validate_hash(candid_record, icrc3_value_record); + + }, + ); + + test( + "#Text", + func() { + let candid_record : Candid.Candid = #Text("hello"); + let icrc3_value_record : RepIndyHash.Value = #Text("hello"); + + assert validate_hash(candid_record, icrc3_value_record); + }, + ); + + test( + "#Blob", + func() { + let candid_record : Candid.Candid = #Blob("\00\01\02"); + let icrc3_value_record : RepIndyHash.Value = #Blob("\00\01\02"); + + assert validate_hash(candid_record, icrc3_value_record); + }, + ); + + test( + "#Array", + func() { + let candid_record : Candid.Candid = #Array([#Text("hello"), #Text("world")]); + let icrc3_value_record : RepIndyHash.Value = #Array([#Text("hello"), #Text("world")]); + + assert validate_hash(candid_record, icrc3_value_record); + }, + ); + + test( + "#Record/#Map", + func() { + let candid_record : Candid.Candid = #Map([ + ("a", #Nat(1)), + ("b", #Array([#Text("hello"), #Text("world")])), + ("c", #Blob("\00\01\02")), + ("d", #Int(42)), + ]); + let icrc3_value_record : RepIndyHash.Value = #Map([ + ("a", #Nat(1)), + ("b", #Array([#Text("hello"), #Text("world")])), + ("c", #Blob("\00\01\02")), + ("d", #Int(42)), + ]); + + assert validate_hash(candid_record, icrc3_value_record); + + }, + ); + }, ); diff --git a/tests/Stress.test.mo b/tests/Stress.test.mo new file mode 100644 index 0000000..503d54e --- /dev/null +++ b/tests/Stress.test.mo @@ -0,0 +1,410 @@ +// @testmode wasi +import Iter "mo:base/Iter"; +import Debug "mo:base/Debug"; +import Prelude "mo:base/Prelude"; +import Text "mo:base/Text"; +import Char "mo:base/Char"; +import Buffer "mo:base/Buffer"; +import Nat64 "mo:base/Nat64"; + +import Fuzz "mo:fuzz"; +import Itertools "mo:itertools/Iter"; +import { test; suite } "mo:test"; + +import Serde "../src"; +import CandidEncoder "../src/Candid/Blob/Encoder"; +import CandidDecoder "../src/Candid/Blob/Decoder"; +import LegacyCandidDecoder "../src/Candid/Blob/Decoder"; +import LegacyCandidEncoder "../src/Candid/Blob/Encoder"; + +import CandidTestUtils "CandidTestUtils"; + +func createGenerator(seed : Nat) : { next() : Nat } { + // Pure bitwise xorshift64 - no multiplication or addition! + var state : Nat64 = Nat64.fromNat(seed); + if (state == 0) state := 1; // Avoid zero state + + { + next = func() : Nat { + // Only XOR and bit shifts - fastest possible + state ^= state << 13 : Nat64; + state ^= state >> 7 : Nat64; + state ^= state << 17 : Nat64; + Nat64.toNat(state); + }; + }; +}; + +let fuzz = Fuzz.create(createGenerator(42)); + +let limit = 1_000; + +let candify_store_item = { + from_blob = func(blob : Blob) : StoreItem { + let ?c : ?StoreItem = from_candid (blob); + c; + }; + to_blob = func(c : StoreItem) : Blob { to_candid (c) }; +}; + +type X = { name : Text; x : X }; + +// let x: X = { +// name = "yo"; +// x = { +// name = "yo"; +// x = {}; +// }; +// }; + +// let x : Serde.Candid = #Record([ +// ("name", #Text("yo")), +// ("x", x) +// ]); +type CustomerReview = { + username : Text; + rating : Nat; + comment : Text; +}; + +let CustomerReview = #Record([ + ("username", #Text), + ("rating", #Nat), + ("comment", #Text), +]); + +type AvailableSizes = { #xs; #s; #m; #l; #xl }; + +let AvailableSizes = #Variant([ + ("xs", #Null), + ("s", #Null), + ("m", #Null), + ("l", #Null), + ("xl", #Null), +]); + +type ColorOption = { + name : Text; + hex : Text; +}; + +let ColorOption = #Record([ + ("name", #Text), + ("hex", #Text), +]); + +type StoreItem = { + name : Text; + store : Text; + customer_reviews : [CustomerReview]; + available_sizes : AvailableSizes; + color_options : [ColorOption]; + price : Float; + in_stock : Bool; + address : (Text, Text, Text, Text); + contact : { + email : Text; + phone : ?Text; + }; +}; + +let StoreItem : Serde.Candid.CandidType = #Record([ + ("name", #Text), + ("store", #Text), + ("customer_reviews", #Array(CustomerReview)), + ("available_sizes", AvailableSizes), + ("color_options", #Array(ColorOption)), + ("price", #Float), + ("in_stock", #Bool), + ("address", #Tuple([#Text, #Text, #Text, #Text])), + ("contact", #Record([("email", #Text), ("phone", #Option(#Text))])), +]); + +let FormattedStoreItem = Serde.Candid.formatCandidType([StoreItem], null); + +let cities = ["Toronto", "Ottawa", "New York", "Los Angeles", "Chicago", "Houston", "Phoenix", "Philadelphia", "San Antonio", "San Diego", "Dallas", "San Jose"]; +let states = ["ON", "QC", "NY", "CA", "IL", "TX", "AZ", "PA", "TX", "CA", "TX", "CA"]; +let streets = ["King St", "Queen St", "Yonge St", "Bay St", "Bloor St", "Dundas St", "College St", "Spadina Ave", "St Clair Ave", "Danforth Ave", "Eglinton Ave", "Lawrence Ave"]; + +let stores = ["h&m", "zara", "gap", "old navy", "forever 21", "uniqlo", "urban outfitters", "american eagle", "aeropostale", "abercrombie & fitch", "hollister", "express"]; +let email_terminator = ["gmail.com", "yahoo.com", "outlook.com"]; + +let cs_starter_kid = ["black hoodie", "M1 macbook", "white hoodie", "air forces", "Algorithms textbook", "c the hard way", "Udemy subscription", "Nvidea RTX"]; + +let available_sizes = [#xs, #s, #m, #l, #xl]; + +func new_item() : StoreItem { + let store_name = fuzz.array.randomEntry(stores).1; + let store_item : StoreItem = { + name = fuzz.array.randomEntry(cs_starter_kid).1; + store = store_name; + customer_reviews = [ + { + username = "user1"; + rating = fuzz.nat.randomRange(0, 5); + comment = "good"; + }, + { + username = "user2"; + rating = fuzz.nat.randomRange(0, 5); + comment = "ok"; + }, + ]; + available_sizes = fuzz.array.randomEntry(available_sizes).1; + color_options = [ + { name = "red"; hex = "#ff0000" }, + { name = "blue"; hex = "#0000ff" }, + ]; + price = fuzz.float.randomRange(19.99, 399.99); + in_stock = fuzz.bool.random(); + address = ( + fuzz.array.randomEntry(streets).1, + fuzz.array.randomEntry(cities).1, + fuzz.array.randomEntry(states).1, + fuzz.text.randomAlphanumeric(6), + ); + contact = { + email = store_name # "@" # fuzz.array.randomEntry(email_terminator).1; + phone = if (fuzz.nat.randomRange(0, 100) % 3 == 0) { null } else { + ?Text.fromIter( + fuzz.array.randomArray(10, func() : Char { Char.fromNat32(fuzz.nat32.randomRange(0, 9) + Char.toNat32('0')) }).vals() : Iter.Iter + ); + }; + }; + }; +}; + +let store_item_keys = ["name", "store", "customer_reviews", "username", "rating", "comment", "available_sizes", "xs", "s", "m", "l", "xl", "color_options", "name", "hex", "price", "in_stock", "address", "contact", "email", "phone"]; + +let candid_buffer = Buffer.Buffer<[Serde.Candid]>(limit); +let store_items = Buffer.Buffer(limit); + +let candid_buffer_with_types = Buffer.Buffer<[Serde.Candid]>(limit); +let store_items_with_types = Buffer.Buffer(limit); + +// Roundtrip test function that takes a schema, value generator, and comparison function +func roundtripTest( + schema : Serde.Candid.CandidType, + valueGenerator : () -> T, + toBlobFn : (T) -> Blob, + fromBlobFn : (Blob) -> T, + keys : [Text], + iterations : Nat, +) : () { + let values = Buffer.Buffer(iterations); + let candid_values = Buffer.Buffer<[Serde.Candid]>(iterations); + + // Phase 1: Generate values and decode them to Candid + for (i in Itertools.range(0, iterations)) { + let value = valueGenerator(); + values.add(value); + let blob = toBlobFn(value); + let #ok(candid) = CandidDecoder.one_shot(blob, keys, null); + candid_values.add(candid); + }; + + // Phase 2: Encode Candid values back to blobs and verify roundtrip + for (i in Itertools.range(0, iterations)) { + let candid = candid_values.get(i); + let originalValue = values.get(i); + + // Use CandidTestUtils.encode_with_types for consistency validation + let #ok(encodedBlob) = CandidTestUtils.encode_with_types([schema], candid, null); + let decodedValue = fromBlobFn(encodedBlob); + + assert decodedValue == originalValue; + }; +}; + +suite( + "Serde.Candid", + func() { + test( + "decode()", + func() { + for (i in Itertools.range(0, limit)) { + let item = new_item(); + store_items.add(item); + let candid_blob = candify_store_item.to_blob(item); + let #ok(candid) = CandidDecoder.one_shot(candid_blob, store_item_keys, null); + candid_buffer.add(candid); + }; + }, + ); + test( + "encode()", + func() { + for (i in Itertools.range(0, limit)) { + let candid = candid_buffer.get(i); + let res = LegacyCandidEncoder.encode(candid, null); + let #ok(blob) = res; + let item = candify_store_item.from_blob(blob); + Debug.print("item: " # debug_show item); + Debug.print("store_items: " # debug_show store_items.get(i)); + assert item == store_items.get(i); + }; + }, + ); + test( + "decode() with types", + func() { + for (i in Itertools.range(0, limit)) { + let item = new_item(); + store_items_with_types.add(item); + let candid_blob = candify_store_item.to_blob(item); + let #ok(split_blob) = CandidDecoder.split(candid_blob, null); + let #ok(candid) = CandidDecoder.one_shot(candid_blob, store_item_keys, ?{ Serde.Candid.defaultOptions with types = ?FormattedStoreItem }); + candid_buffer_with_types.add(candid); + }; + }, + ); + test( + "encode() with types", + func() { + for (i in Itertools.range(0, limit)) { + let candid = candid_buffer_with_types.get(i); + let res = LegacyCandidEncoder.encode(candid, ?{ Serde.Candid.defaultOptions with types = ?FormattedStoreItem }); + let #ok(blob) = res; + let item = candify_store_item.from_blob(blob); + assert item == store_items_with_types.get(i); + }; + }, + ); + + test( + "roundtrip StoreItem with schema validation", + func() { + roundtripTest( + StoreItem, + new_item, + candify_store_item.to_blob, + candify_store_item.from_blob, + store_item_keys, + 100 // Smaller iteration count for this test + ); + }, + ); + }, +); + +suite( + "Roundtrip Tests", + func() { + test( + "roundtrip simple record", + func() { + type SimpleRecord = { name : Text; age : Nat }; + let SimpleRecordSchema : Serde.Candid.CandidType = #Record([("name", #Text), ("age", #Nat)]); + + let simpleRecordUtils = { + to_blob = func(r : SimpleRecord) : Blob { to_candid (r) }; + from_blob = func(blob : Blob) : SimpleRecord { + let ?r : ?SimpleRecord = from_candid (blob); + r; + }; + }; + + roundtripTest( + SimpleRecordSchema, + func() : SimpleRecord { + { + name = fuzz.text.randomAlphanumeric(10); + age = fuzz.nat.randomRange(0, 100); + }; + }, + simpleRecordUtils.to_blob, + simpleRecordUtils.from_blob, + ["name", "age"], + 100, + ); + }, + ); + + test( + "roundtrip variant type", + func() { + type SimpleVariant = { #text : Text; #num : Nat; #flag : Bool }; + let SimpleVariantSchema : Serde.Candid.CandidType = #Variant([("text", #Text), ("num", #Nat), ("flag", #Bool)]); + + let simpleVariantUtils = { + to_blob = func(v : SimpleVariant) : Blob { to_candid (v) }; + from_blob = func(blob : Blob) : SimpleVariant { + let ?v : ?SimpleVariant = from_candid (blob); + v; + }; + }; + + roundtripTest( + SimpleVariantSchema, + func() : SimpleVariant { + switch (fuzz.nat.randomRange(0, 2)) { + case (0) { #text(fuzz.text.randomAlphanumeric(5)) }; + case (1) { #num(fuzz.nat.randomRange(0, 1000)) }; + case (_) { #flag(fuzz.bool.random()) }; + }; + }, + simpleVariantUtils.to_blob, + simpleVariantUtils.from_blob, + ["text", "num", "flag"], + 100, + ); + }, + ); + + test( + "roundtrip array type", + func() { + let ArraySchema : Serde.Candid.CandidType = #Array(#Nat); + + let arrayUtils = { + to_blob = func(arr : [Nat]) : Blob { to_candid (arr) }; + from_blob = func(blob : Blob) : [Nat] { + let ?arr : ?[Nat] = from_candid (blob); + arr; + }; + }; + + roundtripTest<[Nat]>( + ArraySchema, + func() : [Nat] { + fuzz.array.randomArray(fuzz.nat.randomRange(0, 10), func() : Nat { fuzz.nat.randomRange(0, 1000) }); + }, + arrayUtils.to_blob, + arrayUtils.from_blob, + [], + 100, + ); + }, + ); + + test( + "roundtrip optional type", + func() { + let OptionalSchema : Serde.Candid.CandidType = #Option(#Text); + + let optionalUtils = { + to_blob = func(opt : ?Text) : Blob { to_candid (opt) }; + from_blob = func(blob : Blob) : ?Text { + let ?opt : ??Text = from_candid (blob); + opt; + }; + }; + + roundtripTest( + OptionalSchema, + func() : ?Text { + if (fuzz.bool.random()) { + ?fuzz.text.randomAlphanumeric(8); + } else { + null; + }; + }, + optionalUtils.to_blob, + optionalUtils.from_blob, + [], + 100, + ); + }, + ); + }, +); From 54f2ccf55f2b9937135ab5fb7b4b8e716c6e1c16 Mon Sep 17 00:00:00 2001 From: Tomi Jaga Date: Sat, 2 Aug 2025 12:16:11 -0700 Subject: [PATCH 3/6] Replace binary operations with ByteUtils module in RepIndyHash implementation --- mops.toml | 4 +- src/Candid/Blob/Decoder.mo | 6 +- src/Candid/Blob/Encoder.mo | 3488 ++++++++++++++++---------------- src/Candid/Blob/RepIndyHash.mo | 52 +- 4 files changed, 1760 insertions(+), 1790 deletions(-) diff --git a/mops.toml b/mops.toml index 28050c5..96a50c0 100644 --- a/mops.toml +++ b/mops.toml @@ -3,7 +3,7 @@ name = "serde" version = "3.3.1" description = "A serialisation and deserialisation library for Motoko." repository = "https://github.com/NatLabs/serde" -keywords = ["json", "candid", "cbor", "urlencoded", "serialization"] +keywords = [ "json", "candid", "cbor", "urlencoded", "serialization" ] license = "MIT" [dependencies] @@ -15,7 +15,7 @@ cbor = "2.0.0" map = "9.0.1" sha2 = "0.1.6" fuzz = "1.0.0" -byte-utils = "0.0.1" +byte-utils = "0.1.0" "base@0.7.3" = "0.7.3" [dev-dependencies] diff --git a/src/Candid/Blob/Decoder.mo b/src/Candid/Blob/Decoder.mo index fe07f79..0ceed76 100644 --- a/src/Candid/Blob/Decoder.mo +++ b/src/Candid/Blob/Decoder.mo @@ -845,10 +845,10 @@ module { case (null) {}; }; - if (options.use_icrc_3_value_type) { - #Map(record_entries); - } else if (is_tuple and record_entries.size() > 0) { + if (is_tuple and record_entries.size() > 0) { #Tuple(Array.map<(Text, Candid), Candid>(record_entries, func((_, v) : (Any, Candid)) : Candid = v)); + } else if (options.use_icrc_3_value_type) { + #Map(record_entries); } else { #Record(record_entries); }; diff --git a/src/Candid/Blob/Encoder.mo b/src/Candid/Blob/Encoder.mo index 54f2749..073e49c 100644 --- a/src/Candid/Blob/Encoder.mo +++ b/src/Candid/Blob/Encoder.mo @@ -42,1932 +42,1928 @@ import Utils "../../Utils"; import CandidUtils "CandidUtils"; module { - type Arg = Arg.Arg; - type Type = Type.Type; - type Tag = Tag.Tag; - type Value = Value.Value; - type RecordFieldType = Type.RecordFieldType; - type RecordFieldValue = Value.RecordFieldValue; - type TrieMap = TrieMap.TrieMap; - type Result = Result.Result; - type Buffer = Buffer.Buffer; - type Iter = Iter.Iter; - type Hash = Nat32; - type Map = Map.Map; - type Order = Order.Order; - - type Candid = T.Candid; - type CandidType = T.CandidType; - type KeyValuePair = T.KeyValuePair; - let { thash } = Map; - let { unsigned_leb128; signed_leb128_64 } = Utils; - - // public func encode(candid_values : [Candid], options : ?T.Options) : Result { - // let renaming_map = Map.new(); - // for ((k, v) in Option.get(options, T.defaultOptions).renameKeys.vals()) { - // ignore Map.put(renaming_map, thash, k, v); - // }; - // let (candid_types) = switch (infer_candid_types(candid_values, renaming_map)) { - // case (#ok(inferred_types)) Array.map( - // inferred_types, - // func(candid_type : CandidType) : CandidType = CandidUtils.format_candid_type(candid_type, renaming_map), - // ); - // case (#err(e)) return #err(e); - // }; - // let encoder = Encoder.new(candid_types, options); - // Encoder.encode(encoder, candid_values, options); - // }; - public func encode(candid_values : [Candid], options : ?T.Options) : Result { - one_shot(candid_values, options); - }; - - public func encodeOne(candid : Candid, options : ?T.Options) : Result { - encode([candid], options); - }; - - func infer_candid_types(candid_values : [Candid], renaming_map : Map) : Result<[CandidType], Text> { - let buffer = Buffer.Buffer(candid_values.size()); - - for (candid in candid_values.vals()) { - let candid_type = to_candid_types(candid, renaming_map); - - let rows = Buffer.Buffer<[InternalCandidTypeNode]>(8); - - let node : InternalCandidTypeNode = { - type_ = candid_type; - height = 0; - parent_index = 0; - key = null; - }; - - rows.add([node]); - - order_candid_types_by_height_bfs(rows); - - let res = merge_candid_variants_and_array_types(rows); - let #ok(merged_type) = res else return Utils.send_error(res); - - buffer.add(merged_type); + type Arg = Arg.Arg; + type Type = Type.Type; + type Tag = Tag.Tag; + type Value = Value.Value; + type RecordFieldType = Type.RecordFieldType; + type RecordFieldValue = Value.RecordFieldValue; + type TrieMap = TrieMap.TrieMap; + type Result = Result.Result; + type Buffer = Buffer.Buffer; + type Iter = Iter.Iter; + type Hash = Nat32; + type Map = Map.Map; + type Order = Order.Order; + + type Candid = T.Candid; + type CandidType = T.CandidType; + type KeyValuePair = T.KeyValuePair; + let { thash } = Map; + let { unsigned_leb128; signed_leb128_64 } = Utils; + + // public func encode(candid_values : [Candid], options : ?T.Options) : Result { + // let renaming_map = Map.new(); + // for ((k, v) in Option.get(options, T.defaultOptions).renameKeys.vals()) { + // ignore Map.put(renaming_map, thash, k, v); + // }; + // let (candid_types) = switch (infer_candid_types(candid_values, renaming_map)) { + // case (#ok(inferred_types)) Array.map( + // inferred_types, + // func(candid_type : CandidType) : CandidType = CandidUtils.format_candid_type(candid_type, renaming_map), + // ); + // case (#err(e)) return #err(e); + // }; + // let encoder = Encoder.new(candid_types, options); + // Encoder.encode(encoder, candid_values, options); + // }; + public func encode(candid_values : [Candid], options : ?T.Options) : Result { + one_shot(candid_values, options); }; - #ok(Buffer.toArray(buffer)); - }; - - let C = { - COUNTER = { - COMPOUND_TYPE = 0; - PRIMITIVE_TYPE = 1; - VALUE = 2; + public func encodeOne(candid : Candid, options : ?T.Options) : Result { + encode([candid], options); }; - }; - - public func one_shot(candid_values : [Candid], _options : ?T.Options) : Result { - let renaming_map = Map.new(); + func infer_candid_types(candid_values : [Candid], renaming_map : Map) : Result<[CandidType], Text> { + let buffer = Buffer.Buffer(candid_values.size()); - let compound_type_buffer = Buffer.Buffer(200); - let candid_type_buffer = Buffer.Buffer(200); - let value_buffer = Buffer.Buffer(400); + for (candid in candid_values.vals()) { + let candid_type = to_candid_types(candid, renaming_map); - let counter = [var 0]; - - let options = Option.get(_options, T.defaultOptions); - - for ((k, v) in options.renameKeys.vals()) { - ignore Map.put(renaming_map, thash, k, v); - }; - - var candid_types : [CandidType] = switch (options.types) { - case (?types) { types }; - case (_) switch (infer_candid_types(candid_values, renaming_map)) { - case (#ok(inferred_types)) Array.map( - inferred_types, - func(candid_type : CandidType) : CandidType = CandidUtils.format_candid_type(candid_type, renaming_map), - ); - case (#err(e)) return #err(e); - }; - }; - - one_shot_encode( - candid_types, - candid_values, - compound_type_buffer, - candid_type_buffer, - value_buffer, - counter, - renaming_map, - ); - - let candid_magic_bytes_buffer = Buffer.fromArray([0x44, 0x49, 0x44, 0x4C]); // 'DIDL' magic bytes - - // add compound type to the buffer - let compound_type_size_bytes = Buffer.Buffer(8); - // ByteUtils.Buffer.addLEB128_64(compound_type_size_bytes, Nat64.fromNat(counter[C.COUNTER.COMPOUND_TYPE])); - unsigned_leb128(compound_type_size_bytes, counter[C.COUNTER.COMPOUND_TYPE]); - - // add primitive type to the buffer - let candid_type_size_bytes = Buffer.Buffer(8); - // ByteUtils.Buffer.addLEB128_64(candid_type_size_bytes, Nat64.fromNat(candid_values.size())); - unsigned_leb128(candid_type_size_bytes, candid_values.size()); - - let total_size = candid_magic_bytes_buffer.size() + compound_type_size_bytes.size() + compound_type_buffer.size() + candid_type_size_bytes.size() + candid_type_buffer.size() + value_buffer.size(); - - let sequence = [ - candid_magic_bytes_buffer, - compound_type_size_bytes, - compound_type_buffer, - candid_type_size_bytes, - candid_type_buffer, - value_buffer, - ]; - - // Print each buffer in the sequence as a Blob - // for (buf in sequence.vals()) { - // Debug.print(debug_show Blob.fromArray(Buffer.toArray(buf))); - // }; + let rows = Buffer.Buffer<[InternalCandidTypeNode]>(8); - var i = 0; - var j = 0; - - #ok( - Blob.fromArray( - Array.tabulate( - total_size, - func(_ : Nat) : Nat8 { - var buffer = sequence[i]; - while (j >= buffer.size()) { - j := 0; - i += 1; - buffer := sequence[i]; + let node : InternalCandidTypeNode = { + type_ = candid_type; + height = 0; + parent_index = 0; + key = null; }; - let byte = buffer.get(j); - j += 1; - byte; - }, - ) - ) - ); - }; - - func check_is_tuple(candid_types : [(Text, Any)]) : Bool { - let n = candid_types.size(); // 0-based index - var sum_of_n : Int = 0; - - var i = 0; - label tuple_check while (i < candid_types.size()) { - let record_key = candid_types[i].0; + rows.add([node]); - if (Utils.text_is_number(record_key)) { - sum_of_n += (Utils.text_to_nat(record_key) + 1); - } else break tuple_check; + order_candid_types_by_height_bfs(rows); - i += 1; - }; + let res = merge_candid_variants_and_array_types(rows); + let #ok(merged_type) = res else return Utils.send_error(res); - sum_of_n == (n * (n + 1)) / 2; - }; - - func tuple_type_to_record(tuple_types : [CandidType]) : [(Text, CandidType)] { - Array.tabulate<(Text, CandidType)>( - tuple_types.size(), - func(i : Nat) : (Text, CandidType) { - (debug_show (i), tuple_types[i]); - }, - ); - }; - - func tuple_value_to_record(tuple_values : [Candid]) : [(Text, Candid)] { - Array.tabulate<(Text, Candid)>( - tuple_values.size(), - func(i : Nat) : (Text, Candid) { - (debug_show i, tuple_values[i]); - }, - ); - }; - - public func one_shot_encode( - candid_types : [CandidType], - candid_values : [Candid], - compound_type_buffer : Buffer, - candid_type_buffer : Buffer, - value_buffer : Buffer, - counter : [var Nat], - renaming_map : Map, - ) { - assert candid_values.size() == candid_types.size(); - - // include size of candid values - // unsigned_leb128(type_buffer, candid_values.size()); - - let unique_compound_type_map = Map.new(); - let recursive_map = Map.new(); - - var i = 0; - - while (i < candid_values.size()) { - - ignore encode_candid( - candid_types[i], - candid_values[i], - compound_type_buffer, - candid_type_buffer, - value_buffer, - renaming_map, - unique_compound_type_map, - recursive_map, - counter, - false, - false, - ); - - i += 1; - }; + buffer.add(merged_type); + }; - }; - - /// Encodes only the value part, without encoding any type information. - public func encode_value_only( - candid_type : CandidType, - candid_value : Candid, - value_buffer : Buffer, - renaming_map : Map, - unique_compound_type_map : Map, - recursive_map : Map, - counter : [var Nat], - is_nested_child_of_compound_type : Bool, - ) : ?Hash { - let candid_is_compound_type = switch candid_type { - case (#Option(_) or #Array(_) or #Record(_) or #Map(_) or #Tuple(_) or #Variant(_) or #Recursive(_) or #Blob(_)) true; - case (_) false; + #ok(Buffer.toArray(buffer)); }; - if (candid_is_compound_type) { - switch (candid_type, candid_value) { - case (#Option(opt_type), #Option(opt_value)) { - if (opt_value == #Null and opt_type != #Null) { - value_buffer.add(0); // no value - } else { - value_buffer.add(1); // has value - ignore encode_value_only( - opt_type, - opt_value, - value_buffer, - renaming_map, - unique_compound_type_map, - recursive_map, - counter, - true, - ); - }; - }; - case (#Option(opt_type), #Null) { - value_buffer.add(0); // no value - }; - case (#Array(arr_type), #Array(arr_values)) { - unsigned_leb128(value_buffer, arr_values.size()); - var i = 0; - while (i < arr_values.size()) { - ignore encode_value_only( - arr_type, - arr_values[i], - value_buffer, - renaming_map, - unique_compound_type_map, - recursive_map, - counter, - true, - ); - i += 1; - }; + let C = { + COUNTER = { + COMPOUND_TYPE = 0; + PRIMITIVE_TYPE = 1; + VALUE = 2; }; - case (#Blob, #Blob(blob)) { - let bytes = Array.map(Blob.toArray(blob), func(n : Nat8) : Candid = #Nat8(n)); - ignore encode_value_only( - #Array(#Nat8), - #Array(bytes), - value_buffer, - renaming_map, - unique_compound_type_map, - recursive_map, - counter, - is_nested_child_of_compound_type, - ); - }; - case (#Array(#Nat8), #Blob(blob)) { - let bytes = Array.map(Blob.toArray(blob), func(n : Nat8) : Candid = #Nat8(n)); - ignore encode_value_only( - #Array(#Nat8), - #Array(bytes), - value_buffer, - renaming_map, - unique_compound_type_map, - recursive_map, - counter, - is_nested_child_of_compound_type, - ); - }; - case (#Blob, #Array(bytes)) { - if (bytes.size() > 0) { - switch (bytes[0]) { - case (#Nat8(_)) {}; - case (_) return Debug.trap("invalid blob value: expected array of Nat8, got array of " # debug_show bytes[0]); - }; - }; - ignore encode_value_only( - #Array(#Nat8), - #Array(bytes), - value_buffer, - renaming_map, - unique_compound_type_map, - recursive_map, - counter, - is_nested_child_of_compound_type, - ); - }; - case (#Record(record_types) or #Map(record_types), #Record(record_entries) or #Map(record_entries)) { - let is_tuple = switch candid_type { - case (#Record(rts) or #Map(rts)) check_is_tuple(rts); - case _ false; - }; - - let record_entry_cache : Map.Map = Utils.create_map(record_entries.size()); - - for ((k, v) in record_entries.vals()) { - let field_value_key = get_renamed_key(renaming_map, k); - ignore Map.put(record_entry_cache, thash, field_value_key, v); - }; - - var i = 0; - while (i < record_types.size()) { - let field_type = record_types[i].1; - let field_type_key = get_renamed_key(renaming_map, record_types[i].0); - let field_value = switch (Map.get(record_entry_cache, Map.thash, field_type_key)) { - case (?field_value) field_value; - case (_) Debug.trap("unable to find field key in field types: " # debug_show field_type_key # "in " # debug_show record_entries); - }; - ignore encode_value_only( - field_type, - field_value, - value_buffer, - renaming_map, - unique_compound_type_map, - recursive_map, - counter, - true, - ); - i += 1; - }; - }; - - case (#Tuple(tuple_types), #Tuple(tuple_values)) { - ignore encode_value_only( - #Record(tuple_type_to_record(tuple_types)), - #Record(tuple_value_to_record(tuple_values)), - value_buffer, - renaming_map, - unique_compound_type_map, - recursive_map, - counter, - is_nested_child_of_compound_type, - ); - }; - case (#Tuple(tuple_types), #Record(tuple_values)) { - var i = 0; - assert Itertools.all( - tuple_values.vals(), - func((k, v) : (Text, Any)) : Bool { - i += 1; - Utils.text_is_number(k) and Utils.text_to_nat(k) == (i - 1 : Nat); - }, - ); - ignore encode_value_only( - #Record(tuple_type_to_record(tuple_types)), - #Record(tuple_values), - value_buffer, - renaming_map, - unique_compound_type_map, - recursive_map, - counter, - is_nested_child_of_compound_type, - ); - }; - case (#Record(record_types), #Tuple(tuple_values)) { - var i = 0; - assert Itertools.all( - record_types.vals(), - func((k, v) : (Text, Any)) : Bool { - i += 1; - Utils.text_is_number(k) and Utils.text_to_nat(k) == (i - 1 : Nat); - }, - ); - ignore encode_value_only( - #Record(record_types), - #Record(tuple_value_to_record(tuple_values)), - value_buffer, - renaming_map, - unique_compound_type_map, - recursive_map, - counter, - is_nested_child_of_compound_type, - ); - }; - case (#Variant(variant_types), #Variant(variant)) { - let variant_key = get_renamed_key(renaming_map, variant.0); - let variant_value = variant.1; - let variant_index_res = Array.indexOf<(Text, CandidType)>( - (variant_key, #Empty), - variant_types, - func((a, _) : (Text, CandidType), (b, _) : (Text, CandidType)) : Bool = a == b, - ); - let variant_index = switch (variant_index_res) { - case (?index) index; - case (_) Debug.trap("unable to find variant key in variant types"); - }; - unsigned_leb128(value_buffer, variant_index); - ignore encode_value_only( - variant_types[variant_index].1, - variant_value, - value_buffer, - renaming_map, - unique_compound_type_map, - recursive_map, - counter, - true, - ); - }; - case (_) Debug.trap("invalid (type, value) pair for encode_value_only: " # debug_show { candid_type; candid_value }); - }; - - } else { - // primitive types - switch (candid_type, candid_value) { - case (#Nat, #Nat(n)) { - unsigned_leb128(value_buffer, n); - }; - case (#Nat8, #Nat8(n)) { - value_buffer.add(n); - }; - case (#Nat16, #Nat16(n)) { - ByteUtils.Buffer.LE.addNat16(value_buffer, n); - }; - case (#Nat32, #Nat32(n)) { - ByteUtils.Buffer.LE.addNat32(value_buffer, n); - }; - case (#Nat64, #Nat64(n)) { - ByteUtils.Buffer.LE.addNat64(value_buffer, n); - }; - case (#Int, #Int(n)) { - signed_leb128_64(value_buffer, n); - }; - case (#Int8, #Int8(i8)) { - value_buffer.add(Int8.toNat8(i8)); - }; - case (#Int16, #Int16(i16)) { - ByteUtils.Buffer.LE.addInt16(value_buffer, i16); - }; - case (#Int32, #Int32(i32)) { - ByteUtils.Buffer.LE.addInt32(value_buffer, i32); - }; - case (#Int64, #Int64(i64)) { - ByteUtils.Buffer.LE.addInt64(value_buffer, i64); - }; - case (#Float, #Float(f64)) { - ByteUtils.Buffer.LE.addFloat(value_buffer, f64); - }; - case (#Bool, #Bool(b)) { - value_buffer.add(if (b) (1) else (0)); - }; - case (#Null, #Null) {}; - case (#Empty, #Empty) {}; - case (#Text, #Text(t)) { - let utf8_blob = Text.encodeUtf8(t); - unsigned_leb128(value_buffer, utf8_blob.size()); - var i = 0; - while (i < utf8_blob.size()) { - value_buffer.add(utf8_blob[i]); - i += 1; - }; - }; - case (#Principal, #Principal(p)) { - value_buffer.add(0x01); // indicate transparency state - let blob = Principal.toBlob(p); - unsigned_leb128(value_buffer, blob.size()); - var i = 0; - while (i < blob.size()) { - value_buffer.add(blob[i]); - i += 1; - }; - }; - case (_) Debug.trap("unknown (type, value) pair for encode_value_only: " # debug_show (candid_type, candid_value)); - }; }; - null; - }; - func is_compound_type(candid_type : CandidType) : Bool { - switch (candid_type) { - case (#Option(_) or #Array(_) or #Record(_) or #Map(_) or #Tuple(_) or #Variant(_) or #Recursive(_) or #Blob(_)) true; - case (_) false; - }; - }; - - func encode_primitive_type_only( - candid_type : CandidType, - compound_type_buffer : Buffer, - candid_type_buffer : Buffer, - is_nested_child_of_compound_type : Bool, - ) { - let ref_candid_type_buffer = if (is_nested_child_of_compound_type) { - compound_type_buffer; - } else { - candid_type_buffer; - }; + public func one_shot(candid_values : [Candid], _options : ?T.Options) : Result { - switch (candid_type) { - case (#Nat) ref_candid_type_buffer.add(T.TypeCode.Nat); - case (#Nat8) ref_candid_type_buffer.add(T.TypeCode.Nat8); - case (#Nat16) ref_candid_type_buffer.add(T.TypeCode.Nat16); - case (#Nat32) ref_candid_type_buffer.add(T.TypeCode.Nat32); - case (#Nat64) ref_candid_type_buffer.add(T.TypeCode.Nat64); - - case (#Int) ref_candid_type_buffer.add(T.TypeCode.Int); - case (#Int8) ref_candid_type_buffer.add(T.TypeCode.Int8); - case (#Int16) ref_candid_type_buffer.add(T.TypeCode.Int16); - case (#Int32) ref_candid_type_buffer.add(T.TypeCode.Int32); - case (#Int64) ref_candid_type_buffer.add(T.TypeCode.Int64); - - case (#Float) ref_candid_type_buffer.add(T.TypeCode.Float); - case (#Bool) ref_candid_type_buffer.add(T.TypeCode.Bool); - case (#Text) ref_candid_type_buffer.add(T.TypeCode.Text); - case (#Principal) ref_candid_type_buffer.add(T.TypeCode.Principal); - case (#Null) ref_candid_type_buffer.add(T.TypeCode.Null); - case (#Empty) ref_candid_type_buffer.add(T.TypeCode.Empty); - - case (_) Debug.trap("encode_primitive_type_only(): unknown primitive type " # debug_show candid_type); - }; - }; - - func encode_compound_type_only( - candid_type : CandidType, - compound_type_buffer : Buffer, - candid_type_buffer : Buffer, - renaming_map : Map, - unique_compound_type_map : Map, - counter : [var Nat], - is_nested_child_of_compound_type : Bool, - ) { - let type_info = get_type_info(candid_type); - let compound_type_exists = Map.has(unique_compound_type_map, thash, type_info); - if (compound_type_exists) return; - // Debug.print("encode_compound_type_only(): " # debug_show type_info); - switch (candid_type) { - case (#Option(opt_type)) { - let opt_type_is_compound = is_compound_type(opt_type); - - if (not opt_type_is_compound) { - compound_type_buffer.add(T.TypeCode.Option); - }; + let renaming_map = Map.new(); - encode_type_only( - opt_type, - compound_type_buffer, - candid_type_buffer, - renaming_map, - unique_compound_type_map, - counter, - true, - ); + let compound_type_buffer = Buffer.Buffer(200); + let candid_type_buffer = Buffer.Buffer(200); + let value_buffer = Buffer.Buffer(400); - if (opt_type_is_compound) { - compound_type_buffer.add(T.TypeCode.Option); - let opt_type_info = get_type_info(opt_type); - let pos = switch (Map.get(unique_compound_type_map, thash, opt_type_info)) { - case (?pos) pos; - case (_) Debug.trap("unable to find compound type pos to store in primitive type sequence for " # debug_show (type_info)); - }; - unsigned_leb128(compound_type_buffer, pos); - }; + let counter = [var 0]; - }; + let options = Option.get(_options, T.defaultOptions); - case (#Array(arr_type)) { - let arr_type_is_compound = is_compound_type(arr_type); - if (not arr_type_is_compound) { - compound_type_buffer.add(T.TypeCode.Array); + for ((k, v) in options.renameKeys.vals()) { + ignore Map.put(renaming_map, thash, k, v); }; - encode_type_only( - arr_type, - compound_type_buffer, - candid_type_buffer, - renaming_map, - unique_compound_type_map, - counter, - true, - ); - - if (arr_type_is_compound) { - compound_type_buffer.add(T.TypeCode.Array); - let arr_type_info = get_type_info(arr_type); - let pos = switch (Map.get(unique_compound_type_map, thash, arr_type_info)) { - case (?pos) pos; - case (_) Debug.trap("unable to find compound type pos to store in primitive type sequence for " # debug_show (type_info)); - }; - unsigned_leb128(compound_type_buffer, pos); + var candid_types : [CandidType] = switch (options.types) { + case (?types) { types }; + case (_) switch (infer_candid_types(candid_values, renaming_map)) { + case (#ok(inferred_types)) Array.map( + inferred_types, + func(candid_type : CandidType) : CandidType = CandidUtils.format_candid_type(candid_type, renaming_map), + ); + case (#err(e)) return #err(e); + }; }; - }; - - case (#Blob) return encode_compound_type_only( - #Array(#Nat8), - compound_type_buffer, - candid_type_buffer, - renaming_map, - unique_compound_type_map, - counter, - is_nested_child_of_compound_type, - ); - - case (#Record(record_types) or #Map(record_types)) { - let is_tuple = check_is_tuple(record_types); - var i = 0; - while (i < record_types.size()) { - let field_type = record_types[i].1; - - let value_type_is_compound = is_compound_type(field_type); - - if (value_type_is_compound) encode_type_only( - field_type, + one_shot_encode( + candid_types, + candid_values, compound_type_buffer, candid_type_buffer, - renaming_map, - unique_compound_type_map, + value_buffer, counter, - true, - ); - - i += 1; - }; + renaming_map, + ); - compound_type_buffer.add(T.TypeCode.Record); - unsigned_leb128(compound_type_buffer, record_types.size()); + let candid_magic_bytes_buffer = Buffer.fromArray([0x44, 0x49, 0x44, 0x4C]); // 'DIDL' magic bytes - i := 0; - while (i < record_types.size()) { - let field_type = record_types[i].1; + // add compound type to the buffer + let compound_type_size_bytes = Buffer.Buffer(8); + // ByteUtils.Buffer.addLEB128_64(compound_type_size_bytes, Nat64.fromNat(counter[C.COUNTER.COMPOUND_TYPE])); + unsigned_leb128(compound_type_size_bytes, counter[C.COUNTER.COMPOUND_TYPE]); - let value_type_is_compound = is_compound_type(field_type); + // add primitive type to the buffer + let candid_type_size_bytes = Buffer.Buffer(8); + // ByteUtils.Buffer.addLEB128_64(candid_type_size_bytes, Nat64.fromNat(candid_values.size())); + unsigned_leb128(candid_type_size_bytes, candid_values.size()); - if (is_tuple) { - unsigned_leb128(compound_type_buffer, i); - } else { - let record_key = get_renamed_key(renaming_map, record_types[i].0); + let total_size = candid_magic_bytes_buffer.size() + compound_type_size_bytes.size() + compound_type_buffer.size() + candid_type_size_bytes.size() + candid_type_buffer.size() + value_buffer.size(); - let hash_key = hash_record_key(record_key); - unsigned_leb128(compound_type_buffer, Nat32.toNat(hash_key)); - }; + let sequence = [ + candid_magic_bytes_buffer, + compound_type_size_bytes, + compound_type_buffer, + candid_type_size_bytes, + candid_type_buffer, + value_buffer, + ]; - if (value_type_is_compound) { - let value_type_info = get_type_info(field_type); - let pos = switch (Map.get(unique_compound_type_map, thash, value_type_info)) { - case (?pos) pos; - case (_) Debug.trap("unable to find compound type pos to store in primitive type sequence for " # debug_show (type_info)); - }; - unsigned_leb128(compound_type_buffer, pos); - } else { - encode_primitive_type_only( - field_type, - compound_type_buffer, - candid_type_buffer, - true, - ); - }; + // Print each buffer in the sequence as a Blob + // for (buf in sequence.vals()) { + // Debug.print(debug_show Blob.fromArray(Buffer.toArray(buf))); + // }; - i += 1; - }; - }; - - case (#Tuple(tuple_types)) { - return encode_compound_type_only( - #Record(tuple_type_to_record(tuple_types)), - compound_type_buffer, - candid_type_buffer, - renaming_map, - unique_compound_type_map, - counter, - true, + var i = 0; + var j = 0; + + #ok( + Blob.fromArray( + Array.tabulate( + total_size, + func(_ : Nat) : Nat8 { + var buffer = sequence[i]; + while (j >= buffer.size()) { + j := 0; + i += 1; + buffer := sequence[i]; + }; + + let byte = buffer.get(j); + j += 1; + byte; + }, + ) + ) ); - }; + }; - case (#Variant(variant_types)) { + func check_is_tuple(candid_types : [(Text, Any)]) : Bool { + let n = candid_types.size(); // 0-based index + var sum_of_n : Int = 0; var i = 0; - while (i < variant_types.size()) { - let variant_type = variant_types[i].1; - - let variant_type_is_compound = is_compound_type(variant_type); - - if (variant_type_is_compound) { - encode_compound_type_only( - variant_type, - compound_type_buffer, - candid_type_buffer, - renaming_map, - unique_compound_type_map, - counter, - true, - ); - }; - - i += 1; - }; + label tuple_check while (i < candid_types.size()) { + let record_key = candid_types[i].0; - compound_type_buffer.add(T.TypeCode.Variant); - unsigned_leb128(compound_type_buffer, variant_types.size()); + if (Utils.text_is_number(record_key)) { + sum_of_n += (Utils.text_to_nat(record_key) + 1); + } else break tuple_check; - i := 0; - while (i < variant_types.size()) { - let variant_key = get_renamed_key(renaming_map, variant_types[i].0); - let variant_type = variant_types[i].1; - let variant_type_is_compound = is_compound_type(variant_type); - - let hash_key = hash_record_key(variant_key); - unsigned_leb128(compound_type_buffer, Nat32.toNat(hash_key)); - - if (variant_type_is_compound) { - let variant_type_info = get_type_info(variant_type); - let pos = switch (Map.get(unique_compound_type_map, thash, variant_type_info)) { - case (?pos) pos; - case (_) Debug.trap("unable to find compound type pos to store in primitive type sequence for " # debug_show (type_info)); - }; - unsigned_leb128(compound_type_buffer, pos); - } else { - encode_primitive_type_only( - variant_type, - compound_type_buffer, - candid_type_buffer, - true, - ); - }; - - i += 1; + i += 1; }; - }; - case (_) Debug.trap("encode_compound_type_only(): unknown compound type " # debug_show candid_type); + sum_of_n == (n * (n + 1)) / 2; }; - ignore Map.put(unique_compound_type_map, thash, type_info, counter[C.COUNTER.COMPOUND_TYPE]); - counter[C.COUNTER.COMPOUND_TYPE] += 1; - - }; - - public func encode_type_only( - candid_type : CandidType, - compound_type_buffer : Buffer, - candid_type_buffer : Buffer, - renaming_map : Map, - unique_compound_type_map : Map, - counter : [var Nat], - is_nested_child_of_compound_type : Bool, - ) { - if (is_compound_type(candid_type)) { - encode_compound_type_only( - candid_type, - compound_type_buffer, - candid_type_buffer, - renaming_map, - unique_compound_type_map, - counter, - is_nested_child_of_compound_type, - ); - - // Add compound type reference to primitive type buffer for top-level types - if (not is_nested_child_of_compound_type) { - let type_info = get_type_info(candid_type); - let pos = switch (Map.get(unique_compound_type_map, thash, type_info)) { - case (?pos) pos; - case (_) Debug.trap("unable to find compound type pos to store in primitive type sequence for " # debug_show (type_info)); - }; - unsigned_leb128(candid_type_buffer, pos); - }; - } else { - encode_primitive_type_only( - candid_type, - compound_type_buffer, - candid_type_buffer, - is_nested_child_of_compound_type, - ); - }; - }; - func get_type_info(_candid_type : CandidType) : Text { - let candid_type = switch (_candid_type) { - case (#Map(records)) #Record(records); - case (#Blob) #Array(#Nat8); - case (#Tuple(tuple_types)) #Record(tuple_type_to_record(tuple_types)); - case (candid_type) candid_type; + func tuple_type_to_record(tuple_types : [CandidType]) : [(Text, CandidType)] { + Array.tabulate<(Text, CandidType)>( + tuple_types.size(), + func(i : Nat) : (Text, CandidType) { + (debug_show (i), tuple_types[i]); + }, + ); }; - debug_show candid_type; - }; - - func encode_primitive_type( - candid_type : CandidType, - candid_value : Candid, - compound_type_buffer : Buffer, - candid_type_buffer : Buffer, - value_buffer : Buffer, - renaming_map : Map, - unique_compound_type_map : Map, - recursive_map : Map, - is_nested_child_of_compound_type : Bool, - ignore_type : Bool, - ) { - let ref_candid_type_buffer = if (ignore_type) { - object { - public func add(_ : Nat8) {}; // do nothing - }; - } else if (is_nested_child_of_compound_type) { - compound_type_buffer; - } else { - candid_type_buffer; + func tuple_value_to_record(tuple_values : [Candid]) : [(Text, Candid)] { + Array.tabulate<(Text, Candid)>( + tuple_values.size(), + func(i : Nat) : (Text, Candid) { + (debug_show i, tuple_values[i]); + }, + ); }; - switch (candid_type, candid_value) { - case (#Nat, #Nat(n)) { - // Debug.print("start encoding Nat: " # debug_show n); - ref_candid_type_buffer.add(T.TypeCode.Nat); - // Debug.print("encoded type codde"); - unsigned_leb128(value_buffer, n); - - }; - case (#Nat8, #Nat8(n)) { - ref_candid_type_buffer.add(T.TypeCode.Nat8); - value_buffer.add(n); - }; - case (#Nat16, #Nat16(n)) { - ref_candid_type_buffer.add(T.TypeCode.Nat16); - ByteUtils.Buffer.LE.addNat16(value_buffer, n); - }; - case (#Nat32, #Nat32(n)) { - ref_candid_type_buffer.add(T.TypeCode.Nat32); - ByteUtils.Buffer.LE.addNat32(value_buffer, n); - }; - case (#Nat64, #Nat64(n)) { - ref_candid_type_buffer.add(T.TypeCode.Nat64); - ByteUtils.Buffer.LE.addNat64(value_buffer, n); - }; - case (#Int, #Int(n)) { - ref_candid_type_buffer.add(T.TypeCode.Int); - signed_leb128_64(value_buffer, n); - }; - case (#Int8, #Int8(i8)) { - ref_candid_type_buffer.add(T.TypeCode.Int8); - value_buffer.add(Int8.toNat8(i8)); - }; - case (#Int16, #Int16(i16)) { - ref_candid_type_buffer.add(T.TypeCode.Int16); - ByteUtils.Buffer.LE.addInt16(value_buffer, i16); - }; - case (#Int32, #Int32(i32)) { - ref_candid_type_buffer.add(T.TypeCode.Int32); - ByteUtils.Buffer.LE.addInt32(value_buffer, i32); - }; - case (#Int64, #Int64(i64)) { - ref_candid_type_buffer.add(T.TypeCode.Int64); - ByteUtils.Buffer.LE.addInt64(value_buffer, i64); - }; - case (#Float, #Float(f64)) { - ref_candid_type_buffer.add(T.TypeCode.Float); - ByteUtils.Buffer.LE.addFloat(value_buffer, f64); - }; - case (#Bool, #Bool(b)) { - ref_candid_type_buffer.add(T.TypeCode.Bool); - value_buffer.add(if (b) (1) else (0)); - }; - case (#Null, #Null) { - ref_candid_type_buffer.add(T.TypeCode.Null); - }; - case (#Empty, #Empty) { - ref_candid_type_buffer.add(T.TypeCode.Empty); - }; - case (#Text, #Text(t)) { - ref_candid_type_buffer.add(T.TypeCode.Text); - - let utf8_blob = Text.encodeUtf8(t); - unsigned_leb128(value_buffer, utf8_blob.size()); + public func one_shot_encode( + candid_types : [CandidType], + candid_values : [Candid], + compound_type_buffer : Buffer, + candid_type_buffer : Buffer, + value_buffer : Buffer, + counter : [var Nat], + renaming_map : Map, + ) { + assert candid_values.size() == candid_types.size(); - var i = 0; - while (i < utf8_blob.size()) { - value_buffer.add(utf8_blob[i]); - i += 1; - }; + // include size of candid values + // unsigned_leb128(type_buffer, candid_values.size()); - }; - case (#Principal, #Principal(p)) { - ref_candid_type_buffer.add(T.TypeCode.Principal); - - value_buffer.add(0x01); // indicate transparency state - let blob = Principal.toBlob(p); - unsigned_leb128(value_buffer, blob.size()); + let unique_compound_type_map = Map.new(); + let recursive_map = Map.new(); var i = 0; - while (i < blob.size()) { - value_buffer.add(blob[i]); - i += 1; - }; - }; - case (_) Debug.trap("unknown (type, value) pair: " # debug_show (candid_type, candid_value)); - }; - }; - - func encode_compound_type( - candid_type : CandidType, - candid_value : Candid, - compound_type_buffer : Buffer, - candid_type_buffer : Buffer, - value_buffer : Buffer, - renaming_map : Map, - unique_compound_type_map : Map, - recursive_map : Map, - counter : [var Nat], - is_nested_child_of_compound_type : Bool, - _type_exists : Bool, - ) { - - // Debug.print("encode_compound_type(): " # debug_show (candid_type, candid_value)); - - // ----------------- Compound Types ----------------- // - - // encode_candid type only - // case (candid_type, #Null) { - // encode_nested_type(candid_type, compound_type_buffer); - // }; - - let type_info = get_type_info(candid_type); - - // type_exists_in_compound_type_sequence - let type_exists = _type_exists or Map.has(unique_compound_type_map, thash, type_info); - - switch (candid_type, candid_value) { - - case (#Option(opt_type), #Option(opt_value)) { + while (i < candid_values.size()) { - let opt_type_is_compound = is_compound_type(opt_type); + ignore encode_candid( + candid_types[i], + candid_values[i], + compound_type_buffer, + candid_type_buffer, + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + false, + false, + ); - if (not type_exists and not opt_type_is_compound) { - compound_type_buffer.add(T.TypeCode.Option); + i += 1; }; - if (opt_value == #Null and opt_type != #Null) { - // a result of being able to set #Null at any point in an #Option type - // for instance, type #Option(#Nat) with value #Null + }; - value_buffer.add(0); // no value + /// Encodes only the value part, without encoding any type information. + public func encode_value_only( + candid_type : CandidType, + candid_value : Candid, + value_buffer : Buffer, + renaming_map : Map, + unique_compound_type_map : Map, + recursive_map : Map, + counter : [var Nat], + is_nested_child_of_compound_type : Bool, + ) : ?Hash { + let candid_is_compound_type = switch candid_type { + case (#Option(_) or #Array(_) or #Record(_) or #Map(_) or #Tuple(_) or #Variant(_) or #Recursive(_) or #Blob(_)) true; + case (_) false; + }; - if (not type_exists) encode_type_only( - opt_type, - compound_type_buffer, - candid_type_buffer, - renaming_map, - unique_compound_type_map, - counter, - true, - ); + if (candid_is_compound_type) { + switch (candid_type, candid_value) { + case (#Option(opt_type), #Option(opt_value)) { + if (opt_value == #Null and opt_type != #Null) { + value_buffer.add(0); // no value + } else { + value_buffer.add(1); // has value + ignore encode_value_only( + opt_type, + opt_value, + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + true, + ); + }; + }; + case (#Option(opt_type), #Null) { + value_buffer.add(0); // no value + }; + case (#Array(arr_type), #Array(arr_values)) { + unsigned_leb128(value_buffer, arr_values.size()); + var i = 0; + while (i < arr_values.size()) { + ignore encode_value_only( + arr_type, + arr_values[i], + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + true, + ); + i += 1; + }; + }; + case (#Blob, #Blob(blob)) { + let bytes = Array.map(Blob.toArray(blob), func(n : Nat8) : Candid = #Nat8(n)); + ignore encode_value_only( + #Array(#Nat8), + #Array(bytes), + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + is_nested_child_of_compound_type, + ); + }; + case (#Array(#Nat8), #Blob(blob)) { + let bytes = Array.map(Blob.toArray(blob), func(n : Nat8) : Candid = #Nat8(n)); + ignore encode_value_only( + #Array(#Nat8), + #Array(bytes), + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + is_nested_child_of_compound_type, + ); + }; + case (#Blob, #Array(bytes)) { + if (bytes.size() > 0) { + switch (bytes[0]) { + case (#Nat8(_)) {}; + case (_) return Debug.trap("invalid blob value: expected array of Nat8, got array of " # debug_show bytes[0]); + }; + }; + ignore encode_value_only( + #Array(#Nat8), + #Array(bytes), + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + is_nested_child_of_compound_type, + ); + }; + case (#Record(record_types) or #Map(record_types), #Record(record_entries) or #Map(record_entries)) { + + let record_entry_cache : Map.Map = Utils.create_map(record_entries.size()); + + for ((k, v) in record_entries.vals()) { + let field_value_key = get_renamed_key(renaming_map, k); + ignore Map.put(record_entry_cache, thash, field_value_key, v); + }; + + var i = 0; + while (i < record_types.size()) { + let field_type = record_types[i].1; + let field_type_key = get_renamed_key(renaming_map, record_types[i].0); + let field_value = switch (Map.get(record_entry_cache, Map.thash, field_type_key)) { + case (?field_value) field_value; + case (_) Debug.trap("unable to find field key in field types: " # debug_show field_type_key # "in " # debug_show record_entries); + }; + ignore encode_value_only( + field_type, + field_value, + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + true, + ); + i += 1; + }; + }; + + case (#Tuple(tuple_types), #Tuple(tuple_values)) { + ignore encode_value_only( + #Record(tuple_type_to_record(tuple_types)), + #Record(tuple_value_to_record(tuple_values)), + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + is_nested_child_of_compound_type, + ); + }; + case (#Tuple(tuple_types), #Record(tuple_values)) { + var i = 0; + assert Itertools.all( + tuple_values.vals(), + func((k, v) : (Text, Any)) : Bool { + i += 1; + Utils.text_is_number(k) and Utils.text_to_nat(k) == (i - 1 : Nat); + }, + ); + ignore encode_value_only( + #Record(tuple_type_to_record(tuple_types)), + #Record(tuple_values), + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + is_nested_child_of_compound_type, + ); + }; + case (#Record(record_types), #Tuple(tuple_values)) { + var i = 0; + assert Itertools.all( + record_types.vals(), + func((k, v) : (Text, Any)) : Bool { + i += 1; + Utils.text_is_number(k) and Utils.text_to_nat(k) == (i - 1 : Nat); + }, + ); + ignore encode_value_only( + #Record(record_types), + #Record(tuple_value_to_record(tuple_values)), + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + is_nested_child_of_compound_type, + ); + }; + case (#Variant(variant_types), #Variant(variant)) { + let variant_key = get_renamed_key(renaming_map, variant.0); + let variant_value = variant.1; + let variant_index_res = Array.indexOf<(Text, CandidType)>( + (variant_key, #Empty), + variant_types, + func((a, _) : (Text, CandidType), (b, _) : (Text, CandidType)) : Bool = a == b, + ); + let variant_index = switch (variant_index_res) { + case (?index) index; + case (_) Debug.trap("unable to find variant key in variant types"); + }; + unsigned_leb128(value_buffer, variant_index); + ignore encode_value_only( + variant_types[variant_index].1, + variant_value, + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + true, + ); + }; + case (_) Debug.trap("invalid (type, value) pair for encode_value_only: " # debug_show { candid_type; candid_value }); + }; } else { - value_buffer.add(1); // has value - - ignore encode_candid( - opt_type, - opt_value, - compound_type_buffer, - candid_type_buffer, - value_buffer, - renaming_map, - unique_compound_type_map, - recursive_map, - counter, - true, - type_exists, - ); - }; - - if ( - not type_exists and opt_type_is_compound - ) { - // let prev_start = get_prev_compound_type_start_index(compound_type_buffer); - compound_type_buffer.add(T.TypeCode.Option); - let opt_type_info = get_type_info(opt_type); - let pos = switch (Map.get(unique_compound_type_map, thash, opt_type_info)) { - case (?pos) pos; - case (_) Debug.trap("unable to find compound type pos to store in primitive type sequence for " # debug_show (type_info)); - }; - unsigned_leb128(compound_type_buffer, pos); + // primitive types + switch (candid_type, candid_value) { + case (#Nat, #Nat(n)) { + unsigned_leb128(value_buffer, n); + }; + case (#Nat8, #Nat8(n)) { + value_buffer.add(n); + }; + case (#Nat16, #Nat16(n)) { + ByteUtils.Buffer.LE.addNat16(value_buffer, n); + }; + case (#Nat32, #Nat32(n)) { + ByteUtils.Buffer.LE.addNat32(value_buffer, n); + }; + case (#Nat64, #Nat64(n)) { + ByteUtils.Buffer.LE.addNat64(value_buffer, n); + }; + case (#Int, #Int(n)) { + signed_leb128_64(value_buffer, n); + }; + case (#Int8, #Int8(i8)) { + value_buffer.add(Int8.toNat8(i8)); + }; + case (#Int16, #Int16(i16)) { + ByteUtils.Buffer.LE.addInt16(value_buffer, i16); + }; + case (#Int32, #Int32(i32)) { + ByteUtils.Buffer.LE.addInt32(value_buffer, i32); + }; + case (#Int64, #Int64(i64)) { + ByteUtils.Buffer.LE.addInt64(value_buffer, i64); + }; + case (#Float, #Float(f64)) { + ByteUtils.Buffer.LE.addFloat(value_buffer, f64); + }; + case (#Bool, #Bool(b)) { + value_buffer.add(if (b) (1) else (0)); + }; + case (#Null, #Null) {}; + case (#Empty, #Empty) {}; + case (#Text, #Text(t)) { + let utf8_blob = Text.encodeUtf8(t); + unsigned_leb128(value_buffer, utf8_blob.size()); + var i = 0; + while (i < utf8_blob.size()) { + value_buffer.add(utf8_blob[i]); + i += 1; + }; + }; + case (#Principal, #Principal(p)) { + value_buffer.add(0x01); // indicate transparency state + let blob = Principal.toBlob(p); + unsigned_leb128(value_buffer, blob.size()); + var i = 0; + while (i < blob.size()) { + value_buffer.add(blob[i]); + i += 1; + }; + }; + case (_) Debug.trap("unknown (type, value) pair for encode_value_only: " # debug_show (candid_type, candid_value)); + }; }; - }; - - // a result of being able to set #Null at any point in an #Option type - // for instance, type #Option(#Nat) with value #Null - case (#Option(opt_type), #Null) { - value_buffer.add(0); // no value - - let opt_type_is_compound = is_compound_type(opt_type); + null; + }; - if (not type_exists and not opt_type_is_compound) { - compound_type_buffer.add(T.TypeCode.Option); + func is_compound_type(candid_type : CandidType) : Bool { + switch (candid_type) { + case (#Option(_) or #Array(_) or #Record(_) or #Map(_) or #Tuple(_) or #Variant(_) or #Recursive(_) or #Blob(_)) true; + case (_) false; }; + }; - if (not type_exists) encode_type_only( - opt_type, - compound_type_buffer, - candid_type_buffer, - renaming_map, - unique_compound_type_map, - counter, - true, - ); - - if ( - not type_exists and opt_type_is_compound - ) { - compound_type_buffer.add(T.TypeCode.Option); - let opt_type_info = get_type_info(opt_type); - let pos = switch (Map.get(unique_compound_type_map, thash, opt_type_info)) { - case (?pos) pos; - case (_) Debug.trap("unable to find compound type pos to store in primitive type sequence for " # debug_show (type_info, opt_type)); - }; - unsigned_leb128(compound_type_buffer, pos); + func encode_primitive_type_only( + candid_type : CandidType, + compound_type_buffer : Buffer, + candid_type_buffer : Buffer, + is_nested_child_of_compound_type : Bool, + ) { + let ref_candid_type_buffer = if (is_nested_child_of_compound_type) { + compound_type_buffer; + } else { + candid_type_buffer; }; - }; - - case (#Array(arr_type), #Array(arr_values)) { - let arr_type_is_compound = is_compound_type(arr_type); - - if (not type_exists and not arr_type_is_compound) { - compound_type_buffer.add(T.TypeCode.Array); + switch (candid_type) { + case (#Nat) ref_candid_type_buffer.add(T.TypeCode.Nat); + case (#Nat8) ref_candid_type_buffer.add(T.TypeCode.Nat8); + case (#Nat16) ref_candid_type_buffer.add(T.TypeCode.Nat16); + case (#Nat32) ref_candid_type_buffer.add(T.TypeCode.Nat32); + case (#Nat64) ref_candid_type_buffer.add(T.TypeCode.Nat64); + + case (#Int) ref_candid_type_buffer.add(T.TypeCode.Int); + case (#Int8) ref_candid_type_buffer.add(T.TypeCode.Int8); + case (#Int16) ref_candid_type_buffer.add(T.TypeCode.Int16); + case (#Int32) ref_candid_type_buffer.add(T.TypeCode.Int32); + case (#Int64) ref_candid_type_buffer.add(T.TypeCode.Int64); + + case (#Float) ref_candid_type_buffer.add(T.TypeCode.Float); + case (#Bool) ref_candid_type_buffer.add(T.TypeCode.Bool); + case (#Text) ref_candid_type_buffer.add(T.TypeCode.Text); + case (#Principal) ref_candid_type_buffer.add(T.TypeCode.Principal); + case (#Null) ref_candid_type_buffer.add(T.TypeCode.Null); + case (#Empty) ref_candid_type_buffer.add(T.TypeCode.Empty); + + case (_) Debug.trap("encode_primitive_type_only(): unknown primitive type " # debug_show candid_type); }; + }; - // if (not type_exists) - - unsigned_leb128(value_buffer, arr_values.size()); + func encode_compound_type_only( + candid_type : CandidType, + compound_type_buffer : Buffer, + candid_type_buffer : Buffer, + renaming_map : Map, + unique_compound_type_map : Map, + counter : [var Nat], + is_nested_child_of_compound_type : Bool, + ) { + let type_info = get_type_info(candid_type); + let compound_type_exists = Map.has(unique_compound_type_map, thash, type_info); + if (compound_type_exists) return; + // Debug.print("encode_compound_type_only(): " # debug_show type_info); + switch (candid_type) { + case (#Option(opt_type)) { + let opt_type_is_compound = is_compound_type(opt_type); + + if (not opt_type_is_compound) { + compound_type_buffer.add(T.TypeCode.Option); + }; + + encode_type_only( + opt_type, + compound_type_buffer, + candid_type_buffer, + renaming_map, + unique_compound_type_map, + counter, + true, + ); + + if (opt_type_is_compound) { + compound_type_buffer.add(T.TypeCode.Option); + let opt_type_info = get_type_info(opt_type); + let pos = switch (Map.get(unique_compound_type_map, thash, opt_type_info)) { + case (?pos) pos; + case (_) Debug.trap("unable to find compound type pos to store in primitive type sequence for " # debug_show (type_info)); + }; + unsigned_leb128(compound_type_buffer, pos); + }; - var i = 0; + }; - if (arr_values.size() == 0 and not type_exists) { - encode_type_only( - arr_type, - compound_type_buffer, - candid_type_buffer, - renaming_map, - unique_compound_type_map, - counter, - true, - ); - } else while (i < arr_values.size()) { - let val = arr_values[i]; - - ignore encode_candid( - arr_type, - val, - compound_type_buffer, - candid_type_buffer, - value_buffer, - renaming_map, - unique_compound_type_map, - recursive_map, - counter, - true, - type_exists or i > 0, - ); + case (#Array(arr_type)) { + let arr_type_is_compound = is_compound_type(arr_type); + if (not arr_type_is_compound) { + compound_type_buffer.add(T.TypeCode.Array); + }; + + encode_type_only( + arr_type, + compound_type_buffer, + candid_type_buffer, + renaming_map, + unique_compound_type_map, + counter, + true, + ); + + if (arr_type_is_compound) { + compound_type_buffer.add(T.TypeCode.Array); + let arr_type_info = get_type_info(arr_type); + let pos = switch (Map.get(unique_compound_type_map, thash, arr_type_info)) { + case (?pos) pos; + case (_) Debug.trap("unable to find compound type pos to store in primitive type sequence for " # debug_show (type_info)); + }; + unsigned_leb128(compound_type_buffer, pos); + }; + }; - i += 1; - }; + case (#Blob) return encode_compound_type_only( + #Array(#Nat8), + compound_type_buffer, + candid_type_buffer, + renaming_map, + unique_compound_type_map, + counter, + is_nested_child_of_compound_type, + ); - if (not type_exists and arr_type_is_compound) { - compound_type_buffer.add(T.TypeCode.Array); + case (#Record(record_types) or #Map(record_types)) { + let is_tuple = check_is_tuple(record_types); + + var i = 0; + while (i < record_types.size()) { + let field_type = record_types[i].1; + + let value_type_is_compound = is_compound_type(field_type); + + if (value_type_is_compound) encode_type_only( + field_type, + compound_type_buffer, + candid_type_buffer, + renaming_map, + unique_compound_type_map, + counter, + true, + ); + + i += 1; + }; + + compound_type_buffer.add(T.TypeCode.Record); + unsigned_leb128(compound_type_buffer, record_types.size()); + + i := 0; + while (i < record_types.size()) { + let field_type = record_types[i].1; + + let value_type_is_compound = is_compound_type(field_type); + + if (is_tuple) { + unsigned_leb128(compound_type_buffer, i); + } else { + let record_key = get_renamed_key(renaming_map, record_types[i].0); + + let hash_key = hash_record_key(record_key); + unsigned_leb128(compound_type_buffer, Nat32.toNat(hash_key)); + }; + + if (value_type_is_compound) { + let value_type_info = get_type_info(field_type); + let pos = switch (Map.get(unique_compound_type_map, thash, value_type_info)) { + case (?pos) pos; + case (_) Debug.trap("unable to find compound type pos to store in primitive type sequence for " # debug_show (type_info)); + }; + unsigned_leb128(compound_type_buffer, pos); + } else { + encode_primitive_type_only( + field_type, + compound_type_buffer, + candid_type_buffer, + true, + ); + }; + + i += 1; + }; + }; - let arr_type_info = get_type_info(arr_type); - let pos = switch (Map.get(unique_compound_type_map, thash, arr_type_info)) { - case (?pos) pos; - case (_) Debug.trap("unable to find compound type pos to store in primitive type sequence for " # debug_show (type_info, arr_type)); - }; - unsigned_leb128(compound_type_buffer, pos); + case (#Tuple(tuple_types)) { + return encode_compound_type_only( + #Record(tuple_type_to_record(tuple_types)), + compound_type_buffer, + candid_type_buffer, + renaming_map, + unique_compound_type_map, + counter, + true, + ); + }; - }; + case (#Variant(variant_types)) { + + var i = 0; + while (i < variant_types.size()) { + let variant_type = variant_types[i].1; + + let variant_type_is_compound = is_compound_type(variant_type); + + if (variant_type_is_compound) { + encode_compound_type_only( + variant_type, + compound_type_buffer, + candid_type_buffer, + renaming_map, + unique_compound_type_map, + counter, + true, + ); + }; + + i += 1; + }; + + compound_type_buffer.add(T.TypeCode.Variant); + unsigned_leb128(compound_type_buffer, variant_types.size()); + + i := 0; + while (i < variant_types.size()) { + let variant_key = get_renamed_key(renaming_map, variant_types[i].0); + let variant_type = variant_types[i].1; + let variant_type_is_compound = is_compound_type(variant_type); + + let hash_key = hash_record_key(variant_key); + unsigned_leb128(compound_type_buffer, Nat32.toNat(hash_key)); + + if (variant_type_is_compound) { + let variant_type_info = get_type_info(variant_type); + let pos = switch (Map.get(unique_compound_type_map, thash, variant_type_info)) { + case (?pos) pos; + case (_) Debug.trap("unable to find compound type pos to store in primitive type sequence for " # debug_show (type_info)); + }; + unsigned_leb128(compound_type_buffer, pos); + } else { + encode_primitive_type_only( + variant_type, + compound_type_buffer, + candid_type_buffer, + true, + ); + }; + + i += 1; + }; + }; - }; - case (#Blob, #Blob(blob)) { - let bytes = Array.map(Blob.toArray(blob), func(n : Nat8) : Candid = #Nat8(n)); - - return encode_compound_type( - #Array(#Nat8), - #Array(bytes), - compound_type_buffer, - candid_type_buffer, - value_buffer, - renaming_map, - unique_compound_type_map, - recursive_map, - counter, - is_nested_child_of_compound_type, - type_exists, - ); - }; - - case (#Array(#Nat8), #Blob(blob)) { - let bytes = Array.map(Blob.toArray(blob), func(n : Nat8) : Candid = #Nat8(n)); - - return encode_compound_type( - #Array(#Nat8), - #Array(bytes), - compound_type_buffer, - candid_type_buffer, - value_buffer, - renaming_map, - unique_compound_type_map, - recursive_map, - counter, - is_nested_child_of_compound_type, - type_exists, - ); - }; - case (#Blob, #Array(bytes)) { - if (bytes.size() > 0) { - switch (bytes[0]) { - case (#Nat8(_)) {}; - case (_) return Debug.trap("invalid blob value: expected array of Nat8, got array of " # debug_show bytes[0]); - }; + case (_) Debug.trap("encode_compound_type_only(): unknown compound type " # debug_show candid_type); }; - return encode_compound_type( - #Array(#Nat8), - #Array(bytes), - compound_type_buffer, - candid_type_buffer, - value_buffer, - renaming_map, - unique_compound_type_map, - recursive_map, - counter, - is_nested_child_of_compound_type, - type_exists, - ); - }; + ignore Map.put(unique_compound_type_map, thash, type_info, counter[C.COUNTER.COMPOUND_TYPE]); + counter[C.COUNTER.COMPOUND_TYPE] += 1; - case (#Record(record_types) or #Map(record_types), #Record(record_entries) or #Map(record_entries)) { - assert record_types.size() >= record_entries.size(); + }; - let is_tuple = check_is_tuple(record_types); + public func encode_type_only( + candid_type : CandidType, + compound_type_buffer : Buffer, + candid_type_buffer : Buffer, + renaming_map : Map, + unique_compound_type_map : Map, + counter : [var Nat], + is_nested_child_of_compound_type : Bool, + ) { + if (is_compound_type(candid_type)) { + encode_compound_type_only( + candid_type, + compound_type_buffer, + candid_type_buffer, + renaming_map, + unique_compound_type_map, + counter, + is_nested_child_of_compound_type, + ); - let cache_size = if (record_entries.size() % 2 == 0) { - record_entries.size() + 2; + // Add compound type reference to primitive type buffer for top-level types + if (not is_nested_child_of_compound_type) { + let type_info = get_type_info(candid_type); + let pos = switch (Map.get(unique_compound_type_map, thash, type_info)) { + case (?pos) pos; + case (_) Debug.trap("unable to find compound type pos to store in primitive type sequence for " # debug_show (type_info)); + }; + unsigned_leb128(candid_type_buffer, pos); + }; } else { - record_entries.size() + 3; + encode_primitive_type_only( + candid_type, + compound_type_buffer, + candid_type_buffer, + is_nested_child_of_compound_type, + ); + }; + }; + func get_type_info(_candid_type : CandidType) : Text { + let candid_type = switch (_candid_type) { + case (#Map(records)) #Record(records); + case (#Blob) #Array(#Nat8); + case (#Tuple(tuple_types)) #Record(tuple_type_to_record(tuple_types)); + case (candid_type) candid_type; }; - let record_entry_cache : Map.Map = Utils.create_map(cache_size); + debug_show candid_type; + }; - for ((k, v) in record_entries.vals()) { - let field_value_key = get_renamed_key(renaming_map, k); - ignore Map.put(record_entry_cache, thash, field_value_key, v); + func encode_primitive_type( + candid_type : CandidType, + candid_value : Candid, + compound_type_buffer : Buffer, + candid_type_buffer : Buffer, + value_buffer : Buffer, + renaming_map : Map, + unique_compound_type_map : Map, + recursive_map : Map, + is_nested_child_of_compound_type : Bool, + ignore_type : Bool, + ) { + let ref_candid_type_buffer = if (ignore_type) { + object { + public func add(_ : Nat8) {}; // do nothing + }; + } else if (is_nested_child_of_compound_type) { + compound_type_buffer; + } else { + candid_type_buffer; }; - var i = 0; - while (i < record_types.size()) { - let field_type = record_types[i].1; + switch (candid_type, candid_value) { + case (#Nat, #Nat(n)) { + // Debug.print("start encoding Nat: " # debug_show n); + ref_candid_type_buffer.add(T.TypeCode.Nat); + // Debug.print("encoded type codde"); + unsigned_leb128(value_buffer, n); - let field_type_key = get_renamed_key(renaming_map, record_types[i].0); + }; + case (#Nat8, #Nat8(n)) { + ref_candid_type_buffer.add(T.TypeCode.Nat8); + value_buffer.add(n); + }; + case (#Nat16, #Nat16(n)) { + ref_candid_type_buffer.add(T.TypeCode.Nat16); + ByteUtils.Buffer.LE.addNat16(value_buffer, n); + }; + case (#Nat32, #Nat32(n)) { + ref_candid_type_buffer.add(T.TypeCode.Nat32); + ByteUtils.Buffer.LE.addNat32(value_buffer, n); + }; + case (#Nat64, #Nat64(n)) { + ref_candid_type_buffer.add(T.TypeCode.Nat64); + ByteUtils.Buffer.LE.addNat64(value_buffer, n); + }; + case (#Int, #Int(n)) { + ref_candid_type_buffer.add(T.TypeCode.Int); + signed_leb128_64(value_buffer, n); + }; + case (#Int8, #Int8(i8)) { + ref_candid_type_buffer.add(T.TypeCode.Int8); + value_buffer.add(Int8.toNat8(i8)); + }; + case (#Int16, #Int16(i16)) { + ref_candid_type_buffer.add(T.TypeCode.Int16); + ByteUtils.Buffer.LE.addInt16(value_buffer, i16); + }; + case (#Int32, #Int32(i32)) { + ref_candid_type_buffer.add(T.TypeCode.Int32); + ByteUtils.Buffer.LE.addInt32(value_buffer, i32); + }; + case (#Int64, #Int64(i64)) { + ref_candid_type_buffer.add(T.TypeCode.Int64); + ByteUtils.Buffer.LE.addInt64(value_buffer, i64); + }; + case (#Float, #Float(f64)) { + ref_candid_type_buffer.add(T.TypeCode.Float); + ByteUtils.Buffer.LE.addFloat(value_buffer, f64); + }; + case (#Bool, #Bool(b)) { + ref_candid_type_buffer.add(T.TypeCode.Bool); + value_buffer.add(if (b) (1) else (0)); + }; + case (#Null, #Null) { + ref_candid_type_buffer.add(T.TypeCode.Null); + }; + case (#Empty, #Empty) { + ref_candid_type_buffer.add(T.TypeCode.Empty); + }; + case (#Text, #Text(t)) { + ref_candid_type_buffer.add(T.TypeCode.Text); - let field_value = switch (Map.get(record_entry_cache, Map.thash, field_type_key)) { - case (?field_value) field_value; - case (_) Debug.trap("unable to find field key in field types: " # debug_show field_type_key # "in " # debug_show record_entries); - }; + let utf8_blob = Text.encodeUtf8(t); + unsigned_leb128(value_buffer, utf8_blob.size()); - let value_type_is_compound = is_compound_type(field_type); + var i = 0; + while (i < utf8_blob.size()) { + value_buffer.add(utf8_blob[i]); + i += 1; + }; - ignore encode_candid( - field_type, - field_value, - compound_type_buffer, - candid_type_buffer, - value_buffer, - renaming_map, - unique_compound_type_map, - recursive_map, - counter, - true, - type_exists or not value_type_is_compound, - ); + }; + case (#Principal, #Principal(p)) { + ref_candid_type_buffer.add(T.TypeCode.Principal); + + value_buffer.add(0x01); // indicate transparency state + let blob = Principal.toBlob(p); + unsigned_leb128(value_buffer, blob.size()); + + var i = 0; + while (i < blob.size()) { + value_buffer.add(blob[i]); + i += 1; + }; + }; - i += 1; + case (_) Debug.trap("unknown (type, value) pair: " # debug_show (candid_type, candid_value)); }; + }; - if (not type_exists) { - compound_type_buffer.add(T.TypeCode.Record); - unsigned_leb128(compound_type_buffer, record_types.size()); - - i := 0; - - while (i < record_types.size()) { - let field_type = record_types[i].1; + func encode_compound_type( + candid_type : CandidType, + candid_value : Candid, + compound_type_buffer : Buffer, + candid_type_buffer : Buffer, + value_buffer : Buffer, + renaming_map : Map, + unique_compound_type_map : Map, + recursive_map : Map, + counter : [var Nat], + is_nested_child_of_compound_type : Bool, + _type_exists : Bool, + ) { + + // Debug.print("encode_compound_type(): " # debug_show (candid_type, candid_value)); + + // ----------------- Compound Types ----------------- // + + // encode_candid type only + // case (candid_type, #Null) { + // encode_nested_type(candid_type, compound_type_buffer); + // }; - let value_type_is_compound = is_compound_type(field_type); + let type_info = get_type_info(candid_type); - if (is_tuple) { - unsigned_leb128(compound_type_buffer, i); - } else { - let record_key = get_renamed_key(renaming_map, record_types[i].0); - let hash_key = hash_record_key(record_key); - unsigned_leb128(compound_type_buffer, Nat32.toNat(hash_key)); + // type_exists_in_compound_type_sequence + let type_exists = _type_exists or Map.has(unique_compound_type_map, thash, type_info); + + switch (candid_type, candid_value) { + + case (#Option(opt_type), #Option(opt_value)) { + + let opt_type_is_compound = is_compound_type(opt_type); + + if (not type_exists and not opt_type_is_compound) { + compound_type_buffer.add(T.TypeCode.Option); + }; + + if (opt_value == #Null and opt_type != #Null) { + // a result of being able to set #Null at any point in an #Option type + // for instance, type #Option(#Nat) with value #Null + + value_buffer.add(0); // no value + + if (not type_exists) encode_type_only( + opt_type, + compound_type_buffer, + candid_type_buffer, + renaming_map, + unique_compound_type_map, + counter, + true, + ); + + } else { + value_buffer.add(1); // has value + + ignore encode_candid( + opt_type, + opt_value, + compound_type_buffer, + candid_type_buffer, + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + true, + type_exists, + ); + }; + + if ( + not type_exists and opt_type_is_compound + ) { + // let prev_start = get_prev_compound_type_start_index(compound_type_buffer); + compound_type_buffer.add(T.TypeCode.Option); + let opt_type_info = get_type_info(opt_type); + let pos = switch (Map.get(unique_compound_type_map, thash, opt_type_info)) { + case (?pos) pos; + case (_) Debug.trap("unable to find compound type pos to store in primitive type sequence for " # debug_show (type_info)); + }; + unsigned_leb128(compound_type_buffer, pos); + }; }; - if (value_type_is_compound) { - let value_type_info = get_type_info(field_type); - let pos = switch (Map.get(unique_compound_type_map, thash, value_type_info)) { - case (?pos) pos; - case (_) Debug.trap("unable to find compound type pos to store in primitive type sequence for " # debug_show (type_info, field_type)); - }; + // a result of being able to set #Null at any point in an #Option type + // for instance, type #Option(#Nat) with value #Null + case (#Option(opt_type), #Null) { + value_buffer.add(0); // no value + + let opt_type_is_compound = is_compound_type(opt_type); + + if (not type_exists and not opt_type_is_compound) { + compound_type_buffer.add(T.TypeCode.Option); + }; + + if (not type_exists) encode_type_only( + opt_type, + compound_type_buffer, + candid_type_buffer, + renaming_map, + unique_compound_type_map, + counter, + true, + ); + + if ( + not type_exists and opt_type_is_compound + ) { + compound_type_buffer.add(T.TypeCode.Option); + let opt_type_info = get_type_info(opt_type); + let pos = switch (Map.get(unique_compound_type_map, thash, opt_type_info)) { + case (?pos) pos; + case (_) Debug.trap("unable to find compound type pos to store in primitive type sequence for " # debug_show (type_info, opt_type)); + }; + unsigned_leb128(compound_type_buffer, pos); + }; - unsigned_leb128(compound_type_buffer, pos); - } else { - encode_primitive_type_only( - field_type, - compound_type_buffer, - candid_type_buffer, - true, - ); }; - i += 1; - }; - }; + case (#Array(arr_type), #Array(arr_values)) { + let arr_type_is_compound = is_compound_type(arr_type); + + if (not type_exists and not arr_type_is_compound) { + compound_type_buffer.add(T.TypeCode.Array); + }; + + // if (not type_exists) + + unsigned_leb128(value_buffer, arr_values.size()); + + var i = 0; + + if (arr_values.size() == 0 and not type_exists) { + encode_type_only( + arr_type, + compound_type_buffer, + candid_type_buffer, + renaming_map, + unique_compound_type_map, + counter, + true, + ); + } else while (i < arr_values.size()) { + let val = arr_values[i]; + + ignore encode_candid( + arr_type, + val, + compound_type_buffer, + candid_type_buffer, + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + true, + type_exists or i > 0, + ); + + i += 1; + }; + + if (not type_exists and arr_type_is_compound) { + compound_type_buffer.add(T.TypeCode.Array); + + let arr_type_info = get_type_info(arr_type); + let pos = switch (Map.get(unique_compound_type_map, thash, arr_type_info)) { + case (?pos) pos; + case (_) Debug.trap("unable to find compound type pos to store in primitive type sequence for " # debug_show (type_info, arr_type)); + }; + unsigned_leb128(compound_type_buffer, pos); + + }; - }; - case (#Tuple(tuple_types), #Tuple(tuple_values)) { - return encode_compound_type( - #Record(tuple_type_to_record(tuple_types)), - #Record(tuple_value_to_record(tuple_values)), - compound_type_buffer, - candid_type_buffer, - value_buffer, - renaming_map, - unique_compound_type_map, - recursive_map, - counter, - is_nested_child_of_compound_type, - type_exists, - ); - }; - case (#Tuple(tuple_types), #Record(tuple_values)) { - var i = 0; - assert Itertools.all( - tuple_values.vals(), - func((k, v) : (Text, Any)) : Bool { - i += 1; - Utils.text_is_number(k) and Utils.text_to_nat(k) == (i - 1 : Nat); - }, - ); - - return encode_compound_type( - #Record(tuple_type_to_record(tuple_types)), - #Record(tuple_values), - compound_type_buffer, - candid_type_buffer, - value_buffer, - renaming_map, - unique_compound_type_map, - recursive_map, - counter, - is_nested_child_of_compound_type, - type_exists, - ); - }; - - case (#Record(record_types), #Tuple(tuple_values)) { - var i = 0; - assert Itertools.all( - record_types.vals(), - func((k, v) : (Text, Any)) : Bool { - i += 1; - Utils.text_is_number(k) and Utils.text_to_nat(k) == (i - 1 : Nat); - }, - ); - - return encode_compound_type( - #Record(record_types), - #Record(tuple_value_to_record(tuple_values)), - compound_type_buffer, - candid_type_buffer, - value_buffer, - renaming_map, - unique_compound_type_map, - recursive_map, - counter, - is_nested_child_of_compound_type, - type_exists, - ); - }; - - case (#Variant(variant_types), #Variant(variant)) { - let variant_key = get_renamed_key(renaming_map, variant.0); - let variant_value = variant.1; + }; + case (#Blob, #Blob(blob)) { + let bytes = Array.map(Blob.toArray(blob), func(n : Nat8) : Candid = #Nat8(n)); + + return encode_compound_type( + #Array(#Nat8), + #Array(bytes), + compound_type_buffer, + candid_type_buffer, + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + is_nested_child_of_compound_type, + type_exists, + ); + }; - let variant_index_res = Array.indexOf<(Text, CandidType)>( - (variant_key, #Empty), // attach #Empty variant type to satisfy the type checker - variant_types, - func((a, _) : (Text, CandidType), (b, _) : (Text, CandidType)) : Bool = a == b, - ); + case (#Array(#Nat8), #Blob(blob)) { + let bytes = Array.map(Blob.toArray(blob), func(n : Nat8) : Candid = #Nat8(n)); + + return encode_compound_type( + #Array(#Nat8), + #Array(bytes), + compound_type_buffer, + candid_type_buffer, + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + is_nested_child_of_compound_type, + type_exists, + ); + }; + case (#Blob, #Array(bytes)) { + if (bytes.size() > 0) { + switch (bytes[0]) { + case (#Nat8(_)) {}; + case (_) return Debug.trap("invalid blob value: expected array of Nat8, got array of " # debug_show bytes[0]); + }; + }; + + return encode_compound_type( + #Array(#Nat8), + #Array(bytes), + compound_type_buffer, + candid_type_buffer, + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + is_nested_child_of_compound_type, + type_exists, + ); + }; - let variant_index = switch (variant_index_res) { - case (?index) index; - case (_) Debug.trap("unable to find variant key in variant types"); - }; + case (#Record(record_types) or #Map(record_types), #Record(record_entries) or #Map(record_entries)) { + assert record_types.size() >= record_entries.size(); + + let is_tuple = check_is_tuple(record_types); + + let cache_size = if (record_entries.size() % 2 == 0) { + record_entries.size() + 2; + } else { + record_entries.size() + 3; + }; + + let record_entry_cache : Map.Map = Utils.create_map(cache_size); + + for ((k, v) in record_entries.vals()) { + let field_value_key = get_renamed_key(renaming_map, k); + ignore Map.put(record_entry_cache, thash, field_value_key, v); + }; + + var i = 0; + while (i < record_types.size()) { + let field_type = record_types[i].1; + + let field_type_key = get_renamed_key(renaming_map, record_types[i].0); + + let field_value = switch (Map.get(record_entry_cache, Map.thash, field_type_key)) { + case (?field_value) field_value; + case (_) Debug.trap("unable to find field key in field types: " # debug_show field_type_key # "in " # debug_show record_entries); + }; + + let value_type_is_compound = is_compound_type(field_type); + + ignore encode_candid( + field_type, + field_value, + compound_type_buffer, + candid_type_buffer, + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + true, + type_exists or not value_type_is_compound, + ); + + i += 1; + }; + + if (not type_exists) { + compound_type_buffer.add(T.TypeCode.Record); + unsigned_leb128(compound_type_buffer, record_types.size()); + + i := 0; + + while (i < record_types.size()) { + let field_type = record_types[i].1; + + let value_type_is_compound = is_compound_type(field_type); + + if (is_tuple) { + unsigned_leb128(compound_type_buffer, i); + } else { + let record_key = get_renamed_key(renaming_map, record_types[i].0); + let hash_key = hash_record_key(record_key); + unsigned_leb128(compound_type_buffer, Nat32.toNat(hash_key)); + }; + + if (value_type_is_compound) { + let value_type_info = get_type_info(field_type); + let pos = switch (Map.get(unique_compound_type_map, thash, value_type_info)) { + case (?pos) pos; + case (_) Debug.trap("unable to find compound type pos to store in primitive type sequence for " # debug_show (type_info, field_type)); + }; + + unsigned_leb128(compound_type_buffer, pos); + } else { + encode_primitive_type_only( + field_type, + compound_type_buffer, + candid_type_buffer, + true, + ); + }; + + i += 1; + }; + }; - var i = 0; - while (i < variant_types.size()) { - let variant_key = variant_types[i].0; - let variant_type = variant_types[i].1; + }; + case (#Tuple(tuple_types), #Tuple(tuple_values)) { + return encode_compound_type( + #Record(tuple_type_to_record(tuple_types)), + #Record(tuple_value_to_record(tuple_values)), + compound_type_buffer, + candid_type_buffer, + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + is_nested_child_of_compound_type, + type_exists, + ); + }; + case (#Tuple(tuple_types), #Record(tuple_values)) { + var i = 0; + assert Itertools.all( + tuple_values.vals(), + func((k, v) : (Text, Any)) : Bool { + i += 1; + Utils.text_is_number(k) and Utils.text_to_nat(k) == (i - 1 : Nat); + }, + ); + + return encode_compound_type( + #Record(tuple_type_to_record(tuple_types)), + #Record(tuple_values), + compound_type_buffer, + candid_type_buffer, + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + is_nested_child_of_compound_type, + type_exists, + ); + }; - let variant_type_is_compound = is_compound_type(variant_type); + case (#Record(record_types), #Tuple(tuple_values)) { + var i = 0; + assert Itertools.all( + record_types.vals(), + func((k, v) : (Text, Any)) : Bool { + i += 1; + Utils.text_is_number(k) and Utils.text_to_nat(k) == (i - 1 : Nat); + }, + ); + + return encode_compound_type( + #Record(record_types), + #Record(tuple_value_to_record(tuple_values)), + compound_type_buffer, + candid_type_buffer, + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + is_nested_child_of_compound_type, + type_exists, + ); + }; - if (i == variant_index) { - unsigned_leb128(value_buffer, i); + case (#Variant(variant_types), #Variant(variant)) { + let variant_key = get_renamed_key(renaming_map, variant.0); + let variant_value = variant.1; + + let variant_index_res = Array.indexOf<(Text, CandidType)>( + (variant_key, #Empty), // attach #Empty variant type to satisfy the type checker + variant_types, + func((a, _) : (Text, CandidType), (b, _) : (Text, CandidType)) : Bool = a == b, + ); + + let variant_index = switch (variant_index_res) { + case (?index) index; + case (_) Debug.trap("unable to find variant key in variant types"); + }; + + var i = 0; + while (i < variant_types.size()) { + let variant_key = variant_types[i].0; + let variant_type = variant_types[i].1; + + let variant_type_is_compound = is_compound_type(variant_type); + + if (i == variant_index) { + unsigned_leb128(value_buffer, i); + + ignore encode_candid( + variant_type, + variant_value, + compound_type_buffer, + candid_type_buffer, + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + true, + type_exists or not variant_type_is_compound, + ); + } else if (variant_type_is_compound) { + encode_compound_type_only( + variant_type, + compound_type_buffer, + candid_type_buffer, + renaming_map, + unique_compound_type_map, + counter, + true, + ); + }; + + i += 1; + }; + + if (not type_exists) { + compound_type_buffer.add(T.TypeCode.Variant); + unsigned_leb128(compound_type_buffer, variant_types.size()); + + i := 0; + while (i < variant_types.size()) { + let variant_key = get_renamed_key(renaming_map, variant_types[i].0); + let variant_type = variant_types[i].1; + let variant_type_is_compound = is_compound_type(variant_type); + + let hash_key = hash_record_key(variant_key); + unsigned_leb128(compound_type_buffer, Nat32.toNat(hash_key)); + + if (variant_type_is_compound) { + let variant_type_info = get_type_info(variant_type); + let pos = switch (Map.get(unique_compound_type_map, thash, variant_type_info)) { + case (?pos) pos; + case (_) Debug.trap("unable to find compound type pos to store in primitive type sequence for " # debug_show (type_info)); + }; + unsigned_leb128(compound_type_buffer, pos); + } else { + encode_primitive_type_only( + variant_type, + compound_type_buffer, + candid_type_buffer, + true, + ); + }; + + i += 1; + }; + }; - ignore encode_candid( - variant_type, - variant_value, - compound_type_buffer, - candid_type_buffer, - value_buffer, - renaming_map, - unique_compound_type_map, - recursive_map, - counter, - true, - type_exists or not variant_type_is_compound, - ); - } else if (variant_type_is_compound) { - encode_compound_type_only( - variant_type, - compound_type_buffer, - candid_type_buffer, - renaming_map, - unique_compound_type_map, - counter, - true, - ); - }; + }; - i += 1; + case (_) Debug.trap("invalid (type, value) pair: " # debug_show { candid_type; candid_value }); }; if (not type_exists) { - compound_type_buffer.add(T.TypeCode.Variant); - unsigned_leb128(compound_type_buffer, variant_types.size()); - - i := 0; - while (i < variant_types.size()) { - let variant_key = get_renamed_key(renaming_map, variant_types[i].0); - let variant_type = variant_types[i].1; - let variant_type_is_compound = is_compound_type(variant_type); + var pos = counter[C.COUNTER.COMPOUND_TYPE]; + counter[C.COUNTER.COMPOUND_TYPE] += 1; - let hash_key = hash_record_key(variant_key); - unsigned_leb128(compound_type_buffer, Nat32.toNat(hash_key)); + ignore Map.put(unique_compound_type_map, thash, type_info, pos); + }; - if (variant_type_is_compound) { - let variant_type_info = get_type_info(variant_type); - let pos = switch (Map.get(unique_compound_type_map, thash, variant_type_info)) { + // if it is the top level parent and not one of the nested children + if (not is_nested_child_of_compound_type) { + let pos = switch (Map.get(unique_compound_type_map, thash, type_info)) { case (?pos) pos; case (_) Debug.trap("unable to find compound type pos to store in primitive type sequence for " # debug_show (type_info)); - }; - unsigned_leb128(compound_type_buffer, pos); - } else { - encode_primitive_type_only( - variant_type, - compound_type_buffer, - candid_type_buffer, - true, - ); }; - - i += 1; - }; + unsigned_leb128(candid_type_buffer, pos); }; - - }; - - case (_) Debug.trap("invalid (type, value) pair: " # debug_show { candid_type; candid_value }); }; - if (not type_exists) { - var pos = counter[C.COUNTER.COMPOUND_TYPE]; - counter[C.COUNTER.COMPOUND_TYPE] += 1; - - ignore Map.put(unique_compound_type_map, thash, type_info, pos); - }; + func encode_candid( + candid_type : CandidType, + candid_value : Candid, + compound_type_buffer : Buffer, + candid_type_buffer : Buffer, + value_buffer : Buffer, + renaming_map : Map, + unique_compound_type_map : Map, + recursive_map : Map, + counter : [var Nat], + is_nested_child_of_compound_type : Bool, + ignore_type : Bool, + ) : ?Hash { + + let candid_is_compound_type = is_compound_type(candid_type); + + if (candid_is_compound_type) { + encode_compound_type( + candid_type, + candid_value, + compound_type_buffer, + candid_type_buffer, + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + counter, + is_nested_child_of_compound_type, + ignore_type, + ); + } else { + encode_primitive_type( + candid_type, + candid_value, + compound_type_buffer, + candid_type_buffer, + value_buffer, + renaming_map, + unique_compound_type_map, + recursive_map, + is_nested_child_of_compound_type, + ignore_type, + ); + }; - // if it is the top level parent and not one of the nested children - if (not is_nested_child_of_compound_type) { - let pos = switch (Map.get(unique_compound_type_map, thash, type_info)) { - case (?pos) pos; - case (_) Debug.trap("unable to find compound type pos to store in primitive type sequence for " # debug_show (type_info)); - }; - unsigned_leb128(candid_type_buffer, pos); + null; }; - }; - - func encode_candid( - candid_type : CandidType, - candid_value : Candid, - compound_type_buffer : Buffer, - candid_type_buffer : Buffer, - value_buffer : Buffer, - renaming_map : Map, - unique_compound_type_map : Map, - recursive_map : Map, - counter : [var Nat], - is_nested_child_of_compound_type : Bool, - ignore_type : Bool, - ) : ?Hash { - - let candid_is_compound_type = is_compound_type(candid_type); - - if (candid_is_compound_type) { - encode_compound_type( - candid_type, - candid_value, - compound_type_buffer, - candid_type_buffer, - value_buffer, - renaming_map, - unique_compound_type_map, - recursive_map, - counter, - is_nested_child_of_compound_type, - ignore_type, - ); - } else { - encode_primitive_type( - candid_type, - candid_value, - compound_type_buffer, - candid_type_buffer, - value_buffer, - renaming_map, - unique_compound_type_map, - recursive_map, - is_nested_child_of_compound_type, - ignore_type, - ); + type InternalCandidTypes = { + #Int; + #Int8; + #Int16; + #Int32; + #Int64; + + #Nat; + #Nat8; + #Nat16; + #Nat32; + #Nat64; + #Bool; + #Float; + #Text; + #Blob; + #Null; + #Empty; + #Principal; + + #Option : InternalCandidTypes; + #Array : [InternalCandidTypes]; + #Record : [(Text, InternalCandidTypes)]; + // #Map : [(Text, InternalCandidTypes)]; + #Tuple : [InternalCandidTypes]; + #Variant : [(Text, InternalCandidTypes)]; + #Recursive : (Nat, InternalCandidTypes); }; - null; - }; - type InternalCandidTypes = { - #Int; - #Int8; - #Int16; - #Int32; - #Int64; - - #Nat; - #Nat8; - #Nat16; - #Nat32; - #Nat64; - #Bool; - #Float; - #Text; - #Blob; - #Null; - #Empty; - #Principal; - - #Option : InternalCandidTypes; - #Array : [InternalCandidTypes]; - #Record : [(Text, InternalCandidTypes)]; - // #Map : [(Text, InternalCandidTypes)]; - #Tuple : [InternalCandidTypes]; - #Variant : [(Text, InternalCandidTypes)]; - #Recursive : (Nat, InternalCandidTypes); - }; - - type InternalCandidTypeNode = { - type_ : InternalCandidTypes; - height : Nat; - parent_index : Nat; - key : ?Text; - }; - - type CandidTypeNode = { - type_ : CandidType; - height : Nat; - parent_index : Nat; - key : ?Text; - }; - - func to_candid_types(candid : Candid, renaming_map : Map) : (InternalCandidTypes) { - switch (candid) { - case (#Nat(_)) (#Nat); - case (#Nat8(_)) (#Nat8); - case (#Nat16(_)) (#Nat16); - case (#Nat32(_)) (#Nat32); - case (#Nat64(_)) (#Nat64); - - case (#Int(_)) (#Int); - case (#Int8(_)) (#Int8); - case (#Int16(_)) (#Int16); - case (#Int32(_)) (#Int32); - case (#Int64(_)) (#Int64); - - case (#Float(_)) (#Float); - - case (#Bool(_)) (#Bool); - - case (#Principal(_)) (#Principal); - - case (#Text(_)) (#Text); - - case (#Null) (#Null); - case (#Empty) (#Empty); - - case (#Blob(_)) { #Array([#Nat8]) }; - - case (#Option(optType)) { - let inner_type = to_candid_types(optType, renaming_map); - #Option(inner_type); - }; - case (#Array(arr)) { - let inner_types = Buffer.Buffer(arr.size()); - - for (item in arr.vals()) { - let (inner_type) = to_candid_types(item, renaming_map); - inner_types.add(inner_type); - }; - - let types = Buffer.toArray(inner_types); - - (#Array(types)); - }; - - case (#Record(records) or #Map(records)) { - let types_buffer = Buffer.Buffer<(Text, InternalCandidTypes)>(records.size()); - - for ((record_key, record_val) in records.vals()) { - let (inner_type) = to_candid_types(record_val, renaming_map); - - types_buffer.add((record_key, inner_type)); - }; - - let types = Buffer.toArray(types_buffer); - - #Record(types); - }; - - case (#Tuple(tuple_values)) { - let tuple_types = Array.map( - tuple_values, - func(c : Candid) : InternalCandidTypes { - to_candid_types(c, renaming_map); - }, - ); - - #Tuple(tuple_types); - }; - - case (#Variant((key, val))) { - let (inner_type) = to_candid_types(val, renaming_map); - - #Variant([(key, inner_type)]); - }; + type InternalCandidTypeNode = { + type_ : InternalCandidTypes; + height : Nat; + parent_index : Nat; + key : ?Text; }; - }; - - func internal_to_candid_type(internal_type : InternalCandidTypes, vec_index : ?Nat) : CandidType { - switch (internal_type, vec_index) { - case (#Array(vec_types), ?vec_index) #Array(internal_to_candid_type(vec_types[vec_index], null)); - case (#Array(vec_types), _) #Array(internal_to_candid_type(vec_types[0], null)); - case (#Option(opt_type), _) #Option(internal_to_candid_type(opt_type, null)); - case (#Record(record_types), _) { - let new_record_types = Array.map<(Text, InternalCandidTypes), (Text, CandidType)>( - record_types, - func((key, field_type) : (Text, InternalCandidTypes)) : (Text, CandidType) { - let inner_type = internal_to_candid_type(field_type, null); - (key, inner_type); - }, - ); - - #Record(new_record_types); - }; - case (#Tuple(tuple_types), _) { - let new_tuple_types = Array.map( - tuple_types, - func(inner_type : InternalCandidTypes) : CandidType { - internal_to_candid_type(inner_type, null); - }, - ); - - #Tuple(new_tuple_types); - }; - case (#Variant(variant_types), _) { - let new_variant_types = Array.map<(Text, InternalCandidTypes), (Text, CandidType)>( - variant_types, - func((key, variant_type) : (Text, InternalCandidTypes)) : (Text, CandidType) { - let inner_type = internal_to_candid_type(variant_type, null); - (key, inner_type); - }, - ); - - #Variant(new_variant_types); - }; - case (#Recursive(n, _), _) #Recursive(n); - - case (#Int, _) #Int; - case (#Int8, _) #Int8; - case (#Int16, _) #Int16; - case (#Int32, _) #Int32; - case (#Int64, _) #Int64; - - case (#Nat, _) #Nat; - case (#Nat8, _) #Nat8; - case (#Nat16, _) #Nat16; - case (#Nat32, _) #Nat32; - case (#Nat64, _) #Nat64; - - case (#Bool, _) #Bool; - case (#Float, _) #Float; - case (#Text, _) #Text; - case (#Blob, _) #Blob; - case (#Null, _) #Null; - case (#Empty, _) #Empty; - case (#Principal, _) #Principal; - + type CandidTypeNode = { + type_ : CandidType; + height : Nat; + parent_index : Nat; + key : ?Text; }; - }; - func to_candid_record_field_type(node : CandidTypeNode) : (Text, CandidType) { - let ?key = node.key else return Debug.trap("to_candid_record_field_type: key is null"); - return (key, node.type_); - }; + func to_candid_types(candid : Candid, renaming_map : Map) : (InternalCandidTypes) { + switch (candid) { + case (#Nat(_)) (#Nat); + case (#Nat8(_)) (#Nat8); + case (#Nat16(_)) (#Nat16); + case (#Nat32(_)) (#Nat32); + case (#Nat64(_)) (#Nat64); - func merge_candid_variants_and_array_types(rows : Buffer<[InternalCandidTypeNode]>) : Result { - let buffer = Buffer.Buffer(8); + case (#Int(_)) (#Int); + case (#Int8(_)) (#Int8); + case (#Int16(_)) (#Int16); + case (#Int32(_)) (#Int32); + case (#Int64(_)) (#Int64); - func calc_height(parent : Nat, child : Nat) : Nat = parent + child; + case (#Float(_)) (#Float); - let ?_bottom = rows.removeLast() else return #err("trying to pop bottom but rows is empty"); + case (#Bool(_)) (#Bool); - var bottom = Array.map( - _bottom, - func(node : InternalCandidTypeNode) : CandidTypeNode = { - type_ = internal_to_candid_type(node.type_, null); - height = node.height; - parent_index = node.parent_index; - key = node.key; - }, - ); + case (#Principal(_)) (#Principal); - while (rows.size() > 0) { + case (#Text(_)) (#Text); - let ?above_bottom = rows.removeLast() else return #err("trying to pop above_bottom but rows is empty"); + case (#Null) (#Null); + case (#Empty) (#Empty); - var bottom_iter = Itertools.peekable(bottom.vals()); + case (#Blob(_)) { #Array([#Nat8]) }; - let variants = Buffer.Buffer<(Text, CandidType)>(bottom.size()); - let variant_indexes = Buffer.Buffer(bottom.size()); - - for ((index, parent_node) in Itertools.enumerate(above_bottom.vals())) { - let tmp_bottom_iter = PeekableIter.takeWhile(bottom_iter, func({ parent_index; key } : CandidTypeNode) : Bool = index == parent_index); - let { parent_index; key = parent_key } = parent_node; - - switch (parent_node.type_) { - case (#Option(_)) { - let ?child_node = tmp_bottom_iter.next() else return #err(" #Option error: no item in tmp_bottom_iter"); - - let merged_node : CandidTypeNode = { - type_ = #Option(child_node.type_); - height = calc_height(parent_node.height, child_node.height); - parent_index; - key = parent_key; - }; - buffer.add(merged_node); - }; - case (#Array(_)) { - let vec_nodes = Iter.toArray(tmp_bottom_iter); - - let max = { - var height = 0; - var type_ : CandidType = #Empty; - }; - - for (node in vec_nodes.vals()) { - if (max.height < node.height) { - max.height := node.height; - max.type_ := node.type_; - }; + case (#Option(optType)) { + let inner_type = to_candid_types(optType, renaming_map); + #Option(inner_type); }; + case (#Array(arr)) { + let inner_types = Buffer.Buffer(arr.size()); - let best_node : CandidTypeNode = { - type_ = #Array(max.type_); - height = calc_height(parent_node.height, max.height); - parent_index; - key = parent_key; - }; + for (item in arr.vals()) { + let (inner_type) = to_candid_types(item, renaming_map); + inner_types.add(inner_type); + }; - buffer.add(best_node); - }; - case (#Record(_)) { - var height = 0; + let types = Buffer.toArray(inner_types); - func get_max_height(item : CandidTypeNode) { - height := Nat.max(height, item.height); + (#Array(types)); }; - var record_fields : [(Text, CandidType)] = Iter.toArray( - Iter.map( - tmp_bottom_iter, - func(node : CandidTypeNode) : (Text, CandidType) { - get_max_height(node); - to_candid_record_field_type(node); - }, - ) - ); + case (#Record(records) or #Map(records)) { + let types_buffer = Buffer.Buffer<(Text, InternalCandidTypes)>(records.size()); - let merged_node : CandidTypeNode = { - type_ = #Record(record_fields); - height = calc_height(parent_node.height, height); - parent_index; - key = parent_key; - }; - buffer.add(merged_node); - }; - case (#Variant(_)) { - var height = 0; + for ((record_key, record_val) in records.vals()) { + let (inner_type) = to_candid_types(record_val, renaming_map); - func get_max_height(item : CandidTypeNode) { - height := Nat.max(height, item.height); - }; + types_buffer.add((record_key, inner_type)); + }; - var variant_types : [(Text, CandidType)] = Iter.toArray( - Iter.map( - tmp_bottom_iter, - func(node : CandidTypeNode) : (Text, CandidType) { - get_max_height(node); - to_candid_record_field_type(node); - }, - ) - ); + let types = Buffer.toArray(types_buffer); - for (variant_type in variant_types.vals()) { - variants.add(variant_type); + #Record(types); }; - variant_indexes.add(buffer.size()); + case (#Tuple(tuple_values)) { + let tuple_types = Array.map( + tuple_values, + func(c : Candid) : InternalCandidTypes { + to_candid_types(c, renaming_map); + }, + ); - let merged_node : CandidTypeNode = { - type_ = #Variant(variant_types); - height = calc_height(parent_node.height, height); - parent_index; - key = parent_key; + #Tuple(tuple_types); }; - buffer.add(merged_node); + case (#Variant((key, val))) { + let (inner_type) = to_candid_types(val, renaming_map); - }; - case (_) { - let new_parent_node : CandidTypeNode = { - type_ = internal_to_candid_type(parent_node.type_, null); - height = parent_node.height; - parent_index; - key = parent_key; + #Variant([(key, inner_type)]); }; - - buffer.add(new_parent_node); - }; }; - }; - if (variants.size() > 0) { - let full_variant_type : CandidType = #Variant(Buffer.toArray(variants)); + }; - for (index in variant_indexes.vals()) { - let prev_node = buffer.get(index); - let new_node : CandidTypeNode = { - type_ = full_variant_type; - height = prev_node.height; - parent_index = prev_node.parent_index; - key = prev_node.key; - }; + func internal_to_candid_type(internal_type : InternalCandidTypes, vec_index : ?Nat) : CandidType { + switch (internal_type, vec_index) { + case (#Array(vec_types), ?vec_index) #Array(internal_to_candid_type(vec_types[vec_index], null)); + case (#Array(vec_types), _) #Array(internal_to_candid_type(vec_types[0], null)); + case (#Option(opt_type), _) #Option(internal_to_candid_type(opt_type, null)); + case (#Record(record_types), _) { + let new_record_types = Array.map<(Text, InternalCandidTypes), (Text, CandidType)>( + record_types, + func((key, field_type) : (Text, InternalCandidTypes)) : (Text, CandidType) { + let inner_type = internal_to_candid_type(field_type, null); + (key, inner_type); + }, + ); + + #Record(new_record_types); + }; + case (#Tuple(tuple_types), _) { + let new_tuple_types = Array.map( + tuple_types, + func(inner_type : InternalCandidTypes) : CandidType { + internal_to_candid_type(inner_type, null); + }, + ); + + #Tuple(new_tuple_types); + }; + case (#Variant(variant_types), _) { + let new_variant_types = Array.map<(Text, InternalCandidTypes), (Text, CandidType)>( + variant_types, + func((key, variant_type) : (Text, InternalCandidTypes)) : (Text, CandidType) { + let inner_type = internal_to_candid_type(variant_type, null); + (key, inner_type); + }, + ); + + #Variant(new_variant_types); + }; + case (#Recursive(n, _), _) #Recursive(n); + + case (#Int, _) #Int; + case (#Int8, _) #Int8; + case (#Int16, _) #Int16; + case (#Int32, _) #Int32; + case (#Int64, _) #Int64; + + case (#Nat, _) #Nat; + case (#Nat8, _) #Nat8; + case (#Nat16, _) #Nat16; + case (#Nat32, _) #Nat32; + case (#Nat64, _) #Nat64; + + case (#Bool, _) #Bool; + case (#Float, _) #Float; + case (#Text, _) #Text; + case (#Blob, _) #Blob; + case (#Null, _) #Null; + case (#Empty, _) #Empty; + case (#Principal, _) #Principal; - buffer.put(index, new_node); }; - }; + }; - bottom := Buffer.toArray(buffer); - buffer.clear(); + func to_candid_record_field_type(node : CandidTypeNode) : (Text, CandidType) { + let ?key = node.key else return Debug.trap("to_candid_record_field_type: key is null"); + return (key, node.type_); }; - let merged_type = bottom[0].type_; - #ok(merged_type); - }; + func merge_candid_variants_and_array_types(rows : Buffer<[InternalCandidTypeNode]>) : Result { + let buffer = Buffer.Buffer(8); - func get_candid_height_value(type_ : InternalCandidTypes) : Nat { - switch (type_) { - case (#Empty or #Null) 0; - case (_) 1; - }; - }; + func calc_height(parent : Nat, child : Nat) : Nat = parent + child; - // for most of the typs we can easily retrieve it from the value, but for an array it becomes a bit tricky - // because of optional values we can have seemingly different types in the array - // for example type [?Nat] with values [null, ?1], for each values will have a inferred type of [#Option(#Null), #Option(#Nat)] - // We need a way to choose #Option(#Nat) over #Option(#Null) in this case + let ?_bottom = rows.removeLast() else return #err("trying to pop bottom but rows is empty"); - func order_candid_types_by_height_bfs(rows : Buffer<[InternalCandidTypeNode]>) { + var bottom = Array.map( + _bottom, + func(node : InternalCandidTypeNode) : CandidTypeNode = { + type_ = internal_to_candid_type(node.type_, null); + height = node.height; + parent_index = node.parent_index; + key = node.key; + }, + ); - label while_loop while (rows.size() > 0) { - let candid_values = Buffer.last(rows) else return Prelude.unreachable(); - let buffer = Buffer.Buffer(8); + while (rows.size() > 0) { + + let ?above_bottom = rows.removeLast() else return #err("trying to pop above_bottom but rows is empty"); + + var bottom_iter = Itertools.peekable(bottom.vals()); + + let variants = Buffer.Buffer<(Text, CandidType)>(bottom.size()); + let variant_indexes = Buffer.Buffer(bottom.size()); + + for ((index, parent_node) in Itertools.enumerate(above_bottom.vals())) { + let tmp_bottom_iter = PeekableIter.takeWhile(bottom_iter, func({ parent_index; key } : CandidTypeNode) : Bool = index == parent_index); + let { parent_index; key = parent_key } = parent_node; + + switch (parent_node.type_) { + case (#Option(_)) { + let ?child_node = tmp_bottom_iter.next() else return #err(" #Option error: no item in tmp_bottom_iter"); + + let merged_node : CandidTypeNode = { + type_ = #Option(child_node.type_); + height = calc_height(parent_node.height, child_node.height); + parent_index; + key = parent_key; + }; + buffer.add(merged_node); + }; + case (#Array(_)) { + let vec_nodes = Iter.toArray(tmp_bottom_iter); + + let max = { + var height = 0; + var type_ : CandidType = #Empty; + }; + + for (node in vec_nodes.vals()) { + if (max.height < node.height) { + max.height := node.height; + max.type_ := node.type_; + }; + }; + + let best_node : CandidTypeNode = { + type_ = #Array(max.type_); + height = calc_height(parent_node.height, max.height); + parent_index; + key = parent_key; + }; + + buffer.add(best_node); + }; + case (#Record(_)) { + var height = 0; + + func get_max_height(item : CandidTypeNode) { + height := Nat.max(height, item.height); + }; + + var record_fields : [(Text, CandidType)] = Iter.toArray( + Iter.map( + tmp_bottom_iter, + func(node : CandidTypeNode) : (Text, CandidType) { + get_max_height(node); + to_candid_record_field_type(node); + }, + ) + ); + + let merged_node : CandidTypeNode = { + type_ = #Record(record_fields); + height = calc_height(parent_node.height, height); + parent_index; + key = parent_key; + }; + buffer.add(merged_node); + }; + case (#Variant(_)) { + var height = 0; + + func get_max_height(item : CandidTypeNode) { + height := Nat.max(height, item.height); + }; + + var variant_types : [(Text, CandidType)] = Iter.toArray( + Iter.map( + tmp_bottom_iter, + func(node : CandidTypeNode) : (Text, CandidType) { + get_max_height(node); + to_candid_record_field_type(node); + }, + ) + ); + + for (variant_type in variant_types.vals()) { + variants.add(variant_type); + }; + + variant_indexes.add(buffer.size()); + + let merged_node : CandidTypeNode = { + type_ = #Variant(variant_types); + height = calc_height(parent_node.height, height); + parent_index; + key = parent_key; + }; + + buffer.add(merged_node); + + }; + case (_) { + let new_parent_node : CandidTypeNode = { + type_ = internal_to_candid_type(parent_node.type_, null); + height = parent_node.height; + parent_index; + key = parent_key; + }; + + buffer.add(new_parent_node); + }; + }; + }; - var has_compound_type = false; + if (variants.size() > 0) { + let full_variant_type : CandidType = #Variant(Buffer.toArray(variants)); - for ((index, parent_node) in Itertools.enumerate(candid_values.vals())) { + for (index in variant_indexes.vals()) { + let prev_node = buffer.get(index); + let new_node : CandidTypeNode = { + type_ = full_variant_type; + height = prev_node.height; + parent_index = prev_node.parent_index; + key = prev_node.key; + }; - switch (parent_node.type_) { - case (#Option(inner_type)) { - has_compound_type := true; - let child_node : InternalCandidTypeNode = { - type_ = inner_type; - height = get_candid_height_value(inner_type); - parent_index = index; - key = null; + buffer.put(index, new_node); + }; }; - buffer.add(child_node); - }; - case (#Array(vec_types)) { - has_compound_type := true; + bottom := Buffer.toArray(buffer); + buffer.clear(); + }; - for (vec_type in vec_types.vals()) { - let child_node : InternalCandidTypeNode = { - type_ = vec_type; - height = get_candid_height_value(vec_type); - parent_index = index; - key = null; - }; + let merged_type = bottom[0].type_; + #ok(merged_type); + }; - buffer.add(child_node); - }; + func get_candid_height_value(type_ : InternalCandidTypes) : Nat { + switch (type_) { + case (#Empty or #Null) 0; + case (_) 1; + }; + }; - }; - case (#Record(records)) { - - for ((key, field_type) in records.vals()) { - has_compound_type := true; - let child_node : InternalCandidTypeNode = { - type_ = field_type; - height = get_candid_height_value(field_type); - parent_index = index; - key = ?key; - }; - buffer.add(child_node); + // for most of the typs we can easily retrieve it from the value, but for an array it becomes a bit tricky + // because of optional values we can have seemingly different types in the array + // for example type [?Nat] with values [null, ?1], for each values will have a inferred type of [#Option(#Null), #Option(#Nat)] + // We need a way to choose #Option(#Nat) over #Option(#Null) in this case + + func order_candid_types_by_height_bfs(rows : Buffer<[InternalCandidTypeNode]>) { + + label while_loop while (rows.size() > 0) { + let candid_values = Buffer.last(rows) else return Prelude.unreachable(); + let buffer = Buffer.Buffer(8); + + var has_compound_type = false; + + for ((index, parent_node) in Itertools.enumerate(candid_values.vals())) { + + switch (parent_node.type_) { + case (#Option(inner_type)) { + has_compound_type := true; + let child_node : InternalCandidTypeNode = { + type_ = inner_type; + height = get_candid_height_value(inner_type); + parent_index = index; + key = null; + }; + + buffer.add(child_node); + }; + case (#Array(vec_types)) { + has_compound_type := true; + + for (vec_type in vec_types.vals()) { + let child_node : InternalCandidTypeNode = { + type_ = vec_type; + height = get_candid_height_value(vec_type); + parent_index = index; + key = null; + }; + + buffer.add(child_node); + }; + + }; + case (#Record(records)) { + + for ((key, field_type) in records.vals()) { + has_compound_type := true; + let child_node : InternalCandidTypeNode = { + type_ = field_type; + height = get_candid_height_value(field_type); + parent_index = index; + key = ?key; + }; + buffer.add(child_node); + }; + }; + case (#Variant(variants)) { + has_compound_type := true; + + for ((key, variant_type) in variants.vals()) { + has_compound_type := true; + let child_node : InternalCandidTypeNode = { + type_ = variant_type; + height = get_candid_height_value(variant_type); + parent_index = index; + key = ?key; + }; + buffer.add(child_node); + }; + }; + case (_) {}; + }; }; - }; - case (#Variant(variants)) { - has_compound_type := true; - - for ((key, variant_type) in variants.vals()) { - has_compound_type := true; - let child_node : InternalCandidTypeNode = { - type_ = variant_type; - height = get_candid_height_value(variant_type); - parent_index = index; - key = ?key; - }; - buffer.add(child_node); + + if (has_compound_type) { + rows.add(Buffer.toArray(buffer)); + } else { + return; }; - }; - case (_) {}; }; - }; - - if (has_compound_type) { - rows.add(Buffer.toArray(buffer)); - } else { - return; - }; }; - }; - func get_renamed_key(renaming_map : Map, key : Text) : Text { - switch (Map.get(renaming_map, thash, key)) { - case (?v) v; - case (_) key; + func get_renamed_key(renaming_map : Map, key : Text) : Text { + switch (Map.get(renaming_map, thash, key)) { + case (?v) v; + case (_) key; + }; }; - }; }; diff --git a/src/Candid/Blob/RepIndyHash.mo b/src/Candid/Blob/RepIndyHash.mo index 2098bca..40f685c 100644 --- a/src/Candid/Blob/RepIndyHash.mo +++ b/src/Candid/Blob/RepIndyHash.mo @@ -18,52 +18,39 @@ import T "../Types"; import Utils "../../Utils"; import Sha256 "mo:sha2/Sha256"; +import ByteUtils "mo:byte-utils"; + module { type Buffer = Buffer.Buffer; let { ReusableBuffer; unsigned_leb128; signed_leb128_64 } = Utils; public func hash(candid_value : T.Candid) : Blob { - let buffer = ReusableBuffer(100); + // let buffer = ReusableBuffer(100); + let buffer = Buffer.Buffer(100); let sha256 = Sha256.Digest(#sha256); candid_hash(buffer, sha256, candid_value); }; func candid_hash( - buffer : Utils.ReusableBuffer, + buffer : Buffer.Buffer, sha256 : Sha256.Digest, candid_value : T.Candid, ) : Blob { switch (candid_value) { case (#Int(n)) signed_leb128_64(buffer, n); case (#Int8(i8)) { - buffer.add(Int8.toNat8(i8)); + ByteUtils.Buffer.LE.addInt8(buffer, i8); }; case (#Int16(i16)) { - let n16 = Int16.toNat16(i16); - buffer.add((n16 & 0xFF) |> Nat16.toNat8(_)); - buffer.add((n16 >> 8) |> Nat16.toNat8(_)); + ByteUtils.Buffer.LE.addInt16(buffer, i16); }; case (#Int32(i32)) { - let n = Int32.toNat32(i32); - - buffer.add((n & 0xFF) |> Nat32.toNat16(_) |> Nat16.toNat8(_)); - buffer.add(((n >> 8) & 0xFF) |> Nat32.toNat16(_) |> Nat16.toNat8(_)); - buffer.add(((n >> 16) & 0xFF) |> Nat32.toNat16(_) |> Nat16.toNat8(_)); - buffer.add((n >> 24) |> Nat32.toNat16(_) |> Nat16.toNat8(_)); + ByteUtils.Buffer.LE.addInt32(buffer, i32); }; case (#Int64(i64)) { - let n = Int64.toNat64(i64); - - buffer.add((n & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - buffer.add(((n >> 8) & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - buffer.add(((n >> 16) & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - buffer.add(((n >> 24) & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - buffer.add(((n >> 32) & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - buffer.add(((n >> 40) & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - buffer.add(((n >> 48) & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - buffer.add((n >> 56) |> Nat64.toNat(_) |> Nat8.fromNat(_)); + ByteUtils.Buffer.LE.addInt64(buffer, i64); }; case (#Nat(n)) unsigned_leb128(buffer, n); @@ -72,30 +59,17 @@ module { buffer.add(n); }; case (#Nat16(n)) { - buffer.add((n & 0xFF) |> Nat16.toNat8(_)); - buffer.add((n >> 8) |> Nat16.toNat8(_)); + ByteUtils.Buffer.LE.addNat16(buffer, n); }; case (#Nat32(n)) { - buffer.add((n & 0xFF) |> Nat32.toNat16(_) |> Nat16.toNat8(_)); - buffer.add(((n >> 8) & 0xFF) |> Nat32.toNat16(_) |> Nat16.toNat8(_)); - buffer.add(((n >> 16) & 0xFF) |> Nat32.toNat16(_) |> Nat16.toNat8(_)); - buffer.add((n >> 24) |> Nat32.toNat16(_) |> Nat16.toNat8(_)); + ByteUtils.Buffer.LE.addNat32(buffer, n); }; case (#Nat64(n)) { - buffer.add((n & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - buffer.add(((n >> 8) & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - buffer.add(((n >> 16) & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - buffer.add(((n >> 24) & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - buffer.add(((n >> 32) & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - buffer.add(((n >> 40) & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - buffer.add(((n >> 48) & 0xFF) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - buffer.add((n >> 56) |> Nat64.toNat(_) |> Nat8.fromNat(_)); - + ByteUtils.Buffer.LE.addNat64(buffer, n); }; case (#Float(f64)) { - // let floatX : FloatX.FloatX = FloatX.fromFloat(f64, #f64); - // FloatX.encode(buffer, floatX, #lsb); + ByteUtils.Buffer.LE.addFloat(buffer, f64); }; case (#Bool(b)) { buffer.add(if (b) (1) else (0)); From 562a5e4a0b0b0810ce53e463230439ed68530d9c Mon Sep 17 00:00:00 2001 From: Tomi Jaga Date: Sun, 3 Aug 2025 04:53:56 -0700 Subject: [PATCH 4/6] update github benchmarks workflow --- .github/workflows/benchmarks.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index fd5851b..7ba9648 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -46,7 +46,6 @@ jobs: npm --yes -g i ic-mops mops i mops toolchain init - mops toolchain use moc latest # set moc path for dfx to use echo "DFX_MOC_PATH=$(mops toolchain bin moc)" >> $GITHUB_ENV @@ -61,16 +60,15 @@ jobs: continue-on-error: true - name: Checkout out the branch with benchmark results - if: github.event_name == 'pull_request' uses: actions/checkout@v4 with: ref: benchmark-results path: .benchmark-results-branch/ + - name: Move Saved Benchmarks - if: github.event_name == 'pull_request' run: mv .benchmark-results-branch/.bench .bench 2>/dev/null || mkdir -p .bench - - name: Benchmarks + - name: Pull Request Benchmarks if: github.event_name == 'pull_request' id: benchmarks run: | From 273dffb2c4067b14e7d3e13884c8caec2e7c1ba4 Mon Sep 17 00:00:00 2001 From: Tomi Jaga Date: Fri, 7 Nov 2025 06:29:06 -0800 Subject: [PATCH 5/6] Support Type Inference on partial data: omitting null fields in json entry --- .bench/serde.bench.json | 102 +++++++++----- bench/serde.bench.mo | 1 - src/Candid/Blob/Encoder.mo | 280 ++++++++++++++++++++++++++++++++++--- tests/Candid.Test.mo | 15 +- tests/JSON.Test.mo | 43 ++++++ 5 files changed, 378 insertions(+), 63 deletions(-) diff --git a/.bench/serde.bench.json b/.bench/serde.bench.json index 8d4258b..f3d5df8 100644 --- a/.bench/serde.bench.json +++ b/.bench/serde.bench.json @@ -1,9 +1,9 @@ { "version": 1, - "moc": "0.14.9", + "moc": "0.14.13", "replica": "dfx", - "replicaVersion": "0.26.0", - "gc": "copying", + "replicaVersion": "0.29.2", + "gc": "generational", "forceGc": true, "results": [ [ @@ -11,14 +11,14 @@ { "rts_stable_memory_size": 0, "stable_memory_size": 0, - "instructions": 308306995, - "rts_memory_size": 0, - "rts_total_allocation": 22892744, - "rts_collector_instructions": 31603911, - "rts_mutator_instructions": -10283, + "instructions": 394461139, + "rts_memory_size": 17563648, + "rts_total_allocation": 27418360, + "rts_collector_instructions": -29134, + "rts_mutator_instructions": -10415, "rts_logical_stable_memory_size": 0, - "rts_heap_size": 1581832, - "rts_reclaimed": 21310912 + "rts_heap_size": 1580216, + "rts_reclaimed": 25838144 } ], [ @@ -26,14 +26,14 @@ { "rts_stable_memory_size": 0, "stable_memory_size": 0, - "instructions": 1152554248, - "rts_memory_size": 38076416, - "rts_total_allocation": 65628396, - "rts_collector_instructions": 6995, - "rts_mutator_instructions": -7896, + "instructions": 1105298555, + "rts_memory_size": 40894464, + "rts_total_allocation": 65569368, + "rts_collector_instructions": -7907, + "rts_mutator_instructions": -8138, "rts_logical_stable_memory_size": 0, "rts_heap_size": 308, - "rts_reclaimed": 65628088 + "rts_reclaimed": 65569060 } ], [ @@ -41,14 +41,14 @@ { "rts_stable_memory_size": 0, "stable_memory_size": 0, - "instructions": 385683071, + "instructions": 225586733, "rts_memory_size": 0, - "rts_total_allocation": 26540744, - "rts_collector_instructions": 6638, - "rts_mutator_instructions": -7896, + "rts_total_allocation": 18405496, + "rts_collector_instructions": -7907, + "rts_mutator_instructions": -8138, "rts_logical_stable_memory_size": 0, "rts_heap_size": 272, - "rts_reclaimed": 26540472 + "rts_reclaimed": 18405224 } ], [ @@ -56,14 +56,14 @@ { "rts_stable_memory_size": 0, "stable_memory_size": 0, - "instructions": 918929773, + "instructions": 891671974, "rts_memory_size": 0, - "rts_total_allocation": 35914440, - "rts_collector_instructions": 6638, - "rts_mutator_instructions": -7896, + "rts_total_allocation": 35602928, + "rts_collector_instructions": -7907, + "rts_mutator_instructions": -8138, "rts_logical_stable_memory_size": 0, "rts_heap_size": 272, - "rts_reclaimed": 35914168 + "rts_reclaimed": 35602656 } ], [ @@ -71,14 +71,14 @@ { "rts_stable_memory_size": 0, "stable_memory_size": 0, - "instructions": 31841278, + "instructions": 31314021, "rts_memory_size": 0, - "rts_total_allocation": 540264, - "rts_collector_instructions": 6638, - "rts_mutator_instructions": -7896, + "rts_total_allocation": 555728, + "rts_collector_instructions": -7907, + "rts_mutator_instructions": -8138, "rts_logical_stable_memory_size": 0, "rts_heap_size": 272, - "rts_reclaimed": 539992 + "rts_reclaimed": 555456 } ], [ @@ -86,14 +86,44 @@ { "rts_stable_memory_size": 0, "stable_memory_size": 0, - "instructions": 10262063, + "instructions": 9061804, + "rts_memory_size": 0, + "rts_total_allocation": 611788, + "rts_collector_instructions": -7907, + "rts_mutator_instructions": -8138, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 272, + "rts_reclaimed": 611516 + } + ], + [ + "Serde: Single Type Serializer:decode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 111955117, + "rts_memory_size": 0, + "rts_total_allocation": 6834008, + "rts_collector_instructions": -7907, + "rts_mutator_instructions": -8138, + "rts_logical_stable_memory_size": 0, + "rts_heap_size": 272, + "rts_reclaimed": 6833736 + } + ], + [ + "Serde: Single Type Serializer:encode()", + { + "rts_stable_memory_size": 0, + "stable_memory_size": 0, + "instructions": 222580666, "rts_memory_size": 0, - "rts_total_allocation": 596508, - "rts_collector_instructions": 6638, - "rts_mutator_instructions": -7896, + "rts_total_allocation": 8413432, + "rts_collector_instructions": -7907, + "rts_mutator_instructions": -8138, "rts_logical_stable_memory_size": 0, "rts_heap_size": 272, - "rts_reclaimed": 596236 + "rts_reclaimed": 8413160 } ] ] diff --git a/bench/serde.bench.mo b/bench/serde.bench.mo index fb9ed64..6790696 100644 --- a/bench/serde.bench.mo +++ b/bench/serde.bench.mo @@ -25,7 +25,6 @@ module { "Serde: One Shot sans type inference", "Motoko (to_candid(), from_candid())", "Serde: Single Type Serializer", - ]); bench.cols([ diff --git a/src/Candid/Blob/Encoder.mo b/src/Candid/Blob/Encoder.mo index 90a78f4..59028f4 100644 --- a/src/Candid/Blob/Encoder.mo +++ b/src/Candid/Blob/Encoder.mo @@ -385,8 +385,26 @@ module { let field_type = record_types[i].1; let field_type_key = get_renamed_key(renaming_map, record_types[i].0); let field_value = switch (Map.get(record_entry_cache, Map.thash, field_type_key)) { - case (?field_value) field_value; - case (_) Debug.trap("unable to find field key in field types: " # debug_show field_type_key # "in " # debug_show record_entries); + case (?field_value) { + // If field type is optional but value isn't, wrap it + switch (field_type, field_value) { + case (#Option(inner_type), val) { + // Check if value is already wrapped in Option + switch (val) { + case (#Option(_) or #Null) val; // Already wrapped + case (_) #Option(val); // Wrap it + }; + }; + case (_) field_value; + }; + }; + case (null) { + // Field is missing - if the type is optional, use #Null + switch (field_type) { + case (#Option(_)) #Null; + case (_) Debug.trap("unable to find field key in field types: " # debug_show field_type_key # "in " # debug_show record_entries); + }; + }; }; ignore encode_value_only( field_type, @@ -1200,7 +1218,6 @@ module { }; case (#Record(record_types) or #Map(record_types), #Record(record_entries) or #Map(record_entries)) { - assert record_types.size() >= record_entries.size(); let is_tuple = check_is_tuple(record_types); @@ -1224,8 +1241,26 @@ module { let field_type_key = get_renamed_key(renaming_map, record_types[i].0); let field_value = switch (Map.get(record_entry_cache, Map.thash, field_type_key)) { - case (?field_value) field_value; - case (_) Debug.trap("unable to find field key in field types: " # debug_show field_type_key # "in " # debug_show record_entries); + case (?field_value) { + // If field type is optional but value isn't, wrap it + switch (field_type, field_value) { + case (#Option(inner_type), val) { + // Check if value is already wrapped in Option + switch (val) { + case (#Option(_) or #Null) val; // Already wrapped + case (_) #Option(val); // Wrap it + }; + }; + case (_) field_value; + }; + }; + case (null) { + // Field is missing - if the type is optional, use #Null + switch (field_type) { + case (#Option(_)) #Null; + case (_) Debug.trap("unable to find field key in field types: " # debug_show field_type_key # "in " # debug_show record_entries); + }; + }; }; let value_type_is_compound = is_compound_type(field_type); @@ -1741,21 +1776,100 @@ module { case (#Array(_)) { let vec_nodes = Iter.toArray(tmp_bottom_iter); - let max = { - var height = 0; - var type_ : CandidType = #Empty; - }; + // Special handling for arrays of records - merge fields across all records + let all_records = Buffer.Buffer<[(Text, CandidType)]>(vec_nodes.size()); + var has_records = false; for (node in vec_nodes.vals()) { - if (max.height < node.height) { - max.height := node.height; - max.type_ := node.type_; + switch (node.type_) { + case (#Record(fields)) { + has_records := true; + all_records.add(fields); + }; + case (_) {}; }; }; + // If we have multiple records, merge their fields + let merged_type = if (has_records and all_records.size() > 1) { + let field_map = Map.new(); // key -> (type, height, count) + var max_height = 0; + + for (record_fields in all_records.vals()) { + for ((field_key, field_type) in record_fields.vals()) { + let field_depth = get_type_depth(field_type); + max_height := Nat.max(max_height, field_depth); + + switch (Map.get(field_map, thash, field_key)) { + case (?existing) { + let (existing_type, existing_height, count) = existing; + + // Choose better type + if (is_better_type(field_type, field_depth, existing_type, existing_height)) { + ignore Map.put(field_map, thash, field_key, (field_type, field_depth, count + 1)); + } else { + ignore Map.put(field_map, thash, field_key, (existing_type, existing_height, count + 1)); + }; + }; + case (null) { + ignore Map.put(field_map, thash, field_key, (field_type, field_depth, 1)); + }; + }; + }; + }; + + // Build merged record type, wrapping optional fields + let merged_fields = Buffer.Buffer<(Text, CandidType)>(Map.size(field_map)); + let total_records = all_records.size(); + + for ((field_key, (field_type, field_height, count)) in Map.entries(field_map)) { + // If field doesn't appear in all records, make it optional + let final_type = if (count < total_records) { + switch (field_type) { + case (#Option(_)) field_type; // Already optional + case (_) #Option(field_type); // Make it optional + }; + } else { + field_type; + }; + merged_fields.add((field_key, final_type)); + }; + + #Record(Buffer.toArray(merged_fields)); + } else { + // Not records or only one record, use normal max selection + let max = { + var height = 0; + var type_ : CandidType = #Empty; + }; + + for (node in vec_nodes.vals()) { + if (max.type_ == #Empty or is_better_type(node.type_, node.height, max.type_, max.height)) { + max.height := node.height; + max.type_ := node.type_; + }; + }; + + max.type_; + }; + + let final_height = if (has_records and all_records.size() > 1) { + calc_height(parent_node.height, get_type_depth(merged_type)); + } else { + let max = { + var height = 0; + }; + for (node in vec_nodes.vals()) { + if (node.height > max.height) { + max.height := node.height; + }; + }; + calc_height(parent_node.height, max.height); + }; + let best_node : CandidTypeNode = { - type_ = #Array(max.type_); - height = calc_height(parent_node.height, max.height); + type_ = #Array(merged_type); + height = final_height; parent_index; key = parent_key; }; @@ -1834,7 +1948,34 @@ module { }; if (variants.size() > 0) { - let full_variant_type : CandidType = #Variant(Buffer.toArray(variants)); + // Merge variant types with the same key, choosing the better (more specific) type + let merged_variants = Buffer.Buffer<(Text, CandidType)>(variants.size()); + let variant_map = Map.new(); // key -> (type, height) + + for ((key, variant_type) in variants.vals()) { + switch (Map.get(variant_map, thash, key)) { + case (?existing) { + let (existing_type, existing_height) = existing; + let variant_depth = get_type_depth(variant_type); + + // Choose the better type + if (is_better_type(variant_type, variant_depth, existing_type, existing_height)) { + ignore Map.put(variant_map, thash, key, (variant_type, variant_depth)); + }; + }; + case (null) { + let variant_depth = get_type_depth(variant_type); + ignore Map.put(variant_map, thash, key, (variant_type, variant_depth)); + }; + }; + }; + + // Convert map back to array + for ((key, (variant_type, _)) in Map.entries(variant_map)) { + merged_variants.add((key, variant_type)); + }; + + let full_variant_type : CandidType = #Variant(Buffer.toArray(merged_variants)); for (index in variant_indexes.vals()) { let prev_node = buffer.get(index); @@ -1864,10 +2005,113 @@ module { }; }; - // for most of the typs we can easily retrieve it from the value, but for an array it becomes a bit tricky - // because of optional values we can have seemingly different types in the array - // for example type [?Nat] with values [null, ?1], for each values will have a inferred type of [#Option(#Null), #Option(#Nat)] + // Calculate the width (number of fields) of a type + func get_type_width(type_ : CandidType) : Nat { + switch (type_) { + case (#Record(fields) or #Map(fields)) fields.size(); + case (#Variant(variants)) variants.size(); + case (#Tuple(tuple_types)) tuple_types.size(); + case (#Array(inner)) 1 + get_type_width(inner); + case (#Option(inner)) 1 + get_type_width(inner); + case (_) 1; + }; + }; + + // Calculate the total depth of a type + func get_type_depth(type_ : CandidType) : Nat { + switch (type_) { + case (#Record(fields) or #Map(fields)) { + var max_depth = 0; + for ((_, field_type) in fields.vals()) { + let depth = get_type_depth(field_type); + if (depth > max_depth) { + max_depth := depth; + }; + }; + 1 + max_depth; + }; + case (#Variant(variants)) { + var max_depth = 0; + for ((_, variant_type) in variants.vals()) { + let depth = get_type_depth(variant_type); + if (depth > max_depth) { + max_depth := depth; + }; + }; + 1 + max_depth; + }; + case (#Tuple(tuple_types)) { + var max_depth = 0; + for (tuple_type in tuple_types.vals()) { + let depth = get_type_depth(tuple_type); + if (depth > max_depth) { + max_depth := depth; + }; + }; + 1 + max_depth; + }; + case (#Array(inner)) 1 + get_type_depth(inner); + case (#Option(inner)) 1 + get_type_depth(inner); + case (#Empty or #Null) 0; + case (_) 1; + }; + }; + + // Compare two types to determine which is "better" (more specific) + // Returns true if type1 is better than type2 + func is_better_type(type1 : CandidType, height1 : Nat, type2 : CandidType, height2 : Nat) : Bool { + // Special case: prefer non-null/non-empty types over null/empty + switch (type1, type2) { + case (#Null or #Empty, #Null or #Empty) return false; // Both are null/empty + case (#Null or #Empty, _) return false; // type2 is better (non-null) + case (_, #Null or #Empty) return true; // type1 is better (non-null) + case (_) {}; // Continue with other comparisons + }; + + // Compare Option types specially + switch (type1, type2) { + case (#Option(inner1), #Option(inner2)) { + // Both are options, compare their inner types + let inner1_depth = get_type_depth(inner1); + let inner2_depth = get_type_depth(inner2); + return is_better_type(inner1, inner1_depth, inner2, inner2_depth); + }; + case (#Option(_), _) { + // type1 is optional, type2 is not + // Generally prefer the non-optional unless type2 is null/empty + return false; + }; + case (_, #Option(_)) { + // type2 is optional, type1 is not + return true; + }; + case (_) {}; // Neither is Option, continue + }; + + // Compare by depth (height) + if (height1 != height2) { + return height1 > height2; + }; + + // If depths are equal, compare by width + let width1 = get_type_width(type1); + let width2 = get_type_width(type2); + + if (width1 != width2) { + return width1 > width2; + }; + + // All equal, no preference + false; + }; + + // for most of the types we can easily retrieve it from the value, but for an array it becomes a bit tricky + // because of optional values we can have different types in the array + // for example type [?Nat] with values [null, ?1], will have a inferred type of [#Option(#Null), #Option(#Nat)] // We need a way to choose #Option(#Nat) over #Option(#Null) in this case + // + // todo: Currently works well when there is a difference in the depth of the types (e.g. #Variant("key1", #Nat) vs #Variant("key2", #Record(...))) + // todo: However, it fails when there is a difference only in the width of the types (e.g. #Variant("key1", #Record(a: #Nat)) vs #Variant("key2", #Record(a: #Nat, b: #Option(#Nat)))) func order_candid_types_by_height_bfs(rows : Buffer<[InternalCandidTypeNode]>) { diff --git a/tests/Candid.Test.mo b/tests/Candid.Test.mo index 283221a..bf4d165 100644 --- a/tests/Candid.Test.mo +++ b/tests/Candid.Test.mo @@ -134,17 +134,17 @@ suite( func() { type User = { name : Text; - age : Nat; + age : ?Nat; }; let user_james = { name = "James"; - age = 23; + age = ?23; }; let user_steven = { name = "Steven"; - age = 32; + age = null; }; type Record = { @@ -160,19 +160,18 @@ suite( let record_blob = to_candid (record); let RecordType : Candid.CandidType = #Record([ - ("first", #Record([("name", #Text), ("age", #Nat)])), - ("second", #Record([("name", #Text), ("age", #Nat)])), + ("first", #Record([("name", #Text), ("age", #Option(#Nat))])), + ("second", #Record([("name", #Text), ("age", #Option(#Nat))])), ]); let #ok(candid) = CandidTestUtils.decode_with_types([RecordType], ["first", "second", "name", "age"], record_blob, null) else return assert false; assert candid == [ #Record([ - ("first", #Record([("age", #Nat(23)), ("name", #Text("James"))])), - ("second", #Record([("age", #Nat(32)), ("name", #Text("Steven"))])), + ("first", #Record([("age", #Option(#Nat(23))), ("name", #Text("James"))])), + ("second", #Record([("age", #Null), ("name", #Text("Steven"))])), ]), ]; - }, ); test( diff --git a/tests/JSON.Test.mo b/tests/JSON.Test.mo index d0aca30..94c68ec 100644 --- a/tests/JSON.Test.mo +++ b/tests/JSON.Test.mo @@ -151,6 +151,49 @@ suite( }; }, ); + + test( + "partial JSON to record", + func() { + + type User = { name : Text; id : Int; email : ?Text }; + + let text = "{\"name\": \"Tomi\", \"id\": 123 }"; + + let #ok(blob) = JSON.fromText(text, null); + let user : ?User = from_candid (blob); + + assert user == ?{ name = "Tomi"; id = 123; email = null }; + + let text2 = "{ \"name\": \"Tomi\", \"id\": 123, \"email\": \"test@gmail.com\" }"; + let #ok(blob2) = JSON.fromText(text2, null); + let user2 : ?User = from_candid (blob2); + assert user2 == ?{ + name = "Tomi"; + id = 123; + email = ?"test@gmail.com"; + }; + + let jsonText = "[{\"name\": \"John\", \"id\": 123}, {\"name\": \"Jane\", \"id\": 456, \"email\": \"jane@gmail.com\"}]"; + + let #ok(blob3) = JSON.fromText(jsonText, null); // you probably want to handle the error case here :) + let users : ?[User] = from_candid (blob3); + Debug.print(debug_show ({ users })); + + assert users == ?[ + { + name = "John"; + id = 123; + email = null; + }, + { + name = "Jane"; + id = 456; + email = ?"jane@gmail.com"; + }, + ]; + }, + ); }, ); From 2df08c8b97941f3f58d48b2020d707e3f0a892e9 Mon Sep 17 00:00:00 2001 From: Tomi Jaga Date: Fri, 7 Nov 2025 06:56:11 -0800 Subject: [PATCH 6/6] Update dependencies --- bench/serde.bench.mo | 12 ++--- bench/types.bench.mo | 40 ++++++++-------- mops.toml | 24 +++++----- src/CBOR/lib.mo | 20 ++++---- src/Candid/Blob/CandidUtils.mo | 18 ++++---- src/Candid/Blob/Decoder.mo | 40 ++++++++-------- src/Candid/Blob/Encoder.mo | 48 +++++++++---------- src/Candid/Blob/RepIndyHash.mo | 30 ++++++------ src/Candid/Blob/TypedSerializer.mo | 28 ++++++------ src/Candid/ICRC3Value.mo | 22 ++++----- src/Candid/Text/Parser/Array.mo | 2 +- src/Candid/Text/Parser/Blob.mo | 6 +-- src/Candid/Text/Parser/Bool.mo | 2 +- src/Candid/Text/Parser/Common.mo | 10 ++-- src/Candid/Text/Parser/Float.mo | 4 +- src/Candid/Text/Parser/Int.mo | 4 +- src/Candid/Text/Parser/IntX.mo | 12 ++--- src/Candid/Text/Parser/Nat.mo | 6 +-- src/Candid/Text/Parser/NatX.mo | 12 ++--- src/Candid/Text/Parser/Option.mo | 2 +- src/Candid/Text/Parser/Principal.mo | 4 +- src/Candid/Text/Parser/Record.mo | 4 +- src/Candid/Text/Parser/Text.mo | 8 ++-- src/Candid/Text/Parser/Variant.mo | 2 +- src/Candid/Text/Parser/lib.mo | 10 ++-- src/Candid/Text/ToText.mo | 10 ++-- src/Candid/lib.mo | 4 +- src/JSON/FromText.mo | 8 ++-- src/JSON/ToText.mo | 6 +-- src/UrlEncoded/FromText.mo | 22 ++++----- src/UrlEncoded/Parser.mo | 10 ++-- src/UrlEncoded/ToText.mo | 18 ++++---- src/Utils.mo | 54 +++++++++++----------- src/libs/motoko_candid/utils.mo | 56 +++++++++++------------ submodules/json.mo/README.md | 4 +- tests/CBOR.Test.mo | 12 ++--- tests/Candid.Large.test.mo | 12 ++--- tests/Candid.Test.mo | 12 ++--- tests/CandidTestUtils.mo | 14 +++--- tests/ICRC3.Test.mo | 12 ++--- tests/JSON.Test.mo | 8 ++-- tests/PrimitiveType.Test.mo | 10 ++-- tests/RepIndyHash.Test.mo | 4 +- tests/Stress.test.mo | 14 +++--- tests/UrlEncoded.Test.mo | 4 +- tests/one-shot.backward-reference.test.mo | 26 +++++------ tests/one-shot.forward-reference.mo | 28 ++++++------ tests/test_template.md | 4 +- 48 files changed, 361 insertions(+), 361 deletions(-) diff --git a/bench/serde.bench.mo b/bench/serde.bench.mo index 6790696..b6c60b6 100644 --- a/bench/serde.bench.mo +++ b/bench/serde.bench.mo @@ -1,9 +1,9 @@ -import Iter "mo:base@0.14.14/Iter"; -import Debug "mo:base@0.14.14/Debug"; -import Prelude "mo:base@0.14.14/Prelude"; -import Text "mo:base@0.14.14/Text"; -import Char "mo:base@0.14.14/Char"; -import Buffer "mo:base@0.14.14/Buffer"; +import Iter "mo:base@0.16.0/Iter"; +import Debug "mo:base@0.16.0/Debug"; +import Prelude "mo:base@0.16.0/Prelude"; +import Text "mo:base@0.16.0/Text"; +import Char "mo:base@0.16.0/Char"; +import Buffer "mo:base@0.16.0/Buffer"; import Bench "mo:bench"; import Fuzz "mo:fuzz"; diff --git a/bench/types.bench.mo b/bench/types.bench.mo index 75df188..8b51c6f 100644 --- a/bench/types.bench.mo +++ b/bench/types.bench.mo @@ -1,23 +1,23 @@ -import Iter "mo:base@0.14.14/Iter"; -import Debug "mo:base@0.14.14/Debug"; -import Prelude "mo:base@0.14.14/Prelude"; -import Text "mo:base@0.14.14/Text"; -import Char "mo:base@0.14.14/Char"; -import Buffer "mo:base@0.14.14/Buffer"; -import Array "mo:base@0.14.14/Array"; -import Blob "mo:base@0.14.14/Blob"; -import Principal "mo:base@0.14.14/Principal"; -import Int "mo:base@0.14.14/Int"; -import Int8 "mo:base@0.14.14/Int8"; -import Int16 "mo:base@0.14.14/Int16"; -import Int32 "mo:base@0.14.14/Int32"; -import Int64 "mo:base@0.14.14/Int64"; -import Nat "mo:base@0.14.14/Nat"; -import Nat8 "mo:base@0.14.14/Nat8"; -import Nat16 "mo:base@0.14.14/Nat16"; -import Nat32 "mo:base@0.14.14/Nat32"; -import Nat64 "mo:base@0.14.14/Nat64"; -import Float "mo:base@0.14.14/Float"; +import Iter "mo:base@0.16.0/Iter"; +import Debug "mo:base@0.16.0/Debug"; +import Prelude "mo:base@0.16.0/Prelude"; +import Text "mo:base@0.16.0/Text"; +import Char "mo:base@0.16.0/Char"; +import Buffer "mo:base@0.16.0/Buffer"; +import Array "mo:base@0.16.0/Array"; +import Blob "mo:base@0.16.0/Blob"; +import Principal "mo:base@0.16.0/Principal"; +import Int "mo:base@0.16.0/Int"; +import Int8 "mo:base@0.16.0/Int8"; +import Int16 "mo:base@0.16.0/Int16"; +import Int32 "mo:base@0.16.0/Int32"; +import Int64 "mo:base@0.16.0/Int64"; +import Nat "mo:base@0.16.0/Nat"; +import Nat8 "mo:base@0.16.0/Nat8"; +import Nat16 "mo:base@0.16.0/Nat16"; +import Nat32 "mo:base@0.16.0/Nat32"; +import Nat64 "mo:base@0.16.0/Nat64"; +import Float "mo:base@0.16.0/Float"; import Bench "mo:bench"; import Fuzz "mo:fuzz"; diff --git a/mops.toml b/mops.toml index 9fe02fc..e7b0a70 100644 --- a/mops.toml +++ b/mops.toml @@ -3,26 +3,26 @@ name = "serde" version = "3.3.3" description = "A serialisation and deserialisation library for Motoko." repository = "https://github.com/NatLabs/serde" -keywords = ["json", "candid", "cbor", "urlencoded", "serialization"] +keywords = [ "json", "candid", "cbor", "urlencoded", "serialization" ] license = "MIT" [dependencies] -'base@0.14.14' = "0.14.14" -'itertools@0.2.2' = "0.2.2" -'xtended-numbers@2.0.0' = "2.0.0" -'cbor@4.0.0' = "4.0.0" -'map@9.0.1' = "9.0.1" -'sha2@0.1.6' = "0.1.6" -'byte-utils@0.1.2' = "0.1.2" +"base@0.16.0" = "0.16.0" +"itertools@0.2.2" = "0.2.2" +"xtended-numbers@2.0.0" = "2.0.0" +"cbor@4.0.0" = "4.0.0" +"map@9.0.1" = "9.0.1" +"sha2@0.1.6" = "0.1.6" +"byte-utils@0.1.2" = "0.1.2" "base@0.7.3" = "0.7.3" [dev-dependencies] test = "2.1.1" -'fuzz' = "1.0.0" -'rep-indy-hash' = "0.1.1" -'candid@2.0.0' = "2.0.0" +fuzz = "1.0.0" +rep-indy-hash = "0.1.1" +candid = "2.0.0" bench = "1.0.0" [toolchain] wasmtime = "33.0.1" -moc = "0.14.13" +moc = "0.16.2" diff --git a/src/CBOR/lib.mo b/src/CBOR/lib.mo index 2d4b687..a88b396 100644 --- a/src/CBOR/lib.mo +++ b/src/CBOR/lib.mo @@ -1,13 +1,13 @@ -import Buffer "mo:base@0.14.14/Buffer"; -import Blob "mo:base@0.14.14/Blob"; -import Int8 "mo:base@0.14.14/Int8"; -import Int16 "mo:base@0.14.14/Int16"; -import Int32 "mo:base@0.14.14/Int32"; -import Int64 "mo:base@0.14.14/Int64"; -import Option "mo:base@0.14.14/Option"; -import Nat64 "mo:base@0.14.14/Nat64"; -import Result "mo:base@0.14.14/Result"; -import Principal "mo:base@0.14.14/Principal"; +import Buffer "mo:base@0.16.0/Buffer"; +import Blob "mo:base@0.16.0/Blob"; +import Int8 "mo:base@0.16.0/Int8"; +import Int16 "mo:base@0.16.0/Int16"; +import Int32 "mo:base@0.16.0/Int32"; +import Int64 "mo:base@0.16.0/Int64"; +import Option "mo:base@0.16.0/Option"; +import Nat64 "mo:base@0.16.0/Nat64"; +import Result "mo:base@0.16.0/Result"; +import Principal "mo:base@0.16.0/Principal"; import CBOR_Types "mo:cbor@4.0.0/Types"; import CBOR_Encoder "mo:cbor@4.0.0/Encoder"; diff --git a/src/Candid/Blob/CandidUtils.mo b/src/Candid/Blob/CandidUtils.mo index 6b66d73..5f77531 100644 --- a/src/Candid/Blob/CandidUtils.mo +++ b/src/Candid/Blob/CandidUtils.mo @@ -1,17 +1,17 @@ -import Array "mo:base@0.14.14/Array"; -import Buffer "mo:base@0.14.14/Buffer"; -import Result "mo:base@0.14.14/Result"; -import Nat32 "mo:base@0.14.14/Nat32"; -import Nat "mo:base@0.14.14/Nat"; -import Iter "mo:base@0.14.14/Iter"; -import Text "mo:base@0.14.14/Text"; -import Order "mo:base@0.14.14/Order"; +import Array "mo:base@0.16.0/Array"; +import Buffer "mo:base@0.16.0/Buffer"; +import Result "mo:base@0.16.0/Result"; +import Nat32 "mo:base@0.16.0/Nat32"; +import Nat "mo:base@0.16.0/Nat"; +import Iter "mo:base@0.16.0/Iter"; +import Text "mo:base@0.16.0/Text"; +import Order "mo:base@0.16.0/Order"; import Itertools "mo:itertools@0.2.2/Iter"; import Map "mo:map@9.0.1/Map"; import T "../Types"; -import TrieMap "mo:base@0.14.14/TrieMap"; +import TrieMap "mo:base@0.16.0/TrieMap"; import Utils "../../Utils"; module { diff --git a/src/Candid/Blob/Decoder.mo b/src/Candid/Blob/Decoder.mo index 975630e..dc5dbd9 100644 --- a/src/Candid/Blob/Decoder.mo +++ b/src/Candid/Blob/Decoder.mo @@ -1,23 +1,23 @@ -import Array "mo:base@0.14.14/Array"; -import Blob "mo:base@0.14.14/Blob"; -import Buffer "mo:base@0.14.14/Buffer"; -import Debug "mo:base@0.14.14/Debug"; -import Result "mo:base@0.14.14/Result"; -import Nat64 "mo:base@0.14.14/Nat64"; -import Int8 "mo:base@0.14.14/Int8"; -import Int32 "mo:base@0.14.14/Int32"; -import Nat8 "mo:base@0.14.14/Nat8"; -import Nat32 "mo:base@0.14.14/Nat32"; -import Int64 "mo:base@0.14.14/Int64"; -import Nat "mo:base@0.14.14/Nat"; -import Int "mo:base@0.14.14/Int"; -import Iter "mo:base@0.14.14/Iter"; -import Principal "mo:base@0.14.14/Principal"; -import Text "mo:base@0.14.14/Text"; -import Order "mo:base@0.14.14/Order"; -import Int16 "mo:base@0.14.14/Int16"; -import TrieMap "mo:base@0.14.14/TrieMap"; -import Option "mo:base@0.14.14/Option"; +import Array "mo:base@0.16.0/Array"; +import Blob "mo:base@0.16.0/Blob"; +import Buffer "mo:base@0.16.0/Buffer"; +import Debug "mo:base@0.16.0/Debug"; +import Result "mo:base@0.16.0/Result"; +import Nat64 "mo:base@0.16.0/Nat64"; +import Int8 "mo:base@0.16.0/Int8"; +import Int32 "mo:base@0.16.0/Int32"; +import Nat8 "mo:base@0.16.0/Nat8"; +import Nat32 "mo:base@0.16.0/Nat32"; +import Int64 "mo:base@0.16.0/Int64"; +import Nat "mo:base@0.16.0/Nat"; +import Int "mo:base@0.16.0/Int"; +import Iter "mo:base@0.16.0/Iter"; +import Principal "mo:base@0.16.0/Principal"; +import Text "mo:base@0.16.0/Text"; +import Order "mo:base@0.16.0/Order"; +import Int16 "mo:base@0.16.0/Int16"; +import TrieMap "mo:base@0.16.0/TrieMap"; +import Option "mo:base@0.16.0/Option"; import Map "mo:map@9.0.1/Map"; import Set "mo:map@9.0.1/Set"; diff --git a/src/Candid/Blob/Encoder.mo b/src/Candid/Blob/Encoder.mo index 59028f4..354bd90 100644 --- a/src/Candid/Blob/Encoder.mo +++ b/src/Candid/Blob/Encoder.mo @@ -1,27 +1,27 @@ -import Array "mo:base@0.14.14/Array"; -import Blob "mo:base@0.14.14/Blob"; -import Buffer "mo:base@0.14.14/Buffer"; +import Array "mo:base@0.16.0/Array"; +import Blob "mo:base@0.16.0/Blob"; +import Buffer "mo:base@0.16.0/Buffer"; import B "mo:buffer"; -import Debug "mo:base@0.14.14/Debug"; -import Result "mo:base@0.14.14/Result"; -import Nat64 "mo:base@0.14.14/Nat64"; -import Int8 "mo:base@0.14.14/Int8"; -import Int32 "mo:base@0.14.14/Int32"; -import Nat8 "mo:base@0.14.14/Nat8"; -import Nat32 "mo:base@0.14.14/Nat32"; -import Nat16 "mo:base@0.14.14/Nat16"; -import Int64 "mo:base@0.14.14/Int64"; -import Nat "mo:base@0.14.14/Nat"; -import Int "mo:base@0.14.14/Int"; -import Iter "mo:base@0.14.14/Iter"; -import Prelude "mo:base@0.14.14/Prelude"; -import Principal "mo:base@0.14.14/Principal"; -import Text "mo:base@0.14.14/Text"; -import Order "mo:base@0.14.14/Order"; -import Option "mo:base@0.14.14/Option"; -import Func "mo:base@0.14.14/Func"; -import Char "mo:base@0.14.14/Char"; -import Int16 "mo:base@0.14.14/Int16"; +import Debug "mo:base@0.16.0/Debug"; +import Result "mo:base@0.16.0/Result"; +import Nat64 "mo:base@0.16.0/Nat64"; +import Int8 "mo:base@0.16.0/Int8"; +import Int32 "mo:base@0.16.0/Int32"; +import Nat8 "mo:base@0.16.0/Nat8"; +import Nat32 "mo:base@0.16.0/Nat32"; +import Nat16 "mo:base@0.16.0/Nat16"; +import Int64 "mo:base@0.16.0/Int64"; +import Nat "mo:base@0.16.0/Nat"; +import Int "mo:base@0.16.0/Int"; +import Iter "mo:base@0.16.0/Iter"; +import Prelude "mo:base@0.16.0/Prelude"; +import Principal "mo:base@0.16.0/Principal"; +import Text "mo:base@0.16.0/Text"; +import Order "mo:base@0.16.0/Order"; +import Option "mo:base@0.16.0/Option"; +import Func "mo:base@0.16.0/Func"; +import Char "mo:base@0.16.0/Char"; +import Int16 "mo:base@0.16.0/Int16"; import Itertools "mo:itertools@0.2.2/Iter"; import PeekableIter "mo:itertools@0.2.2/PeekableIter"; @@ -29,7 +29,7 @@ import Map "mo:map@9.0.1/Map"; import ByteUtils "mo:byte-utils@0.1.2"; import T "../Types"; -import TrieMap "mo:base@0.14.14/TrieMap"; +import TrieMap "mo:base@0.16.0/TrieMap"; import Utils "../../Utils"; import CandidUtils "CandidUtils"; diff --git a/src/Candid/Blob/RepIndyHash.mo b/src/Candid/Blob/RepIndyHash.mo index f9ee443..552e189 100644 --- a/src/Candid/Blob/RepIndyHash.mo +++ b/src/Candid/Blob/RepIndyHash.mo @@ -1,18 +1,18 @@ -import Array "mo:base@0.14.14/Array"; -import Blob "mo:base@0.14.14/Blob"; -import Buffer "mo:base@0.14.14/Buffer"; -import Debug "mo:base@0.14.14/Debug"; -import Nat64 "mo:base@0.14.14/Nat64"; -import Int8 "mo:base@0.14.14/Int8"; -import Int32 "mo:base@0.14.14/Int32"; -import Nat8 "mo:base@0.14.14/Nat8"; -import Nat32 "mo:base@0.14.14/Nat32"; -import Nat16 "mo:base@0.14.14/Nat16"; -import Int64 "mo:base@0.14.14/Int64"; -import Nat "mo:base@0.14.14/Nat"; -import Principal "mo:base@0.14.14/Principal"; -import Text "mo:base@0.14.14/Text"; -import Int16 "mo:base@0.14.14/Int16"; +import Array "mo:base@0.16.0/Array"; +import Blob "mo:base@0.16.0/Blob"; +import Buffer "mo:base@0.16.0/Buffer"; +import Debug "mo:base@0.16.0/Debug"; +import Nat64 "mo:base@0.16.0/Nat64"; +import Int8 "mo:base@0.16.0/Int8"; +import Int32 "mo:base@0.16.0/Int32"; +import Nat8 "mo:base@0.16.0/Nat8"; +import Nat32 "mo:base@0.16.0/Nat32"; +import Nat16 "mo:base@0.16.0/Nat16"; +import Int64 "mo:base@0.16.0/Int64"; +import Nat "mo:base@0.16.0/Nat"; +import Principal "mo:base@0.16.0/Principal"; +import Text "mo:base@0.16.0/Text"; +import Int16 "mo:base@0.16.0/Int16"; import T "../Types"; import Utils "../../Utils"; diff --git a/src/Candid/Blob/TypedSerializer.mo b/src/Candid/Blob/TypedSerializer.mo index 8dc788b..5a01a2c 100644 --- a/src/Candid/Blob/TypedSerializer.mo +++ b/src/Candid/Blob/TypedSerializer.mo @@ -1,17 +1,17 @@ -import Array "mo:base@0.14.14/Array"; -import Blob "mo:base@0.14.14/Blob"; -import Buffer "mo:base@0.14.14/Buffer"; -import Result "mo:base@0.14.14/Result"; -import Nat64 "mo:base@0.14.14/Nat64"; -import Nat8 "mo:base@0.14.14/Nat8"; -import Nat32 "mo:base@0.14.14/Nat32"; -import Nat "mo:base@0.14.14/Nat"; -import Iter "mo:base@0.14.14/Iter"; -import Text "mo:base@0.14.14/Text"; -import Order "mo:base@0.14.14/Order"; -import TrieMap "mo:base@0.14.14/TrieMap"; -import Option "mo:base@0.14.14/Option"; -import Debug "mo:base@0.14.14/Debug"; +import Array "mo:base@0.16.0/Array"; +import Blob "mo:base@0.16.0/Blob"; +import Buffer "mo:base@0.16.0/Buffer"; +import Result "mo:base@0.16.0/Result"; +import Nat64 "mo:base@0.16.0/Nat64"; +import Nat8 "mo:base@0.16.0/Nat8"; +import Nat32 "mo:base@0.16.0/Nat32"; +import Nat "mo:base@0.16.0/Nat"; +import Iter "mo:base@0.16.0/Iter"; +import Text "mo:base@0.16.0/Text"; +import Order "mo:base@0.16.0/Order"; +import TrieMap "mo:base@0.16.0/TrieMap"; +import Option "mo:base@0.16.0/Option"; +import Debug "mo:base@0.16.0/Debug"; import Map "mo:map@9.0.1/Map"; import Set "mo:map@9.0.1/Set"; diff --git a/src/Candid/ICRC3Value.mo b/src/Candid/ICRC3Value.mo index a34046a..9364a71 100644 --- a/src/Candid/ICRC3Value.mo +++ b/src/Candid/ICRC3Value.mo @@ -1,14 +1,14 @@ -import Array "mo:base@0.14.14/Array"; -import Nat8 "mo:base@0.14.14/Nat8"; -import Nat16 "mo:base@0.14.14/Nat16"; -import Nat64 "mo:base@0.14.14/Nat64"; -import Nat32 "mo:base@0.14.14/Nat32"; -import Int8 "mo:base@0.14.14/Int8"; -import Int16 "mo:base@0.14.14/Int16"; -import Int64 "mo:base@0.14.14/Int64"; -import Int32 "mo:base@0.14.14/Int32"; -import Debug "mo:base@0.14.14/Debug"; -import Principal "mo:base@0.14.14/Principal"; +import Array "mo:base@0.16.0/Array"; +import Nat8 "mo:base@0.16.0/Nat8"; +import Nat16 "mo:base@0.16.0/Nat16"; +import Nat64 "mo:base@0.16.0/Nat64"; +import Nat32 "mo:base@0.16.0/Nat32"; +import Int8 "mo:base@0.16.0/Int8"; +import Int16 "mo:base@0.16.0/Int16"; +import Int64 "mo:base@0.16.0/Int64"; +import Int32 "mo:base@0.16.0/Int32"; +import Debug "mo:base@0.16.0/Debug"; +import Principal "mo:base@0.16.0/Principal"; import T "Types"; diff --git a/src/Candid/Text/Parser/Array.mo b/src/Candid/Text/Parser/Array.mo index d90b4bf..6a8b08a 100644 --- a/src/Candid/Text/Parser/Array.mo +++ b/src/Candid/Text/Parser/Array.mo @@ -1,4 +1,4 @@ -import List "mo:base@0.14.14/List"; +import List "mo:base@0.16.0/List"; import C "../../../../submodules/parser-combinators.mo/src/Combinators"; import P "../../../../submodules/parser-combinators.mo/src/Parser"; diff --git a/src/Candid/Text/Parser/Blob.mo b/src/Candid/Text/Parser/Blob.mo index fe6328d..31222e3 100644 --- a/src/Candid/Text/Parser/Blob.mo +++ b/src/Candid/Text/Parser/Blob.mo @@ -1,6 +1,6 @@ -import Blob "mo:base@0.14.14/Blob"; -import Iter "mo:base@0.14.14/Iter"; -import List "mo:base@0.14.14/List"; +import Blob "mo:base@0.16.0/Blob"; +import Iter "mo:base@0.16.0/Iter"; +import List "mo:base@0.16.0/List"; import C "../../../../submodules/parser-combinators.mo/src/Combinators"; import P "../../../../submodules/parser-combinators.mo/src/Parser"; diff --git a/src/Candid/Text/Parser/Bool.mo b/src/Candid/Text/Parser/Bool.mo index ef9d298..938f463 100644 --- a/src/Candid/Text/Parser/Bool.mo +++ b/src/Candid/Text/Parser/Bool.mo @@ -1,4 +1,4 @@ -import List "mo:base@0.14.14/List"; +import List "mo:base@0.16.0/List"; import C "../../../../submodules/parser-combinators.mo/src/Combinators"; import P "../../../../submodules/parser-combinators.mo/src/Parser"; diff --git a/src/Candid/Text/Parser/Common.mo b/src/Candid/Text/Parser/Common.mo index 7cdda43..d1649de 100644 --- a/src/Candid/Text/Parser/Common.mo +++ b/src/Candid/Text/Parser/Common.mo @@ -1,8 +1,8 @@ -import Char "mo:base@0.14.14/Char"; -import Iter "mo:base@0.14.14/Iter"; -import List "mo:base@0.14.14/List"; -import Nat32 "mo:base@0.14.14/Nat32"; -import Text "mo:base@0.14.14/Text"; +import Char "mo:base@0.16.0/Char"; +import Iter "mo:base@0.16.0/Iter"; +import List "mo:base@0.16.0/List"; +import Nat32 "mo:base@0.16.0/Nat32"; +import Text "mo:base@0.16.0/Text"; import C "../../../../submodules/parser-combinators.mo/src/Combinators"; import P "../../../../submodules/parser-combinators.mo/src/Parser"; diff --git a/src/Candid/Text/Parser/Float.mo b/src/Candid/Text/Parser/Float.mo index 1f01818..948d5b7 100644 --- a/src/Candid/Text/Parser/Float.mo +++ b/src/Candid/Text/Parser/Float.mo @@ -1,5 +1,5 @@ -import Float "mo:base@0.14.14/Float"; -import List "mo:base@0.14.14/List"; +import Float "mo:base@0.16.0/Float"; +import List "mo:base@0.16.0/List"; import C "../../../../submodules/parser-combinators.mo/src/Combinators"; import P "../../../../submodules/parser-combinators.mo/src/Parser"; diff --git a/src/Candid/Text/Parser/Int.mo b/src/Candid/Text/Parser/Int.mo index 8e11752..54b3fba 100644 --- a/src/Candid/Text/Parser/Int.mo +++ b/src/Candid/Text/Parser/Int.mo @@ -1,5 +1,5 @@ -import Int "mo:base@0.14.14/Int"; -import List "mo:base@0.14.14/List"; +import Int "mo:base@0.16.0/Int"; +import List "mo:base@0.16.0/List"; import C "../../../../submodules/parser-combinators.mo/src/Combinators"; import P "../../../../submodules/parser-combinators.mo/src/Parser"; diff --git a/src/Candid/Text/Parser/IntX.mo b/src/Candid/Text/Parser/IntX.mo index 0ac3a78..0d14469 100644 --- a/src/Candid/Text/Parser/IntX.mo +++ b/src/Candid/Text/Parser/IntX.mo @@ -1,9 +1,9 @@ -import Debug "mo:base@0.14.14/Debug"; -import List "mo:base@0.14.14/List"; -import Int8 "mo:base@0.14.14/Int8"; -import Int16 "mo:base@0.14.14/Int16"; -import Int32 "mo:base@0.14.14/Int32"; -import Int64 "mo:base@0.14.14/Int64"; +import Debug "mo:base@0.16.0/Debug"; +import List "mo:base@0.16.0/List"; +import Int8 "mo:base@0.16.0/Int8"; +import Int16 "mo:base@0.16.0/Int16"; +import Int32 "mo:base@0.16.0/Int32"; +import Int64 "mo:base@0.16.0/Int64"; import C "../../../../submodules/parser-combinators.mo/src/Combinators"; import P "../../../../submodules/parser-combinators.mo/src/Parser"; diff --git a/src/Candid/Text/Parser/Nat.mo b/src/Candid/Text/Parser/Nat.mo index 32653a2..6df94a2 100644 --- a/src/Candid/Text/Parser/Nat.mo +++ b/src/Candid/Text/Parser/Nat.mo @@ -1,6 +1,6 @@ -import Iter "mo:base@0.14.14/Iter"; -import List "mo:base@0.14.14/List"; -import Nat64 "mo:base@0.14.14/Nat64"; +import Iter "mo:base@0.16.0/Iter"; +import List "mo:base@0.16.0/List"; +import Nat64 "mo:base@0.16.0/Nat64"; import C "../../../../submodules/parser-combinators.mo/src/Combinators"; import P "../../../../submodules/parser-combinators.mo/src/Parser"; diff --git a/src/Candid/Text/Parser/NatX.mo b/src/Candid/Text/Parser/NatX.mo index 26b1e4d..a1a0f41 100644 --- a/src/Candid/Text/Parser/NatX.mo +++ b/src/Candid/Text/Parser/NatX.mo @@ -1,9 +1,9 @@ -import Debug "mo:base@0.14.14/Debug"; -import List "mo:base@0.14.14/List"; -import Nat8 "mo:base@0.14.14/Nat8"; -import Nat16 "mo:base@0.14.14/Nat16"; -import Nat32 "mo:base@0.14.14/Nat32"; -import Nat64 "mo:base@0.14.14/Nat64"; +import Debug "mo:base@0.16.0/Debug"; +import List "mo:base@0.16.0/List"; +import Nat8 "mo:base@0.16.0/Nat8"; +import Nat16 "mo:base@0.16.0/Nat16"; +import Nat32 "mo:base@0.16.0/Nat32"; +import Nat64 "mo:base@0.16.0/Nat64"; import C "../../../../submodules/parser-combinators.mo/src/Combinators"; import P "../../../../submodules/parser-combinators.mo/src/Parser"; diff --git a/src/Candid/Text/Parser/Option.mo b/src/Candid/Text/Parser/Option.mo index 5d21de2..cf3cc94 100644 --- a/src/Candid/Text/Parser/Option.mo +++ b/src/Candid/Text/Parser/Option.mo @@ -1,4 +1,4 @@ -import List "mo:base@0.14.14/List"; +import List "mo:base@0.16.0/List"; import C "../../../../submodules/parser-combinators.mo/src/Combinators"; import P "../../../../submodules/parser-combinators.mo/src/Parser"; diff --git a/src/Candid/Text/Parser/Principal.mo b/src/Candid/Text/Parser/Principal.mo index 743531c..2dbf0ee 100644 --- a/src/Candid/Text/Parser/Principal.mo +++ b/src/Candid/Text/Parser/Principal.mo @@ -1,5 +1,5 @@ -import List "mo:base@0.14.14/List"; -import Principal "mo:base@0.14.14/Principal"; +import List "mo:base@0.16.0/List"; +import Principal "mo:base@0.16.0/Principal"; import C "../../../../submodules/parser-combinators.mo/src/Combinators"; import P "../../../../submodules/parser-combinators.mo/src/Parser"; diff --git a/src/Candid/Text/Parser/Record.mo b/src/Candid/Text/Parser/Record.mo index 6a0443f..bae290d 100644 --- a/src/Candid/Text/Parser/Record.mo +++ b/src/Candid/Text/Parser/Record.mo @@ -1,5 +1,5 @@ -import Iter "mo:base@0.14.14/Iter"; -import List "mo:base@0.14.14/List"; +import Iter "mo:base@0.16.0/Iter"; +import List "mo:base@0.16.0/List"; import C "../../../../submodules/parser-combinators.mo/src/Combinators"; import P "../../../../submodules/parser-combinators.mo/src/Parser"; diff --git a/src/Candid/Text/Parser/Text.mo b/src/Candid/Text/Parser/Text.mo index bc7314d..58e96bb 100644 --- a/src/Candid/Text/Parser/Text.mo +++ b/src/Candid/Text/Parser/Text.mo @@ -1,7 +1,7 @@ -import Char "mo:base@0.14.14/Char"; -import Iter "mo:base@0.14.14/Iter"; -import List "mo:base@0.14.14/List"; -import Text "mo:base@0.14.14/Text"; +import Char "mo:base@0.16.0/Char"; +import Iter "mo:base@0.16.0/Iter"; +import List "mo:base@0.16.0/List"; +import Text "mo:base@0.16.0/Text"; import C "../../../../submodules/parser-combinators.mo/src/Combinators"; import P "../../../../submodules/parser-combinators.mo/src/Parser"; diff --git a/src/Candid/Text/Parser/Variant.mo b/src/Candid/Text/Parser/Variant.mo index 73ba814..bc9195a 100644 --- a/src/Candid/Text/Parser/Variant.mo +++ b/src/Candid/Text/Parser/Variant.mo @@ -1,4 +1,4 @@ -import List "mo:base@0.14.14/List"; +import List "mo:base@0.16.0/List"; import C "../../../../submodules/parser-combinators.mo/src/Combinators"; import P "../../../../submodules/parser-combinators.mo/src/Parser"; diff --git a/src/Candid/Text/Parser/lib.mo b/src/Candid/Text/Parser/lib.mo index 9f03c07..ae904e9 100644 --- a/src/Candid/Text/Parser/lib.mo +++ b/src/Candid/Text/Parser/lib.mo @@ -1,8 +1,8 @@ -import Char "mo:base@0.14.14/Char"; -import Debug "mo:base@0.14.14/Debug"; -import Iter "mo:base@0.14.14/Iter"; -import List "mo:base@0.14.14/List"; -import TrieMap "mo:base@0.14.14/TrieMap"; +import Char "mo:base@0.16.0/Char"; +import Debug "mo:base@0.16.0/Debug"; +import Iter "mo:base@0.16.0/Iter"; +import List "mo:base@0.16.0/List"; +import TrieMap "mo:base@0.16.0/TrieMap"; import C "../../../../submodules/parser-combinators.mo/src/Combinators"; import P "../../../../submodules/parser-combinators.mo/src/Parser"; diff --git a/src/Candid/Text/ToText.mo b/src/Candid/Text/ToText.mo index 3563df2..f945c1a 100644 --- a/src/Candid/Text/ToText.mo +++ b/src/Candid/Text/ToText.mo @@ -1,8 +1,8 @@ -import Float "mo:base@0.14.14/Float"; -import Array "mo:base@0.14.14/Array"; -import Text "mo:base@0.14.14/Text"; -import Principal "mo:base@0.14.14/Principal"; -import TrieMap "mo:base@0.14.14/TrieMap"; +import Float "mo:base@0.16.0/Float"; +import Array "mo:base@0.16.0/Array"; +import Text "mo:base@0.16.0/Text"; +import Principal "mo:base@0.16.0/Principal"; +import TrieMap "mo:base@0.16.0/TrieMap"; import Itertools "mo:itertools@0.2.2/Iter"; diff --git a/src/Candid/lib.mo b/src/Candid/lib.mo index e3dea6d..8e95e55 100644 --- a/src/Candid/lib.mo +++ b/src/Candid/lib.mo @@ -1,7 +1,7 @@ /// A representation of the Candid format with variants for all possible types. -import Array "mo:base@0.14.14/Array"; -import Text "mo:base@0.14.14/Text"; +import Array "mo:base@0.16.0/Array"; +import Text "mo:base@0.16.0/Text"; import Map "mo:map@9.0.1/Map"; import CandidEncoder "Blob/Encoder"; diff --git a/src/JSON/FromText.mo b/src/JSON/FromText.mo index d1dba35..815821c 100644 --- a/src/JSON/FromText.mo +++ b/src/JSON/FromText.mo @@ -1,7 +1,7 @@ -import Array "mo:base@0.14.14/Array"; -import Result "mo:base@0.14.14/Result"; -import Text "mo:base@0.14.14/Text"; -import Int "mo:base@0.14.14/Int"; +import Array "mo:base@0.16.0/Array"; +import Result "mo:base@0.16.0/Result"; +import Text "mo:base@0.16.0/Text"; +import Int "mo:base@0.16.0/Int"; import JSON "../../submodules/json.mo/src/JSON"; diff --git a/src/JSON/ToText.mo b/src/JSON/ToText.mo index c756df9..d70e9ff 100644 --- a/src/JSON/ToText.mo +++ b/src/JSON/ToText.mo @@ -1,6 +1,6 @@ -import Buffer "mo:base@0.14.14/Buffer"; -import Result "mo:base@0.14.14/Result"; -import Text "mo:base@0.14.14/Text"; +import Buffer "mo:base@0.16.0/Buffer"; +import Result "mo:base@0.16.0/Result"; +import Text "mo:base@0.16.0/Text"; import JSON "../../submodules/json.mo/src/JSON"; import NatX "mo:xtended-numbers/NatX"; diff --git a/src/UrlEncoded/FromText.mo b/src/UrlEncoded/FromText.mo index 3b9893f..ae759c1 100644 --- a/src/UrlEncoded/FromText.mo +++ b/src/UrlEncoded/FromText.mo @@ -1,14 +1,14 @@ -import Array "mo:base@0.14.14/Array"; -import Blob "mo:base@0.14.14/Blob"; -import Buffer "mo:base@0.14.14/Buffer"; -import Char "mo:base@0.14.14/Char"; -import Debug "mo:base@0.14.14/Debug"; -import Result "mo:base@0.14.14/Result"; -import TrieMap "mo:base@0.14.14/TrieMap"; -import Nat "mo:base@0.14.14/Nat"; -import Text "mo:base@0.14.14/Text"; -import Iter "mo:base@0.14.14/Iter"; -import Option "mo:base@0.14.14/Option"; +import Array "mo:base@0.16.0/Array"; +import Blob "mo:base@0.16.0/Blob"; +import Buffer "mo:base@0.16.0/Buffer"; +import Char "mo:base@0.16.0/Char"; +import Debug "mo:base@0.16.0/Debug"; +import Result "mo:base@0.16.0/Result"; +import TrieMap "mo:base@0.16.0/TrieMap"; +import Nat "mo:base@0.16.0/Nat"; +import Text "mo:base@0.16.0/Text"; +import Iter "mo:base@0.16.0/Iter"; +import Option "mo:base@0.16.0/Option"; import Itertools "mo:itertools@0.2.2/Iter"; diff --git a/src/UrlEncoded/Parser.mo b/src/UrlEncoded/Parser.mo index 2729375..9bac115 100644 --- a/src/UrlEncoded/Parser.mo +++ b/src/UrlEncoded/Parser.mo @@ -1,8 +1,8 @@ -import Char "mo:base@0.14.14/Char"; -import Iter "mo:base@0.14.14/Iter"; -import Float "mo:base@0.14.14/Float"; -import List "mo:base@0.14.14/List"; -import Nat32 "mo:base@0.14.14/Nat32"; +import Char "mo:base@0.16.0/Char"; +import Iter "mo:base@0.16.0/Iter"; +import Float "mo:base@0.16.0/Float"; +import List "mo:base@0.16.0/List"; +import Nat32 "mo:base@0.16.0/Nat32"; import C "../../submodules/parser-combinators.mo/src/Combinators"; import P "../../submodules/parser-combinators.mo/src/Parser"; diff --git a/src/UrlEncoded/ToText.mo b/src/UrlEncoded/ToText.mo index 1818b6d..73a18b8 100644 --- a/src/UrlEncoded/ToText.mo +++ b/src/UrlEncoded/ToText.mo @@ -1,12 +1,12 @@ -import Result "mo:base@0.14.14/Result"; -import Nat "mo:base@0.14.14/Nat"; -import Nat32 "mo:base@0.14.14/Nat32"; -import Text "mo:base@0.14.14/Text"; -import TrieMap "mo:base@0.14.14/TrieMap"; -import Iter "mo:base@0.14.14/Iter"; -import Float "mo:base@0.14.14/Float"; -import Principal "mo:base@0.14.14/Principal"; -import Debug "mo:base@0.14.14/Debug"; +import Result "mo:base@0.16.0/Result"; +import Nat "mo:base@0.16.0/Nat"; +import Nat32 "mo:base@0.16.0/Nat32"; +import Text "mo:base@0.16.0/Text"; +import TrieMap "mo:base@0.16.0/TrieMap"; +import Iter "mo:base@0.16.0/Iter"; +import Float "mo:base@0.16.0/Float"; +import Principal "mo:base@0.16.0/Principal"; +import Debug "mo:base@0.16.0/Debug"; import itertools "mo:itertools@0.2.2/Iter"; diff --git a/src/Utils.mo b/src/Utils.mo index 63ef150..2cd57d5 100644 --- a/src/Utils.mo +++ b/src/Utils.mo @@ -1,20 +1,20 @@ -import Array "mo:base@0.14.14/Array"; -import Char "mo:base@0.14.14/Char"; -import Order "mo:base@0.14.14/Order"; -import Float "mo:base@0.14.14/Float"; -import Text "mo:base@0.14.14/Text"; -import Iter "mo:base@0.14.14/Iter"; -import Nat64 "mo:base@0.14.14/Nat64"; -import Nat32 "mo:base@0.14.14/Nat32"; -import Nat8 "mo:base@0.14.14/Nat8"; -import Int "mo:base@0.14.14/Int"; -import Buffer "mo:base@0.14.14/Buffer"; -import Result "mo:base@0.14.14/Result"; -import Int64 "mo:base@0.14.14/Int64"; -import Blob "mo:base@0.14.14/Blob"; - -import Prelude "mo:base@0.14.14/Prelude"; -import Debug "mo:base@0.14.14/Debug"; +import Array "mo:base@0.16.0/Array"; +import Char "mo:base@0.16.0/Char"; +import Order "mo:base@0.16.0/Order"; +import Float "mo:base@0.16.0/Float"; +import Text "mo:base@0.16.0/Text"; +import Iter "mo:base@0.16.0/Iter"; +import Nat64 "mo:base@0.16.0/Nat64"; +import Nat32 "mo:base@0.16.0/Nat32"; +import Nat8 "mo:base@0.16.0/Nat8"; +import Int "mo:base@0.16.0/Int"; +import Buffer "mo:base@0.16.0/Buffer"; +import Result "mo:base@0.16.0/Result"; +import Int64 "mo:base@0.16.0/Int64"; +import Blob "mo:base@0.16.0/Blob"; + +import Prelude "mo:base@0.16.0/Prelude"; +import Debug "mo:base@0.16.0/Debug"; import Itertools "mo:itertools@0.2.2/Iter"; import Map "mo:map@9.0.1/Map"; import MapConst "mo:map@9.0.1/Map/const"; @@ -36,20 +36,20 @@ module { ) ]; - /// Function copied from mo:candid@2.0.0/Tag: https://github.com/edjCase/motoko_candid/blob/d038b7bd953fb8826ae66a5f34bf06dcc29b2e0f/src/Tag.mo#L14-L30 + /// Function copied from mo:candid/Tag: https://github.com/edjCase/motoko_candid/blob/d038b7bd953fb8826ae66a5f34bf06dcc29b2e0f/src/Tag.mo#L14-L30 /// /// Computes the hash of a given record field key. /// public func hash_record_key(name : Text) : Nat32 { - // hash(name) = ( Sum_(i=0..k) utf8(name)[i] * 223^(k-i) ) mod 2^32 where k = |utf8(name)|-1 - let bytes : [Nat8] = Blob.toArray(Text.encodeUtf8(name)); - Array.foldLeft( - bytes, - 0, - func(accum : Nat32, byte : Nat8) : Nat32 { - (accum *% 223) +% Nat32.fromNat(Nat8.toNat(byte)); - }, - ); + // hash(name) = ( Sum_(i=0..k) utf8(name)[i] * 223^(k-i) ) mod 2^32 where k = |utf8(name)|-1 + let bytes : [Nat8] = Blob.toArray(Text.encodeUtf8(name)); + Array.foldLeft( + bytes, + 0, + func(accum : Nat32, byte : Nat8) : Nat32 { + (accum *% 223) +% Nat32.fromNat(Nat8.toNat(byte)); + }, + ); }; public func reverse_order(fn : (A, A) -> Order.Order) : (A, A) -> Order.Order { diff --git a/src/libs/motoko_candid/utils.mo b/src/libs/motoko_candid/utils.mo index 82ea71a..ce707d9 100644 --- a/src/libs/motoko_candid/utils.mo +++ b/src/libs/motoko_candid/utils.mo @@ -1,31 +1,31 @@ -import Array "mo:base@0.14.14/Array"; -import Blob "mo:base@0.14.14/Blob"; -import Buffer "mo:base@0.14.14/Buffer"; -import Debug "mo:base@0.14.14/Debug"; -import Result "mo:base@0.14.14/Result"; -import Nat64 "mo:base@0.14.14/Nat64"; -import Int8 "mo:base@0.14.14/Int8"; -import Int32 "mo:base@0.14.14/Int32"; -import Nat8 "mo:base@0.14.14/Nat8"; -import Nat32 "mo:base@0.14.14/Nat32"; -import Nat16 "mo:base@0.14.14/Nat16"; -import Int64 "mo:base@0.14.14/Int64"; -import Nat "mo:base@0.14.14/Nat"; -import Int "mo:base@0.14.14/Int"; -import Iter "mo:base@0.14.14/Iter"; -import Prelude "mo:base@0.14.14/Prelude"; -import Principal "mo:base@0.14.14/Principal"; -import Text "mo:base@0.14.14/Text"; -import Order "mo:base@0.14.14/Order"; -import Func "mo:base@0.14.14/Func"; -import Char "mo:base@0.14.14/Char"; -import TrieMap "mo:base@0.14.14/TrieMap"; -import Int16 "mo:base@0.14.14/Int16"; - -import Arg "mo:candid@2.0.0/Arg"; -import Value "mo:candid@2.0.0/Value"; -import Type "mo:candid@2.0.0/Type"; -import Tag "mo:candid@2.0.0/Tag"; +import Array "mo:base@0.16.0/Array"; +import Blob "mo:base@0.16.0/Blob"; +import Buffer "mo:base@0.16.0/Buffer"; +import Debug "mo:base@0.16.0/Debug"; +import Result "mo:base@0.16.0/Result"; +import Nat64 "mo:base@0.16.0/Nat64"; +import Int8 "mo:base@0.16.0/Int8"; +import Int32 "mo:base@0.16.0/Int32"; +import Nat8 "mo:base@0.16.0/Nat8"; +import Nat32 "mo:base@0.16.0/Nat32"; +import Nat16 "mo:base@0.16.0/Nat16"; +import Int64 "mo:base@0.16.0/Int64"; +import Nat "mo:base@0.16.0/Nat"; +import Int "mo:base@0.16.0/Int"; +import Iter "mo:base@0.16.0/Iter"; +import Prelude "mo:base@0.16.0/Prelude"; +import Principal "mo:base@0.16.0/Principal"; +import Text "mo:base@0.16.0/Text"; +import Order "mo:base@0.16.0/Order"; +import Func "mo:base@0.16.0/Func"; +import Char "mo:base@0.16.0/Char"; +import TrieMap "mo:base@0.16.0/TrieMap"; +import Int16 "mo:base@0.16.0/Int16"; + +import Arg "mo:candid/Arg"; +import Value "mo:candid/Value"; +import Type "mo:candid/Type"; +import Tag "mo:candid/Tag"; import Itertools "mo:itertools@0.2.2/Iter"; import PeekableIter "mo:itertools@0.2.2/PeekableIter"; import Map "mo:map@9.0.1/Map"; diff --git a/submodules/json.mo/README.md b/submodules/json.mo/README.md index 1459755..8c47eaa 100644 --- a/submodules/json.mo/README.md +++ b/submodules/json.mo/README.md @@ -6,7 +6,7 @@ ```motoko import JSON "mo:json/JSON"; -import Debug "mo:base@0.14.14/Debug"; +import Debug "mo:base@0.16.0/Debug"; let obj : JSON.JSON = #Object([]); Debug.print(JSON.show(obj)); @@ -28,7 +28,7 @@ Debug.print(JSON.show(#Object([ ```motoko import JSON "mo:json/JSON"; -import Debug "mo:base@0.14.14/Debug"; +import Debug "mo:base@0.16.0/Debug"; Debug.print(JSON.show(#Object([("amount", #Float(32.4829))]))); // {"amount": "32.48"} diff --git a/tests/CBOR.Test.mo b/tests/CBOR.Test.mo index b924337..a6112d1 100644 --- a/tests/CBOR.Test.mo +++ b/tests/CBOR.Test.mo @@ -1,10 +1,10 @@ // @testmode wasi -import Array "mo:base@0.14.14/Array"; -import Blob "mo:base@0.14.14/Blob"; -import Debug "mo:base@0.14.14/Debug"; -import Iter "mo:base@0.14.14/Iter"; -import Principal "mo:base@0.14.14/Principal"; -import Text "mo:base@0.14.14/Text"; +import Array "mo:base@0.16.0/Array"; +import Blob "mo:base@0.16.0/Blob"; +import Debug "mo:base@0.16.0/Debug"; +import Iter "mo:base@0.16.0/Iter"; +import Principal "mo:base@0.16.0/Principal"; +import Text "mo:base@0.16.0/Text"; import { test; suite } "mo:test"; diff --git a/tests/Candid.Large.test.mo b/tests/Candid.Large.test.mo index 9224d73..de578b3 100644 --- a/tests/Candid.Large.test.mo +++ b/tests/Candid.Large.test.mo @@ -1,10 +1,10 @@ // @testmode wasi -import Iter "mo:base@0.14.14/Iter"; -import Debug "mo:base@0.14.14/Debug"; -import Prelude "mo:base@0.14.14/Prelude"; -import Text "mo:base@0.14.14/Text"; -import Char "mo:base@0.14.14/Char"; -import Buffer "mo:base@0.14.14/Buffer"; +import Iter "mo:base@0.16.0/Iter"; +import Debug "mo:base@0.16.0/Debug"; +import Prelude "mo:base@0.16.0/Prelude"; +import Text "mo:base@0.16.0/Text"; +import Char "mo:base@0.16.0/Char"; +import Buffer "mo:base@0.16.0/Buffer"; import Fuzz "mo:fuzz"; import Itertools "mo:itertools@0.2.2/Iter"; diff --git a/tests/Candid.Test.mo b/tests/Candid.Test.mo index bf4d165..616f059 100644 --- a/tests/Candid.Test.mo +++ b/tests/Candid.Test.mo @@ -1,10 +1,10 @@ // @testmode wasi -import Blob "mo:base@0.14.14/Blob"; -import Debug "mo:base@0.14.14/Debug"; -import Iter "mo:base@0.14.14/Iter"; -import Nat "mo:base@0.14.14/Nat"; -import Principal "mo:base@0.14.14/Principal"; -import Result "mo:base@0.14.14/Result"; +import Blob "mo:base@0.16.0/Blob"; +import Debug "mo:base@0.16.0/Debug"; +import Iter "mo:base@0.16.0/Iter"; +import Nat "mo:base@0.16.0/Nat"; +import Principal "mo:base@0.16.0/Principal"; +import Result "mo:base@0.16.0/Result"; import { test; suite } "mo:test"; diff --git a/tests/CandidTestUtils.mo b/tests/CandidTestUtils.mo index 9b739bf..fa34b0f 100644 --- a/tests/CandidTestUtils.mo +++ b/tests/CandidTestUtils.mo @@ -1,11 +1,11 @@ // @testmode wasi -import Blob "mo:base@0.14.14/Blob"; -import Debug "mo:base@0.14.14/Debug"; -import Iter "mo:base@0.14.14/Iter"; -import Nat "mo:base@0.14.14/Nat"; -import Principal "mo:base@0.14.14/Principal"; -import Result "mo:base@0.14.14/Result"; -import Option "mo:base@0.14.14/Option"; +import Blob "mo:base@0.16.0/Blob"; +import Debug "mo:base@0.16.0/Debug"; +import Iter "mo:base@0.16.0/Iter"; +import Nat "mo:base@0.16.0/Nat"; +import Principal "mo:base@0.16.0/Principal"; +import Result "mo:base@0.16.0/Result"; +import Option "mo:base@0.16.0/Option"; import { test; suite } "mo:test"; diff --git a/tests/ICRC3.Test.mo b/tests/ICRC3.Test.mo index 9765bc7..7191567 100644 --- a/tests/ICRC3.Test.mo +++ b/tests/ICRC3.Test.mo @@ -1,10 +1,10 @@ // @testmode wasi -import Array "mo:base@0.14.14/Array"; -import Blob "mo:base@0.14.14/Blob"; -import Debug "mo:base@0.14.14/Debug"; -import Iter "mo:base@0.14.14/Iter"; -import Principal "mo:base@0.14.14/Principal"; -import Text "mo:base@0.14.14/Text"; +import Array "mo:base@0.16.0/Array"; +import Blob "mo:base@0.16.0/Blob"; +import Debug "mo:base@0.16.0/Debug"; +import Iter "mo:base@0.16.0/Iter"; +import Principal "mo:base@0.16.0/Principal"; +import Text "mo:base@0.16.0/Text"; import { test; suite } "mo:test"; diff --git a/tests/JSON.Test.mo b/tests/JSON.Test.mo index 94c68ec..d51d726 100644 --- a/tests/JSON.Test.mo +++ b/tests/JSON.Test.mo @@ -1,8 +1,8 @@ // @testmode wasi -import Blob "mo:base@0.14.14/Blob"; -import Debug "mo:base@0.14.14/Debug"; -import Iter "mo:base@0.14.14/Iter"; -import Nat "mo:base@0.14.14/Nat"; +import Blob "mo:base@0.16.0/Blob"; +import Debug "mo:base@0.16.0/Debug"; +import Iter "mo:base@0.16.0/Iter"; +import Nat "mo:base@0.16.0/Nat"; import { test; suite } "mo:test"; diff --git a/tests/PrimitiveType.Test.mo b/tests/PrimitiveType.Test.mo index 2732d33..6b9e2ef 100644 --- a/tests/PrimitiveType.Test.mo +++ b/tests/PrimitiveType.Test.mo @@ -1,9 +1,9 @@ // @testmode wasi -import Blob "mo:base@0.14.14/Blob"; -import Debug "mo:base@0.14.14/Debug"; -import Iter "mo:base@0.14.14/Iter"; -import Nat "mo:base@0.14.14/Nat"; -import Principal "mo:base@0.14.14/Principal"; +import Blob "mo:base@0.16.0/Blob"; +import Debug "mo:base@0.16.0/Debug"; +import Iter "mo:base@0.16.0/Iter"; +import Nat "mo:base@0.16.0/Nat"; +import Principal "mo:base@0.16.0/Principal"; import { test; suite } "mo:test"; diff --git a/tests/RepIndyHash.Test.mo b/tests/RepIndyHash.Test.mo index 086dc95..d4a1267 100644 --- a/tests/RepIndyHash.Test.mo +++ b/tests/RepIndyHash.Test.mo @@ -1,5 +1,5 @@ -import Blob "mo:base@0.14.14/Blob"; -import Debug "mo:base@0.14.14/Debug"; +import Blob "mo:base@0.16.0/Blob"; +import Debug "mo:base@0.16.0/Debug"; import { test; suite } "mo:test"; diff --git a/tests/Stress.test.mo b/tests/Stress.test.mo index 8b230a6..7ee83b0 100644 --- a/tests/Stress.test.mo +++ b/tests/Stress.test.mo @@ -1,11 +1,11 @@ // @testmode wasi -import Iter "mo:base@0.14.14/Iter"; -import Debug "mo:base@0.14.14/Debug"; -import Prelude "mo:base@0.14.14/Prelude"; -import Text "mo:base@0.14.14/Text"; -import Char "mo:base@0.14.14/Char"; -import Buffer "mo:base@0.14.14/Buffer"; -import Nat64 "mo:base@0.14.14/Nat64"; +import Iter "mo:base@0.16.0/Iter"; +import Debug "mo:base@0.16.0/Debug"; +import Prelude "mo:base@0.16.0/Prelude"; +import Text "mo:base@0.16.0/Text"; +import Char "mo:base@0.16.0/Char"; +import Buffer "mo:base@0.16.0/Buffer"; +import Nat64 "mo:base@0.16.0/Nat64"; import Fuzz "mo:fuzz"; import Itertools "mo:itertools@0.2.2/Iter"; diff --git a/tests/UrlEncoded.Test.mo b/tests/UrlEncoded.Test.mo index fdae0fc..3adc0c5 100644 --- a/tests/UrlEncoded.Test.mo +++ b/tests/UrlEncoded.Test.mo @@ -1,6 +1,6 @@ // @testmode wasi -import Debug "mo:base@0.14.14/Debug"; -import Iter "mo:base@0.14.14/Iter"; +import Debug "mo:base@0.16.0/Debug"; +import Iter "mo:base@0.16.0/Iter"; import { test; suite } "mo:test"; diff --git a/tests/one-shot.backward-reference.test.mo b/tests/one-shot.backward-reference.test.mo index 6629938..8c5b68e 100755 --- a/tests/one-shot.backward-reference.test.mo +++ b/tests/one-shot.backward-reference.test.mo @@ -1,16 +1,16 @@ -import Array "mo:base@0.14.14/Array"; -import Blob "mo:base@0.14.14/Blob"; -import Debug "mo:base@0.14.14/Debug"; -import Iter "mo:base@0.14.14/Iter"; -import Principal "mo:base@0.14.14/Principal"; -import Text "mo:base@0.14.14/Text"; -import TrieMap "mo:base@0.14.14/TrieMap"; - -import Arg "mo:candid@2.0.0/Arg"; -import Decoder "mo:candid@2.0.0/Decoder"; -import Encoder "mo:candid@2.0.0/Encoder"; -import Type "mo:candid@2.0.0/Type"; -import Value "mo:candid@2.0.0/Value"; +import Array "mo:base@0.16.0/Array"; +import Blob "mo:base@0.16.0/Blob"; +import Debug "mo:base@0.16.0/Debug"; +import Iter "mo:base@0.16.0/Iter"; +import Principal "mo:base@0.16.0/Principal"; +import Text "mo:base@0.16.0/Text"; +import TrieMap "mo:base@0.16.0/TrieMap"; + +import Arg "mo:candid/Arg"; +import Decoder "mo:candid/Decoder"; +import Encoder "mo:candid/Encoder"; +import Type "mo:candid/Type"; +import Value "mo:candid/Value"; import { test; suite } "mo:test"; import Candid "../src/Candid"; diff --git a/tests/one-shot.forward-reference.mo b/tests/one-shot.forward-reference.mo index 3a59805..699f3f5 100755 --- a/tests/one-shot.forward-reference.mo +++ b/tests/one-shot.forward-reference.mo @@ -1,18 +1,18 @@ // @testmode wasi -import Array "mo:base@0.14.14/Array"; -import Blob "mo:base@0.14.14/Blob"; -import Debug "mo:base@0.14.14/Debug"; -import Iter "mo:base@0.14.14/Iter"; -import Principal "mo:base@0.14.14/Principal"; -import Text "mo:base@0.14.14/Text"; -import TrieMap "mo:base@0.14.14/TrieMap"; -import Option "mo:base@0.14.14/Option"; - -import Arg "mo:candid@2.0.0/Arg"; -import Decoder "mo:candid@2.0.0/Decoder"; -import Encoder "mo:candid@2.0.0/Encoder"; -import Type "mo:candid@2.0.0/Type"; -import Value "mo:candid@2.0.0/Value"; +import Array "mo:base@0.16.0/Array"; +import Blob "mo:base@0.16.0/Blob"; +import Debug "mo:base@0.16.0/Debug"; +import Iter "mo:base@0.16.0/Iter"; +import Principal "mo:base@0.16.0/Principal"; +import Text "mo:base@0.16.0/Text"; +import TrieMap "mo:base@0.16.0/TrieMap"; +import Option "mo:base@0.16.0/Option"; + +import Arg "mo:candid/Arg"; +import Decoder "mo:candid/Decoder"; +import Encoder "mo:candid/Encoder"; +import Type "mo:candid/Type"; +import Value "mo:candid/Value"; import { test; suite } "mo:test"; import Candid "../src/Candid"; diff --git a/tests/test_template.md b/tests/test_template.md index 97062ee..76bff6e 100644 --- a/tests/test_template.md +++ b/tests/test_template.md @@ -1,8 +1,8 @@ Filename: `[Section]/[Function].Test.mo` ```motoko -import Debug "mo:base@0.14.14/Debug"; -import Iter "mo:base@0.14.14/Iter"; +import Debug "mo:base@0.16.0/Debug"; +import Iter "mo:base@0.16.0/Iter"; import ActorSpec "../utils/ActorSpec"; import Algo "../../src";