// The text format.
//
// Ported from the WAT half of wax/src/lib-wasm/output.ml.
//
// The text form is the binary's tree written down: same module, same
// instructions, folded rather than flat and named rather than numbered. Both
// come from one lowering, so the two outputs cannot drift -- which is the
// whole reason the fold is recorded rather than re-derived.
///|
/// An identifier, as the text format spells one: `$name`, or the index when
/// nothing named it.
fn name_or(names : Map[Int, Bytes], i : Int) -> String {
match names.get(i) {
Some(b) => ident(b)
None => i.to_string()
}
}
///|
/// An identifier as the format spells one: `$x` when every character may
/// appear in a bare identifier, and the quoted `$"..."` form otherwise --
/// which is the only way to write a name that is empty or holds a space.
pub fn ident(b : Bytes) -> String {
let text = text_of(b)
if is_bare_identifier(text) {
"$" + text
} else {
"$" + quoted(b)
}
}
///|
fn is_bare_identifier(s : String) -> Bool {
if s.is_empty() {
return false
}
for c in s {
let ok = (c >= '0' && c <= '9') ||
(c >= 'A' && c <= 'Z') ||
(c >= 'a' && c <= 'z') ||
"!#$%&'*+-./:<=>?@\\^_`|~".contains(c.to_string())
if !ok {
return false
}
}
true
}
///|
/// A name-section entry as a string. The section stores UTF-8 bytes; the
/// printer wants characters.
fn text_of(b : Bytes) -> String {
let out = StringBuilder::new()
let mut k = 0
while k < b.length() {
let c = b[k].to_int()
if c < 0x80 {
out.write_char(Int::unsafe_to_char(c))
k = k + 1
} else if c < 0xE0 && k + 1 < b.length() {
out.write_char(
Int::unsafe_to_char(((c & 0x1F) << 6) | (b[k + 1].to_int() & 0x3F)),
)
k = k + 2
} else if c < 0xF0 && k + 2 < b.length() {
out.write_char(
Int::unsafe_to_char(
((c & 0x0F) << 12) |
((b[k + 1].to_int() & 0x3F) << 6) |
(b[k + 2].to_int() & 0x3F),
),
)
k = k + 3
} else if k + 3 < b.length() {
out.write_char(
Int::unsafe_to_char(
((c & 0x07) << 18) |
((b[k + 1].to_int() & 0x3F) << 12) |
((b[k + 2].to_int() & 0x3F) << 6) |
(b[k + 3].to_int() & 0x3F),
),
)
k = k + 4
} else {
k = k + 1
}
}
out.to_string()
}
///|
/// A value type, as the text format spells it.
pub fn valtype(v : @wasm_types.ValType[Int], names : Map[Int, Bytes]) -> String {
match v {
I32 => "i32"
I64 => "i64"
F32 => "f32"
F64 => "f64"
V128 => "v128"
Ref(r) => reftype(r, names)
}
}
///|
/// A reference type. The nullable abstract ones have one-word spellings --
/// `funcref` rather than `(ref null func)` -- and the format prefers them.
pub fn reftype(r : @wasm_types.RefType[Int], names : Map[Int, Bytes]) -> String {
if r.nullable {
match r.typ {
Func => return "funcref"
Extern => return "externref"
Any => return "anyref"
Eq => return "eqref"
I31 => return "i31ref"
Struct => return "structref"
Array => return "arrayref"
Exn => return "exnref"
Cont => return "contref"
None_ => return "nullref"
NoFunc => return "nullfuncref"
NoExtern => return "nullexternref"
NoExn => return "nullexnref"
NoCont => return "nullcontref"
_ => ()
}
}
let inner = heaptype(r.typ, names)
if r.nullable {
"(ref null " + inner + ")"
} else {
"(ref " + inner + ")"
}
}
///|
/// A heap type.
pub fn heaptype(
h : @wasm_types.HeapType[Int],
names : Map[Int, Bytes],
) -> String {
match h {
Func => "func"
NoFunc => "nofunc"
Exn => "exn"
NoExn => "noexn"
Cont => "cont"
NoCont => "nocont"
Extern => "extern"
NoExtern => "noextern"
Any => "any"
Eq => "eq"
I31 => "i31"
Struct => "struct"
Array => "array"
None_ => "none"
Type(i) => name_or(names, i)
Exact(i) => "(exact " + name_or(names, i) + ")"
}
}