///|
fn hex_digit(char : Char) -> Int raise {
let code = char.to_int()
if code >= '0'.to_int() && code <= '9'.to_int() {
code - '0'.to_int()
} else if code >= 'a'.to_int() && code <= 'f'.to_int() {
code - 'a'.to_int() + 10
} else if code >= 'A'.to_int() && code <= 'F'.to_int() {
code - 'A'.to_int() + 10
} else {
fail("invalid hex digit: \{char}")
}
}
///|
fn decode_hex(input : String) -> Bytes raise {
let compact = StringBuilder::new()
for i = 0; i < input.length(); i = i + 1 {
let ch = input[i]
if ch != ' ' && ch != '\n' && ch != '\r' && ch != '\t' {
compact.write_char(Int::unsafe_to_char(ch.to_int()))
}
}
let normalized = compact.to_string()
if normalized.length() % 2 != 0 {
fail("hex input must have an even number of digits")
}
let buf = Buffer()
for i = 0; i < normalized.length(); i = i + 2 {
let hi = hex_digit(Int::unsafe_to_char(normalized[i].to_int()))
let lo = hex_digit(Int::unsafe_to_char(normalized[i + 1].to_int()))
buf.write_byte(((hi << 4) | lo).to_byte())
}
buf.to_bytes()
}
///|
fn encode_hex(bytes : Bytes) -> String {
let alphabet = [
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
]
let builder = StringBuilder::new()
for i = 0; i < bytes.length(); i = i + 1 {
let value = bytes[i].to_int()
builder.write_char(alphabet[value >> 4])
builder.write_char(alphabet[value & 0x0F])
}
builder.to_string()
}
///|
fn sample_value() -> @cbor.CborValue {
@cbor.Map([
(@cbor.Text("message"), @cbor.Text("hello cbor")),
(
@cbor.Text("numbers"),
@cbor.Array([@cbor.Integer(1L), @cbor.Integer(2L), @cbor.Integer(3L)]),
),
(@cbor.Text("flag"), @cbor.Simple(21)),
])
}
///|
fn print_usage() -> Unit {
println("moonbit-cbor CLI")
println(" moon run ./cmd/main")
println(" moon run ./cmd/main decode-hex ")
println(" moon run ./cmd/main roundtrip-hex ")
println(" moon run ./cmd/main benchmark [iterations]")
println(" moon run ./cmd/main application-demo")
}
///|
fn run_demo() -> Unit {
let value = sample_value()
println("Diagnostic:")
println(@cbor.diagnostic(value))
let bytes = @cbor.encode(value)
println("Hex:")
println(encode_hex(bytes))
}
///|
fn run_decode_hex(hex : String) -> Unit raise {
let bytes = decode_hex(hex)
let value = @cbor.decode(bytes) catch { err => fail("decode failed: \{err}") }
println(@cbor.diagnostic(value))
}
///|
fn run_roundtrip_hex(hex : String) -> Unit raise {
let bytes = decode_hex(hex)
let value = @cbor.decode(bytes) catch { err => fail("decode failed: \{err}") }
let encoded = @cbor.encode(value)
println("Diagnostic:")
println(@cbor.diagnostic(value))
println("Re-encoded hex:")
println(encode_hex(encoded))
}
///|
fn run_benchmark_case(
name : String,
value : @cbor.CborValue,
iterations : Int,
) -> Unit {
let encoded = @cbor.encode(value)
let started = @bench.monotonic_clock_start()
for _i = 0; _i < iterations; _i = _i + 1 {
ignore(@cbor.encode(value))
ignore(
@cbor.decode(encoded) catch {
err => abort("benchmark decode failed: \{err}")
},
)
}
let elapsed_us = @bench.monotonic_clock_end(started)
println(
"case=\{name} iterations=\{iterations} payload_bytes=\{encoded.length()} elapsed_us=\{elapsed_us.to_int()} elapsed_ms=\{(elapsed_us / 1000.0).to_string()}",
)
}
///|
fn benchmark_binary_value() -> @cbor.CborValue {
let buf = Buffer()
for i = 0; i < 256; i = i + 1 {
buf.write_byte((i & 0xFF).to_byte())
}
@cbor.Bytes(buf.to_bytes())
}
///|
fn benchmark_utf8_value() -> @cbor.CborValue {
let builder = StringBuilder::new()
builder.write_string("MoonBit CBOR ")
builder.write_char(Int::unsafe_to_char(0x1F642))
builder.write_string(" RFC 8949")
@cbor.Text(builder.to_string())
}
///|
fn benchmark_nested_value() -> @cbor.CborValue {
@cbor.Array([
sample_value(),
@cbor.Map([
(@cbor.Text("payload"), benchmark_binary_value()),
(
@cbor.Text("metadata"),
@cbor.Map([
(@cbor.Text("version"), @cbor.Integer(1L)),
(@cbor.Text("stable"), @cbor.Simple(21)),
]),
),
]),
])
}
///|
fn run_benchmark(iterations : Int) -> Unit {
if iterations <= 0 {
println("iterations must be a positive integer")
return
}
let cases : Array[(String, @cbor.CborValue)] = [
("small-map", sample_value()),
("utf8-text", benchmark_utf8_value()),
("binary-256", benchmark_binary_value()),
("nested-value", benchmark_nested_value()),
]
for item in cases {
run_benchmark_case(item.0, item.1, iterations)
}
}
///|
fn run_application_demo() -> Unit raise {
let payload = @cbor.Map([
(@cbor.Text("user"), @cbor.Text("Ada")),
(@cbor.Text("token"), @cbor.Text("demo-secret")),
(
@cbor.Text("items"),
@cbor.Array([@cbor.Integer(1L), @cbor.Integer(2L), @cbor.Integer(3L)]),
),
])
let request = @cbor.app_request("cli-request", "ingest", payload)
let response = @cbor.app_response(
"cli-response",
"cli-request",
@cbor.Simple(21),
)
let batch = @cbor.app_batch_new(producer="cli-demo", sequence=1UL, messages=[
request, response,
])
let batch_bytes = @cbor.encode_app_batch(batch)
let frame_bytes = @cbor.encode_cbor_frames(
[@cbor.app_batch_to_cbor(batch)],
4096,
)
let rules = [
@cbor.validation_required_text("user"),
@cbor.validation_text_length("user", 1, 64),
@cbor.validation_array_length("items", 1, 16),
]
let report = @cbor.validate_object_payload(payload, rules)
let user = @cbor.cbor_query_text(payload, [@cbor.Key("user")])
let redacted = @cbor.cbor_redacted_preview(payload, ["token"], 160)
println("Application batch:")
println(@cbor.app_batch_summary(batch))
println("Batch bytes: \{batch_bytes.length()}")
println("Framed bytes: \{frame_bytes.length()}")
println("Query user: \{user}")
println("Validation: \{@cbor.validation_report_to_string(report)}")
println("Redacted: \{redacted}")
}
///|
fn parse_iterations(raw : String) -> Int? {
Some(@string.parse_int(raw)) catch {
_ => None
}
}
///|
fn is_command_token(arg : String) -> Bool {
arg == "decode-hex" ||
arg == "roundtrip-hex" ||
arg == "benchmark" ||
arg == "application-demo" ||
arg == "--help" ||
arg == "-h"
}
///|
fn command_index(args : Array[String]) -> Int? {
for i = 0; i < args.length(); i = i + 1 {
if is_command_token(args[i]) {
return Some(i)
}
}
None
}
///|
fn main {
let args = @env.args()
match command_index(args) {
None => run_demo()
Some(index) =>
match args[index] {
"decode-hex" =>
if index + 1 >= args.length() {
print_usage()
} else {
run_decode_hex(args[index + 1]) catch {
message => println(message)
}
}
"roundtrip-hex" =>
if index + 1 >= args.length() {
print_usage()
} else {
run_roundtrip_hex(args[index + 1]) catch {
message => println(message)
}
}
"benchmark" => {
let iterations = if index + 1 < args.length() {
match parse_iterations(args[index + 1]) {
Some(value) => value
None => {
println("iterations must be an integer")
return
}
}
} else {
10000
}
run_benchmark(iterations)
}
"application-demo" =>
run_application_demo() catch {
message => println(message)
}
"--help" => print_usage()
"-h" => print_usage()
_ => print_usage()
}
}
}