unmarshal ocaml marshalled data
moon add bobzhang/unmarshal| Package | Provides |
|---|---|
| bobzhang/unmarshal | shared value model: MarshalValue, MarshalHeader |
| bobzhang/unmarshal/decoder | Decoder (decode marshal bytes) |
| bobzhang/unmarshal/encoder | marshal (encode to marshal bytes) |
| bobzhang/unmarshal/viz | marshal_to_dot (Graphviz visualization) |
| bobzhang/unmarshal/dot | small Graphviz/Mermaid graph builder |
///|
test "marshal_round_trip" {
// Build a value and serialize it (the bytes match `Marshal.to_string`).
let value = @unmarshal.MBlock(tag=0, [
@unmarshal.MInt(42),
@unmarshal.MString(b"hi"),
])
let bytes = @encoder.marshal(value)
// Decoding the bytes gives the value back.
let (_, decoded) = @decoder.Decoder::new(bytes).decode()
inspect(
decoded,
content=(
#|MBlock(tag=0, [MInt(42), MString(<Bytes: [0x68, 0x69]>)])
),
)
}///|
test "basic_usage" {
let data : Bytes = [
b'\x84', b'\x95', b'\xa6', b'\xbe', // Magic number
b'\x00', b'\x00', b'\x00', b'\x01', // Data length: 1
b'\x00', b'\x00', b'\x00', b'\x00', // Num objects: 0
b'\x00', b'\x00', b'\x00', b'\x00', // Size 32: 0
b'\x00', b'\x00', b'\x00', b'\x00', // Size 64: 0
b'\x41', // Data: small int 1
]
let decoder = @decoder.Decoder::new(data)
let (header, value) = decoder.decode()
// Verify the result
inspect(header.magic, content="2224400062")
inspect(value, content="MInt(1)")
}///|
test "decode_integers" {
// Small integer (0-63): single byte encoding
let small_int_data : Bytes = [
b'\x84', b'\x95', b'\xa6', b'\xbe', b'\x00', b'\x00', b'\x00', b'\x01', b'\x00',
b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00',
b'\x00', b'\x00', b'\x6a', // 0x40 + 42 = 0x6a
]
let decoder = @decoder.Decoder::new(small_int_data)
let (_, value) = decoder.decode()
inspect(value, content="MInt(42)")
}///|
test "decode_strings" {
// Small string "Hello" (< 32 chars)
let string_data : Bytes = [
b'\x84', b'\x95', b'\xa6', b'\xbe', b'\x00', b'\x00', b'\x00', b'\x06', b'\x00',
b'\x00', b'\x00', b'\x01', b'\x00', b'\x00', b'\x00', b'\x03', b'\x00', b'\x00',
b'\x00', b'\x02', b'\x25', // PREFIX_SMALL_STRING + 5
b'\x48', b'\x65', b'\x6c', b'\x6c', b'\x6f', // "Hello"
]
let decoder = @decoder.Decoder::new(string_data)
let (_, value) = decoder.decode()
inspect(
value,
content=(
#|MString(b"Hello")
),
)
}///|
test "decode_tuple" {
// Tuple (1, 2)
let tuple_data : Bytes = [
b'\x84', b'\x95', b'\xa6', b'\xbe', b'\x00', b'\x00', b'\x00', b'\x03', b'\x00',
b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00',
b'\x00', b'\x00', b'\xa0', // PREFIX_SMALL_BLOCK: tag=0, size=2
b'\x41', // Small int 1
b'\x42', // Small int 2
]
let decoder = @decoder.Decoder::new(tuple_data)
let (_, value) = decoder.decode()
match value {
@unmarshal.MBlock(tag~, fields) => {
inspect(tag, content="0")
inspect(fields.length(), content="2")
inspect(fields[0], content="MInt(1)")
inspect(fields[1], content="MInt(2)")
}
_ => abort("Expected MBlock")
}
}///|
test "decode_float_array" {
// Simple float array [3.14, 2.71]
let float_array_data : Bytes = [
b'\x84', b'\x95', b'\xa6', b'\xbe', b'\x00', b'\x00', b'\x00', b'\x12', b'\x00',
b'\x00', b'\x00', b'\x01', b'\x00', b'\x00', b'\x00', b'\x05', b'\x00', b'\x00',
b'\x00', b'\x03', b'\x0e', b'\x02', // CODE_DOUBLE_ARRAY8_LITTLE, count=2
// First double: 3.14 (little-endian)
b'\x1f', b'\x85', b'\xeb', b'\x51', b'\xb8', b'\x1e', b'\x09', b'\x40',
// Second double: 2.71 (little-endian)
b'\x29', b'\x5c', b'\x8f', b'\xc2', b'\xf5', b'\xa8', b'\x05', b'\x40',
]
let decoder = @decoder.Decoder::new(float_array_data)
let (_, value) = decoder.decode()
match value {
@unmarshal.MDoubleArray(arr) => {
inspect(arr.length(), content="2")
// Values are approximately 3.14 and 2.71
assert_true(arr[0] > 3.13 && arr[0] < 3.15)
assert_true(arr[1] > 2.70 && arr[1] < 2.72)
}
_ => abort("Expected MDoubleArray")
}
}///|
test "decode_int32" {
// OCaml Int32.of_int 42
let int32_data : Bytes = [
b'\x84', b'\x95', b'\xa6', b'\xbe', b'\x00', b'\x00', b'\x00', b'\x08', b'\x00',
b'\x00', b'\x00', b'\x01', b'\x00', b'\x00', b'\x00', b'\x03', b'\x00', b'\x00',
b'\x00', b'\x03', b'\x19', // CODE_CUSTOM_FIXED
b'\x5f', b'\x69', b'\x00', // "_i" identifier (null-terminated)
b'\x00', b'\x00', b'\x00', b'\x2a', // 42 in big-endian
]
let decoder = @decoder.Decoder::new(int32_data)
let (_, value) = decoder.decode()
match value {
@unmarshal.MCustom(id, data) => {
inspect(id, content="_i")
// Extract the Int32 value (big-endian)
let val = (data[0].to_int() << 24) |
(data[1].to_int() << 16) |
(data[2].to_int() << 8) |
data[3].to_int()
inspect(val, content="42")
}
_ => abort("Expected MCustom")
}
}
///|
test "decode_int64" {
// OCaml Int64.of_int 1000000
let int64_data : Bytes = [
b'\x84', b'\x95', b'\xa6', b'\xbe', b'\x00', b'\x00', b'\x00', b'\x0c', b'\x00',
b'\x00', b'\x00', b'\x01', b'\x00', b'\x00', b'\x00', b'\x03', b'\x00', b'\x00',
b'\x00', b'\x02', b'\x19', // CODE_CUSTOM_FIXED
b'\x5f', b'\x6a', b'\x00', // "_j" identifier (null-terminated)
b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x0f', b'\x42', b'\x40', // 1000000
]
let decoder = @decoder.Decoder::new(int64_data)
let (_, value) = decoder.decode()
match value {
@unmarshal.MCustom(id, [i64be(val)]) => {
inspect(id, content="_j")
inspect(val, content="1000000")
}
_ => abort("Expected MCustom")
}
}
///|
test "decode_nativeint" {
// OCaml Nativeint.of_int 42 (on 64-bit platform)
let nativeint_data : Bytes = [
b'\x84', b'\x95', b'\xa6', b'\xbe', b'\x00', b'\x00', b'\x00', b'\x0c', b'\x00',
b'\x00', b'\x00', b'\x01', b'\x00', b'\x00', b'\x00', b'\x03', b'\x00', b'\x00',
b'\x00', b'\x02', b'\x19', // CODE_CUSTOM_FIXED
b'\x5f', b'\x6e', b'\x00', // "_n" identifier (null-terminated)
b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x2a', // 42
]
let decoder = @decoder.Decoder::new(nativeint_data)
let (_, value) = decoder.decode()
match value {
@unmarshal.MCustom(id, [i64be(val)]) => {
inspect(id, content="_n")
inspect(val, content="42")
}
_ => abort("Expected MCustom")
}
}///|
test "shared_references" {
// Tuple with shared string: ("shared", "shared")
// The second string is a reference to the first
let shared_data : Bytes = [
b'\x84', b'\x95', b'\xa6', b'\xbe', b'\x00', b'\x00', b'\x00', b'\x0a', b'\x00',
b'\x00', b'\x00', b'\x02', b'\x00', b'\x00', b'\x00', b'\x06', b'\x00', b'\x00',
b'\x00', b'\x05', b'\xa0', // Small block, tag=0, size=2
b'\x26', b'\x73', b'\x68', b'\x61', // Small string "shar"
b'\x72', b'\x65', b'\x64', // "ed"
b'\x04', b'\x01', // SHARED8, index 1
]
let decoder = @decoder.Decoder::new(shared_data)
let (_, value) = decoder.decode()
match value {
@unmarshal.MBlock(tag~, fields) => {
inspect(tag, content="0")
inspect(
fields[0],
content=(
#|MString(b"shared")
),
)
inspect(
fields[1],
content=(
#|MString(b"shared")
),
) // Reference to first field
}
_ => abort("Expected MBlock")
}
}moon testocaml test_generator.ml > marshal_data_test.mbtimpl Show for MarshalHeaderimpl Show for MarshalValueunmarshal ocaml marshalled data