///|
pub suberror WastTextError {
Unsupported(String)
}
///|
fn hex_digit(v : Int32) -> Char {
if v < 10 {
(v + '0').unsafe_to_char()
} else {
(v - 10 + 'a').unsafe_to_char()
}
}
///|
fn quote_wast_string(s : String) -> String {
let out = StringBuilder::new()
out.write_char('"')
for c in s {
if c == '"' {
out.write_string("\\\"")
} else if c == '\\' {
out.write_string("\\\\")
} else if c == '\n' {
out.write_string("\\n")
} else if c == '\r' {
out.write_string("\\r")
} else if c == '\t' {
out.write_string("\\t")
} else {
out.write_char(c)
}
}
out.write_char('"')
out.to_string()
}
///|
fn quote_wast_bytes(bytes : Bytes) -> String {
let out = StringBuilder::new()
out.write_char('"')
for b in bytes {
if b >= 0x20 && b <= 0x7e && b != 0x22 && b != 0x5c {
out.write_char(b.to_int().unsafe_to_char())
} else if b == 0x22 {
out.write_string("\\\"")
} else if b == 0x5c {
out.write_string("\\\\")
} else {
out.write_char('\\')
out.write_char(hex_digit((b >> 4).to_int()))
out.write_char(hex_digit((b & 0x0f).to_int()))
}
}
out.write_char('"')
out.to_string()
}
///|
fn string_to_bytes_for_parser(s : String) -> Bytes {
@utf8.encode(s.to_string_view())
}
///|
fn lower_snake_from_pascal(name : String) -> String {
if name.length() == 0 {
return ""
}
let out = StringBuilder::new()
let mut i = 0
while i < name.length() {
let c = name[i]
let is_upper = c >= 'A' && c <= 'Z'
if i > 0 && is_upper {
let prev = name[i - 1]
let prev_is_lower_or_digit = (prev >= 'a' && prev <= 'z') ||
(prev >= '0' && prev <= '9')
let next_is_lower = if i + 1 < name.length() {
let n = name[i + 1]
n >= 'a' && n <= 'z'
} else {
false
}
if prev_is_lower_or_digit || next_is_lower {
out.write_char('_')
}
}
if is_upper {
out.write_char((c.to_int() + 32).unsafe_to_char())
} else {
out.write_char(c.unsafe_to_char())
}
i += 1
}
out.to_string()
}
///|
fn split_first_underscore(s : String) -> (String, String?) {
let mut i = 0
while i < s.length() {
if s[i] == '_' {
let head = s.sub(end=i).to_owned()
let tail = s.sub(start=i + 1).to_owned()
return (head, Some(tail))
}
i += 1
}
(s, None)
}
///|
fn keyword_from_opcode(op : Opcode) -> String {
if op is MemoryAtomic(memory_op) {
return memory_atomic_keyword(memory_op)
}
let raw = lower_snake_from_pascal("\{op}")
let (head, tail) = split_first_underscore(raw)
match tail {
Some(t) => "\{head}.\{t}"
None => head
}
}
///|
fn keyword_from_heap_type_ref(ht : HeapTypeRef) -> String {
match ht {
TypeNum(n) => "\{n}"
TypeId(id) => id
ExactTypeNum(n) => "(exact \{n})"
ExactTypeId(id) => "(exact \{id})"
Any => "any"
String => "string"
Array => "array"
Eq => "eq"
I31 => "i31"
None_ => "none"
NoFunc => "nofunc"
Func => "func"
NoExtern => "noextern"
NoExn => "noexn"
Waitqueue => "waitqueue"
NoWaitqueue => "nowaitqueue"
Shared(heap) => "(shared \{keyword_from_heap_type_ref(heap)})"
Struct => "struct"
}
}
///|
fn keyword_from_value_type(vt : ValueType) -> String {
match vt {
I32 => "i32"
I64 => "i64"
F32 => "f32"
F64 => "f64"
V128 => "v128"
FuncRef => "funcref"
TypedFuncRef(String) => "stringref"
ExternRef => "externref"
ExnRef => "exnref"
NonNullFuncRef => "(ref func)"
NonNullExternRef => "(ref extern)"
NonNullExnRef => "(ref exn)"
TypedFuncRef(ht) => "(ref null \{keyword_from_heap_type_ref(ht)})"
NonNullTypedFuncRef(ht) => "(ref \{keyword_from_heap_type_ref(ht)})"
}
}
///|
fn keyword_from_ref_null_immediate(vt : ValueType) -> String {
match vt {
FuncRef | NonNullFuncRef => "func"
ExternRef | NonNullExternRef => "extern"
ExnRef | NonNullExnRef => "exn"
TypedFuncRef(ht) | NonNullTypedFuncRef(ht) => keyword_from_heap_type_ref(ht)
_ => keyword_from_value_type(vt)
}
}
///|
fn keyword_from_simd_shape(shape : SimdShape) -> String {
lower_snake_from_pascal("\{shape}").replace(old="_", new="")
}
///|
fn index_text(idx : Index) -> String {
match idx {
Num(n) => "\{n}"
Id(name) => name
}
}
///|
fn atomic_order_text(order : AtomicOrder) -> String {
match order {
SeqCst => "seq_cst"
AcqRel => "acq_rel"
Relaxed => "relaxed"
}
}
///|
fn aggregate_atomic_rmw_suffix(op : @lib.AggregateAtomicRmwOp) -> String {
match op {
@lib.AggregateAtomicRmwAdd => "add"
@lib.AggregateAtomicRmwSub => "sub"
@lib.AggregateAtomicRmwAnd => "and"
@lib.AggregateAtomicRmwOr => "or"
@lib.AggregateAtomicRmwXor => "xor"
@lib.AggregateAtomicRmwXchg => "xchg"
}
}
///|
fn render_type_use(type_use : TypeUse) -> String {
let out = StringBuilder::new()
match type_use.type_index {
Some(idx) => {
out.write_string(" (type ")
out.write_string(index_text(idx))
out.write_char(')')
}
None => ()
}
if type_use.func_type.params.length() > 0 {
out.write_string(" (param")
for p in type_use.func_type.params {
out.write_char(' ')
out.write_string(keyword_from_value_type(p))
}
out.write_char(')')
}
if type_use.func_type.results.length() > 0 {
out.write_string(" (result")
for r in type_use.func_type.results {
out.write_char(' ')
out.write_string(keyword_from_value_type(r))
}
out.write_char(')')
}
out.to_string()
}
///|
fn render_field_storage(storage : FieldStorage) -> String {
match storage {
FieldStorage::Value(vt) => keyword_from_value_type(vt)
FieldStorage::I8 => "i8"
FieldStorage::I16 => "i16"
}
}
///|
fn render_struct_field(field : StructFieldDef) -> String {
if field.mutable_ {
"(field (mut \{render_field_storage(field.storage)}))"
} else {
"(field \{render_field_storage(field.storage)})"
}
}
///|
fn render_type_metadata(metadata : TypeDefMetadata) -> String {
let out = StringBuilder::new()
match metadata.describes {
Some(idx) => out.write_string(" (describes \{index_text(idx)})")
None => ()
}
match metadata.descriptor {
Some(idx) => out.write_string(" (descriptor \{index_text(idx)})")
None => ()
}
out.to_string()
}
///|
fn render_type_def_body(body : TypeDefBody) -> String {
match body {
TypeDefBody::Shared(body) => "(shared \{render_type_def_body(body)})"
TypeDefBody::Func(func_type) => {
let tu = { type_index: None, func_type, param_ids: [], }
"(func\{render_type_use(tu)})"
}
TypeDefBody::Struct(fields) => {
let out = StringBuilder::new()
out.write_string("(struct")
for field in fields {
out.write_char(' ')
out.write_string(render_struct_field(field))
}
out.write_char(')')
out.to_string()
}
TypeDefBody::Array(field) => {
let field_text = if field.mutable_ {
"(mut \{render_field_storage(field.storage)})"
} else {
render_field_storage(field.storage)
}
"(array \{field_text})"
}
}
}
///|
fn render_block_type(bt : BlockType) -> String {
let out = StringBuilder::new()
match bt.label {
Some(label) => {
out.write_char(' ')
out.write_string(label)
}
None => ()
}
match bt.type_use {
Some(type_use) => out.write_string(render_type_use(type_use))
None => ()
}
out.to_string()
}
///|
fn render_memarg(ma : MemArg) -> String {
let out = StringBuilder::new()
if ma.offset != 0UL {
out.write_string(" offset=")
out.write_string("\{ma.offset}")
}
out.write_string(" align=")
out.write_string("\{ma.align}")
out.to_string()
}
///|
fn render_v128_const(v : V128Const) -> String {
let out = StringBuilder::new()
out.write_string("v128.const ")
out.write_string(keyword_from_simd_shape(v.shape))
for value in v.values {
out.write_char(' ')
out.write_string(value)
}
out.to_string()
}
///|
fn render_shuffle_lanes(lanes : ShuffleLanes) -> String {
let out = StringBuilder::new()
for i = 0; i < 16; i = i + 1 {
if i > 0 {
out.write_char(' ')
}
out.write_string("\{lanes.lanes[i]}")
}
out.to_string()
}
///|
fn render_catch_clause(catch_clause : CatchClause) -> String {
match catch_clause {
CatchClause(tag_idx, label_idx) =>
"(catch \{index_text(tag_idx)} \{index_text(label_idx)})"
CatchRefClause(tag_idx, label_idx) =>
"(catch_ref \{index_text(tag_idx)} \{index_text(label_idx)})"
CatchAllClause(label_idx) => "(catch_all \{index_text(label_idx)})"
CatchAllRefClause(label_idx) => "(catch_all_ref \{index_text(label_idx)})"
}
}
///|
fn render_instruction_head(instr : Instruction) -> String raise WastTextError {
match instr {
Unreachable => "unreachable"
Nop => "nop"
Br(idx) => "br \{index_text(idx)}"
BrIf(idx) => "br_if \{index_text(idx)}"
BrOnNull(idx) => "br_on_null \{index_text(idx)}"
BrOnNonNull(idx) => "br_on_non_null \{index_text(idx)}"
BrOnCastDescEq(idx, source, target) =>
"br_on_cast_desc_eq \{index_text(idx)} \{keyword_from_value_type(source)} \{keyword_from_value_type(target)}"
BrOnCastDescEqFail(idx, source, target) =>
"br_on_cast_desc_eq_fail \{index_text(idx)} \{keyword_from_value_type(source)} \{keyword_from_value_type(target)}"
BrTable(targets, fallback) => {
let out = StringBuilder::new()
out.write_string("br_table")
for idx in targets {
out.write_char(' ')
out.write_string(index_text(idx))
}
out.write_char(' ')
out.write_string(index_text(fallback))
out.to_string()
}
Return => "return"
Call(idx) => "call \{index_text(idx)}"
CallIndirect(table_idx, type_use) =>
"call_indirect \{index_text(table_idx)}\{render_type_use(type_use)}"
ReturnCall(idx) => "return_call \{index_text(idx)}"
ReturnCallIndirect(table_idx, type_use) =>
"return_call_indirect \{index_text(table_idx)}\{render_type_use(type_use)}"
CallRef(type_use) =>
match type_use.type_index {
Some(index) => "call_ref \{index_text(index)}"
None => "call_ref\{render_type_use(type_use)}"
}
ReturnCallRef(type_use) =>
match type_use.type_index {
Some(index) => "return_call_ref \{index_text(index)}"
None => "return_call_ref\{render_type_use(type_use)}"
}
Throw(idx) => "throw \{index_text(idx)}"
ThrowRef => "throw_ref"
Rethrow(idx) => "rethrow \{index_text(idx)}"
Drop => "drop"
Select(None) => "select"
Select(Some(vts)) => {
let out = StringBuilder::new()
out.write_string("select (result")
for vt in vts {
out.write_char(' ')
out.write_string(keyword_from_value_type(vt))
}
out.write_char(')')
out.to_string()
}
LocalGet(idx) => "local.get \{index_text(idx)}"
LocalSet(idx) => "local.set \{index_text(idx)}"
LocalTee(idx) => "local.tee \{index_text(idx)}"
GlobalGet(idx) => "global.get \{index_text(idx)}"
GlobalSet(idx) => "global.set \{index_text(idx)}"
TableGet(idx) => "table.get \{index_text(idx)}"
TableSet(idx) => "table.set \{index_text(idx)}"
TableSize(idx) => "table.size \{index_text(idx)}"
TableGrow(idx) => "table.grow \{index_text(idx)}"
TableFill(idx) => "table.fill \{index_text(idx)}"
TableCopy(dst, src) => "table.copy \{index_text(dst)} \{index_text(src)}"
TableInit(table_idx, elem_idx) =>
"table.init \{index_text(table_idx)} \{index_text(elem_idx)}"
ElemDrop(idx) => "elem.drop \{index_text(idx)}"
ArrayLoad(op, ti, ma) | ArrayStore(op, ti, ma) =>
"\{keyword_from_opcode(op)} (type \{wt_index_text(ti)})\{render_memarg(ma)}"
Load(op, ma) => "\{keyword_from_opcode(op)}\{render_memarg(ma)}"
Store(op, ma) => "\{keyword_from_opcode(op)}\{render_memarg(ma)}"
MemorySize => "memory.size"
MemoryGrow => "memory.grow"
MemoryFill => "memory.fill"
MemoryCopy => "memory.copy"
MemoryInit(idx) => "memory.init \{index_text(idx)}"
DataDrop(idx) => "data.drop \{index_text(idx)}"
Const(op, text) => "\{keyword_from_opcode(op)} \{text}"
Unary(op) => keyword_from_opcode(op)
Binary(op) => keyword_from_opcode(op)
Test(op) => keyword_from_opcode(op)
Compare(op) => keyword_from_opcode(op)
Convert(op) => keyword_from_opcode(op)
RefNull(vt) => "ref.null \{keyword_from_ref_null_immediate(vt)}"
RefIsNull => "ref.is_null"
RefFunc(idx) => "ref.func \{index_text(idx)}"
StringConst(bytes) => "string.const \{quote_wast_bytes(bytes)}"
StringMeasureWtf16 => "string.measure_wtf16"
StringConcat => "string.concat"
StringEq => "string.eq"
StringAsWtf16 => "string.as_wtf16"
StringViewWtf16GetCodeunit => "stringview_wtf16.get_codeunit"
StringViewWtf16Slice => "stringview_wtf16.slice"
StringNewUtf8Array => "string.new_utf8_array"
StringNewWtf16Array => "string.new_wtf16_array"
StringEncodeUtf8Array => "string.encode_utf8_array"
StringEncodeWtf16Array => "string.encode_wtf16_array"
StringNewLossyUtf8Array => "string.new_lossy_utf8_array"
StringNewWtf8Array => "string.new_wtf8_array"
StringEncodeLossyUtf8Array => "string.encode_lossy_utf8_array"
StringEncodeWtf8Array => "string.encode_wtf8_array"
StructNew(idx) => "struct.new \{index_text(idx)}"
StructNewDefault(idx) => "struct.new_default \{index_text(idx)}"
StructGet(idx, field_idx) => "struct.get \{index_text(idx)} \{field_idx}"
StructGetS(idx, field_idx) => "struct.get_s \{index_text(idx)} \{field_idx}"
StructGetU(idx, field_idx) => "struct.get_u \{index_text(idx)} \{field_idx}"
ArrayNew(idx) => "array.new \{index_text(idx)}"
ArrayNewDefault(idx) => "array.new_default \{index_text(idx)}"
ArrayNewFixed(idx, len) => "array.new_fixed \{index_text(idx)} \{len}"
ArrayNewData(idx, data_idx) =>
"array.new_data \{index_text(idx)} \{index_text(data_idx)}"
ArrayNewElem(idx, elem_idx) =>
"array.new_elem \{index_text(idx)} \{index_text(elem_idx)}"
MemoryAtomic(op, order, memory, ma) =>
render_memory_atomic(op, order, memory, ma)
AtomicFence(order) => "atomic.fence \{atomic_order_text(order)}"
WaitqueueNew => "waitqueue.new"
WaitqueueNotify => "waitqueue.notify"
StructWait(idx, field_idx) => "struct.wait \{index_text(idx)} \{field_idx}"
Publish => "publish"
StructAtomicSet(order, idx, field_idx) =>
"struct.atomic.set \{atomic_order_text(order)} \{index_text(idx)} \{field_idx}"
ArrayAtomicSet(order, idx) =>
"array.atomic.set \{atomic_order_text(order)} \{index_text(idx)}"
StructAtomicGet(order, idx, field_idx) =>
"struct.atomic.get \{atomic_order_text(order)} \{index_text(idx)} \{field_idx}"
StructAtomicGetS(order, idx, field_idx) =>
"struct.atomic.get_s \{atomic_order_text(order)} \{index_text(idx)} \{field_idx}"
StructAtomicGetU(order, idx, field_idx) =>
"struct.atomic.get_u \{atomic_order_text(order)} \{index_text(idx)} \{field_idx}"
StructAtomicRmw(order, op, idx, field_idx) =>
"struct.atomic.rmw.\{aggregate_atomic_rmw_suffix(op)}\{atomic_order_pair_text(order)} \{index_text(idx)} \{field_idx}"
StructAtomicCmpxchg(order, idx, field_idx) =>
"struct.atomic.rmw.cmpxchg\{atomic_order_pair_text(order)} \{index_text(idx)} \{field_idx}"
ArrayAtomicGet(order, idx) =>
"array.atomic.get \{atomic_order_text(order)} \{index_text(idx)}"
ArrayAtomicGetS(order, idx) =>
"array.atomic.get_s \{atomic_order_text(order)} \{index_text(idx)}"
ArrayAtomicGetU(order, idx) =>
"array.atomic.get_u \{atomic_order_text(order)} \{index_text(idx)}"
ArrayAtomicRmw(order, op, idx) =>
"array.atomic.rmw.\{aggregate_atomic_rmw_suffix(op)}\{atomic_order_pair_text(order)} \{index_text(idx)}"
ArrayAtomicCmpxchg(order, idx) =>
"array.atomic.rmw.cmpxchg\{atomic_order_pair_text(order)} \{index_text(idx)}"
StructNewDesc(idx) => "struct.new_desc \{index_text(idx)}"
StructNewDefaultDesc(idx) => "struct.new_default_desc \{index_text(idx)}"
RefGetDesc(idx) => "ref.get_desc \{index_text(idx)}"
RefTestDesc(false, idx) => "ref.test_desc \{index_text(idx)}"
RefTestDesc(true, idx) => "ref.test_desc_null \{index_text(idx)}"
RefCastDescEq(false, idx) => "ref.cast_desc_eq \{index_text(idx)}"
RefCastDescEq(true, idx) => "ref.cast_desc_eq_null \{index_text(idx)}"
V128Const(v) => render_v128_const(v)
V128Load(ma) => "v128.load\{render_memarg(ma)}"
V128Load8x8S(ma) => "v128.load8x8_s\{render_memarg(ma)}"
V128Load8x8U(ma) => "v128.load8x8_u\{render_memarg(ma)}"
V128Load16x4S(ma) => "v128.load16x4_s\{render_memarg(ma)}"
V128Load16x4U(ma) => "v128.load16x4_u\{render_memarg(ma)}"
V128Load32x2S(ma) => "v128.load32x2_s\{render_memarg(ma)}"
V128Load32x2U(ma) => "v128.load32x2_u\{render_memarg(ma)}"
V128Load8Splat(ma) => "v128.load8_splat\{render_memarg(ma)}"
V128Load16Splat(ma) => "v128.load16_splat\{render_memarg(ma)}"
V128Load32Splat(ma) => "v128.load32_splat\{render_memarg(ma)}"
V128Load64Splat(ma) => "v128.load64_splat\{render_memarg(ma)}"
V128Load32Zero(ma) => "v128.load32_zero\{render_memarg(ma)}"
V128Load64Zero(ma) => "v128.load64_zero\{render_memarg(ma)}"
V128Store(ma) => "v128.store\{render_memarg(ma)}"
V128Load8Lane(ma, lane) => "v128.load8_lane\{render_memarg(ma)} \{lane}"
V128Load16Lane(ma, lane) => "v128.load16_lane\{render_memarg(ma)} \{lane}"
V128Load32Lane(ma, lane) => "v128.load32_lane\{render_memarg(ma)} \{lane}"
V128Load64Lane(ma, lane) => "v128.load64_lane\{render_memarg(ma)} \{lane}"
V128Store8Lane(ma, lane) => "v128.store8_lane\{render_memarg(ma)} \{lane}"
V128Store16Lane(ma, lane) => "v128.store16_lane\{render_memarg(ma)} \{lane}"
V128Store32Lane(ma, lane) => "v128.store32_lane\{render_memarg(ma)} \{lane}"
V128Store64Lane(ma, lane) => "v128.store64_lane\{render_memarg(ma)} \{lane}"
I8x16Shuffle(lanes) => "i8x16.shuffle \{render_shuffle_lanes(lanes)}"
I8x16Swizzle => "i8x16.swizzle"
I8x16Splat => "i8x16.splat"
I16x8Splat => "i16x8.splat"
I32x4Splat => "i32x4.splat"
I64x2Splat => "i64x2.splat"
F32x4Splat => "f32x4.splat"
F64x2Splat => "f64x2.splat"
I8x16ExtractLaneS(lane) => "i8x16.extract_lane_s \{lane}"
I8x16ExtractLaneU(lane) => "i8x16.extract_lane_u \{lane}"
I8x16ReplaceLane(lane) => "i8x16.replace_lane \{lane}"
I16x8ExtractLaneS(lane) => "i16x8.extract_lane_s \{lane}"
I16x8ExtractLaneU(lane) => "i16x8.extract_lane_u \{lane}"
I16x8ReplaceLane(lane) => "i16x8.replace_lane \{lane}"
I32x4ExtractLane(lane) => "i32x4.extract_lane \{lane}"
I32x4ReplaceLane(lane) => "i32x4.replace_lane \{lane}"
I64x2ExtractLane(lane) => "i64x2.extract_lane \{lane}"
I64x2ReplaceLane(lane) => "i64x2.replace_lane \{lane}"
F32x4ExtractLane(lane) => "f32x4.extract_lane \{lane}"
F32x4ReplaceLane(lane) => "f32x4.replace_lane \{lane}"
F64x2ExtractLane(lane) => "f64x2.extract_lane \{lane}"
F64x2ReplaceLane(lane) => "f64x2.replace_lane \{lane}"
SimdUnary(op) => keyword_from_opcode(op)
SimdBinary(op) => keyword_from_opcode(op)
SimdTernary(op) => keyword_from_opcode(op)
SimdShift(op) => keyword_from_opcode(op)
SimdTest(op) => keyword_from_opcode(op)
SimdCompare(op) => keyword_from_opcode(op)
SimdConvert(op) => keyword_from_opcode(op)
SimdRelaxed(op) => keyword_from_opcode(op)
Block(_, _)
| Loop(_, _)
| If(_, _, _)
| TryTable(_, _, _)
| TryLegacy(_, _, _, _) =>
raise Unsupported(
"structured instructions must be rendered with body context",
)
}
}
///|
fn write_legacy_catch_clause(
out : StringBuilder,
clause : LegacyCatchClause,
indent : Int32,
) -> Unit raise WastTextError {
let pad = " ".repeat(indent)
match clause {
LegacyCatch(tag_idx, body) => {
out.write_string("\{pad}(catch \{index_text(tag_idx)}\n")
write_instructions(out, body, indent + 2)
out.write_string("\{pad})\n")
}
LegacyCatchAll(body) => {
out.write_string("\{pad}(catch_all\n")
write_instructions(out, body, indent + 2)
out.write_string("\{pad})\n")
}
}
}
///|
fn write_instruction(
out : StringBuilder,
instr : Instruction,
indent : Int32,
) -> Unit raise WastTextError {
let pad = " ".repeat(indent)
match instr {
Block(bt, body) => {
out.write_string("\{pad}block\{render_block_type(bt)}\n")
write_instructions(out, body, indent + 2)
out.write_string("\{pad}end\n")
}
Loop(bt, body) => {
out.write_string("\{pad}loop\{render_block_type(bt)}\n")
write_instructions(out, body, indent + 2)
out.write_string("\{pad}end\n")
}
If(bt, then_body, else_body) => {
out.write_string("\{pad}if\{render_block_type(bt)}\n")
write_instructions(out, then_body, indent + 2)
if else_body.length() > 0 {
out.write_string("\{pad}else\n")
write_instructions(out, else_body, indent + 2)
}
out.write_string("\{pad}end\n")
}
TryTable(bt, catches, body) => {
out.write_string("\{pad}try_table\{render_block_type(bt)}")
for catch_clause in catches {
out.write_char(' ')
out.write_string(render_catch_clause(catch_clause))
}
out.write_char('\n')
write_instructions(out, body, indent + 2)
out.write_string("\{pad}end\n")
}
TryLegacy(bt, body, catches, delegate_target) => {
out.write_string("\{pad}try\{render_block_type(bt)}\n")
out.write_string("\{pad} (do\n")
write_instructions(out, body, indent + 4)
out.write_string("\{pad} )\n")
for clause in catches {
write_legacy_catch_clause(out, clause, indent + 2)
}
if delegate_target is Some(idx) {
out.write_string("\{pad} (delegate \{index_text(idx)})\n")
}
out.write_string("\{pad}end\n")
}
_ => {
out.write_string(pad)
out.write_string(render_instruction_head(instr))
out.write_char('\n')
}
}
}
///|
fn write_instructions(
out : StringBuilder,
instrs : Array[Instruction],
indent : Int32,
) -> Unit raise WastTextError {
for instr in instrs {
write_instruction(out, instr, indent)
}
}
///|
fn write_inline_exports(
out : StringBuilder,
exports : Array[InlineExport],
) -> Unit {
for ex in exports {
match ex {
Export(name) => out.write_string(" (export \{quote_wast_string(name)})")
}
}
}
///|
fn write_annotations(
out : StringBuilder,
annotations : Array[Annotation],
pad : String,
) -> Unit {
for ann in annotations {
out.write_string("\{pad}(@")
out.write_string(ann.name)
for arg in ann.args {
out.write_char(' ')
out.write_string(arg)
}
out.write_string(")\n")
}
}
///|
fn is_default_elem_offset(offset : Array[Instruction]?) -> Bool {
match offset {
Some([Const(I32Const, "0")]) => true
_ => false
}
}
///|
fn write_elem_abbrev_offset(
out : StringBuilder,
offset : Array[Instruction]?,
) -> Unit raise WastTextError {
match offset {
Some(instrs) => {
if instrs.length() == 0 {
return
}
out.write_string(" (")
let mut i = 0
while i < instrs.length() {
if i > 0 {
out.write_char(' ')
}
out.write_string(render_instruction_head(instrs[i]))
i += 1
}
out.write_char(')')
}
None => ()
}
}
///|
fn write_elem_item_inline(
out : StringBuilder,
item : ElemInitExpr,
) -> Unit raise WastTextError {
if item.explicit_item || item.instrs.length() != 1 {
out.write_string("(item")
for instr in item.instrs {
out.write_string(" (")
out.write_string(render_instruction_head(instr))
out.write_char(')')
}
out.write_char(')')
} else {
out.write_char('(')
out.write_string(render_instruction_head(item.instrs[0]))
out.write_char(')')
}
}
///|
fn write_type_field(out : StringBuilder, td : TypeDef, pad : String) -> Unit {
out.write_string("\{pad}(type")
if td.id is Some(id) {
out.write_char(' ')
out.write_string(id)
}
if td.is_sub {
out.write_string(" (sub")
if td.final_ {
out.write_string(" final")
}
for super_idx in td.supers {
out.write_char(' ')
out.write_string(index_text(super_idx))
}
out.write_string(render_type_metadata(td.metadata))
out.write_char(' ')
out.write_string(render_type_def_body(td.body))
out.write_string("))\n")
} else {
out.write_string(render_type_metadata(td.metadata))
out.write_char(' ')
out.write_string(render_type_def_body(td.body))
out.write_string(")\n")
}
}
///|
fn write_module_field(
out : StringBuilder,
field : ModuleField,
indent : Int32,
) -> Unit raise WastTextError {
let pad = " ".repeat(indent)
match field {
TypeField(td) => write_type_field(out, td, pad)
RecField(types) =>
if types.length() == 0 {
out.write_string("\{pad}(rec)\n")
} else {
out.write_string("\{pad}(rec\n")
let child_pad = " ".repeat(indent + 2)
for td in types {
write_type_field(out, td, child_pad)
}
out.write_string("\{pad})\n")
}
FuncField(func) => {
write_annotations(out, func.annotations, pad)
out.write_string("\{pad}(func")
if func.id is Some(id) {
out.write_char(' ')
out.write_string(id)
}
write_inline_exports(out, func.exports)
out.write_string(render_type_use(func.type_use))
for loc in func.locals {
out.write_string(" (local")
if loc.id is Some(id) {
out.write_char(' ')
out.write_string(id)
}
out.write_char(' ')
out.write_string(keyword_from_value_type(loc.val_type))
out.write_char(')')
}
out.write_char('\n')
write_instructions(out, func.body, indent + 2)
out.write_string("\{pad})\n")
}
TableField(tbl) => {
out.write_string("\{pad}(table")
if tbl.id is Some(id) {
out.write_char(' ')
out.write_string(id)
}
write_inline_exports(out, tbl.exports)
match tbl.elem_init {
Some(func_indices) => {
let elem_exprs = tbl.elem_exprs.unwrap_or([])
let use_short_form = tbl.table_type.limits.max is None &&
tbl.table_type.limits.min ==
(func_indices.length() + elem_exprs.length()).reinterpret_as_uint()
if use_short_form {
out.write_string(
" \{keyword_from_value_type(tbl.table_type.elem_type)} (elem",
)
} else {
out.write_string(" \{tbl.table_type.limits.min}")
if tbl.table_type.limits.max is Some(max) {
out.write_string(" \{max}")
}
out.write_string(
" \{keyword_from_value_type(tbl.table_type.elem_type)} (elem",
)
}
if !is_default_elem_offset(tbl.elem_offset) {
write_elem_abbrev_offset(out, tbl.elem_offset)
}
match tbl.elem_init_type {
Some(vt) => out.write_string(" \{keyword_from_value_type(vt)}")
None => ()
}
for fidx in func_indices {
out.write_char(' ')
out.write_string(index_text(fidx))
}
for item in elem_exprs {
out.write_char(' ')
write_elem_item_inline(out, item)
}
out.write_string("))\n")
}
None => {
out.write_string(" \{tbl.table_type.limits.min}")
if tbl.table_type.limits.max is Some(max) {
out.write_string(" \{max}")
}
out.write_string(
" \{keyword_from_value_type(tbl.table_type.elem_type)})\n",
)
}
}
}
MemoryField(mem) => {
out.write_string("\{pad}(memory")
if mem.id is Some(id) {
out.write_char(' ')
out.write_string(id)
}
write_inline_exports(out, mem.exports)
let lim = mem.memory_type
if lim.memory64 {
out.write_string(" i64")
}
out.write_string(" \{lim.min}")
if lim.max is Some(max) {
out.write_string(" \{max}")
}
if lim.shared {
out.write_string(" shared")
}
out.write_string(")\n")
}
GlobalField(global) => {
out.write_string("\{pad}(global")
if global.id is Some(id) {
out.write_char(' ')
out.write_string(id)
}
write_inline_exports(out, global.exports)
if global.global_type.mutable_ {
out.write_string(
" (mut \{keyword_from_value_type(global.global_type.val_type)})",
)
} else {
out.write_string(
" \{keyword_from_value_type(global.global_type.val_type)}",
)
}
out.write_char('\n')
write_instructions(out, global.init, indent + 2)
out.write_string("\{pad})\n")
}
TagField(tag) => {
out.write_string("\{pad}(tag")
if tag.id is Some(id) {
out.write_char(' ')
out.write_string(id)
}
write_inline_exports(out, tag.exports)
out.write_string(render_type_use(tag.type_use))
out.write_string(")\n")
}
ImportField(im) => {
write_annotations(out, im.annotations, pad)
out.write_string(
"\{pad}(import \{quote_wast_string(im.module_name)} \{quote_wast_string(im.field_name)} (",
)
match im.desc {
Func(type_use) => {
out.write_string("func")
if im.id is Some(id) {
out.write_char(' ')
out.write_string(id)
}
out.write_string(render_type_use(type_use))
}
Table(tt) => {
out.write_string("table")
if im.id is Some(id) {
out.write_char(' ')
out.write_string(id)
}
out.write_string(" \{tt.limits.min}")
if tt.limits.max is Some(max) {
out.write_string(" \{max}")
}
out.write_string(" \{keyword_from_value_type(tt.elem_type)}")
}
Memory(mt) => {
out.write_string("memory")
if im.id is Some(id) {
out.write_char(' ')
out.write_string(id)
}
let lim = mt
if lim.memory64 {
out.write_string(" i64")
}
out.write_string(" \{lim.min}")
if lim.max is Some(max) {
out.write_string(" \{max}")
}
if lim.shared {
out.write_string(" shared")
}
}
Global(gt) => {
out.write_string("global")
if im.id is Some(id) {
out.write_char(' ')
out.write_string(id)
}
if gt.mutable_ {
out.write_string(" (mut \{keyword_from_value_type(gt.val_type)})")
} else {
out.write_string(" \{keyword_from_value_type(gt.val_type)}")
}
}
Tag(type_use) => {
out.write_string("tag")
if im.id is Some(id) {
out.write_char(' ')
out.write_string(id)
}
out.write_string(render_type_use(type_use))
}
}
out.write_string("))\n")
}
ExportField(ex) => {
out.write_string("\{pad}(export \{quote_wast_string(ex.name)} (")
match ex.desc {
Func(idx) => out.write_string("func \{index_text(idx)}")
Table(idx) => out.write_string("table \{index_text(idx)}")
Memory(idx) => out.write_string("memory \{index_text(idx)}")
Global(idx) => out.write_string("global \{index_text(idx)}")
Tag(idx) => out.write_string("tag \{index_text(idx)}")
}
out.write_string("))\n")
}
StartField(start) =>
out.write_string("\{pad}(start \{index_text(start.func_index)})\n")
ElemField(elem) => {
out.write_string("\{pad}(elem")
if elem.id is Some(id) {
out.write_char(' ')
out.write_string(id)
}
if elem.declarative {
out.write_string(" declare")
if elem.elem_type is None {
out.write_string(" func")
}
}
if elem.offset.length() > 0 {
out.write_char(' ')
out.write_string(index_text(elem.table_index))
out.write_string(" (offset\n")
write_instructions(out, elem.offset, indent + 4)
out.write_string("\{pad} )")
}
match elem.elem_type {
Some(vt) => {
out.write_char(' ')
out.write_string(keyword_from_value_type(vt))
}
None => ()
}
for idx in elem.func_indices {
out.write_char(' ')
out.write_string(index_text(idx))
}
for item in elem.elem_exprs {
out.write_char(' ')
write_elem_item_inline(out, item)
}
out.write_string(")\n")
}
DataField(data) => {
out.write_string("\{pad}(data")
if data.id is Some(id) {
out.write_char(' ')
out.write_string(id)
}
out.write_char(' ')
out.write_string(index_text(data.memory_index))
if data.offset.length() > 0 {
out.write_string(" (offset\n")
write_instructions(out, data.offset, indent + 4)
out.write_string("\{pad} )")
}
out.write_char(' ')
out.write_string(quote_wast_bytes(data.data))
out.write_string(")\n")
}
}
}
///|
fn render_module(mod : Module) -> String raise WastTextError {
let out = StringBuilder::new()
out.write_string("(module")
if mod.id is Some(id) {
out.write_char(' ')
out.write_string(id)
}
out.write_char('\n')
for field in mod.fields {
write_module_field(out, field, 2)
}
out.write_string(")\n")
out.to_string()
}
///|
fn render_wast_value(v : WastValue) -> String {
match v {
I32(x) => "(i32.const \{x})"
I64(x) => "(i64.const \{x})"
F32(x) => "(f32.const \{x})"
F64(x) => "(f64.const \{x})"
RefNull(vt) => "(ref.null \{keyword_from_ref_null_immediate(vt)})"
RefFunc => "(ref.func 0)"
}
}
///|
fn render_wast_result(r : WastResult) -> String {
match r {
Value(v) => render_wast_value(v)
F32NanCanonical => "(f32.const nan:canonical)"
F32NanArithmetic => "(f32.const nan:arithmetic)"
F64NanCanonical => "(f64.const nan:canonical)"
F64NanArithmetic => "(f64.const nan:arithmetic)"
RefNull(None) => "(ref.null)"
RefNull(Some(vt)) => "(ref.null \{keyword_from_ref_null_immediate(vt)})"
RefFunc => "(ref.func 0)"
}
}
///|
fn render_action(a : WastActionType) -> String {
match a {
Invoke(act) => {
let out = StringBuilder::new()
out.write_string("(invoke")
if act.module_name is Some(m) {
out.write_char(' ')
out.write_string(m)
}
out.write_char(' ')
out.write_string(quote_wast_string(act.name))
for arg in act.args {
out.write_char(' ')
out.write_string(render_wast_value(arg))
}
out.write_char(')')
out.to_string()
}
Get(module_name, global_name) => {
let out = StringBuilder::new()
out.write_string("(get")
if module_name is Some(m) {
out.write_char(' ')
out.write_string(m)
}
out.write_char(' ')
out.write_string(quote_wast_string(global_name))
out.write_char(')')
out.to_string()
}
}
}
///|
fn render_command(cmd : WastCommand) -> String raise WastTextError {
match cmd {
Module(mod) => render_module(mod)
ModuleBinary(id, bytes) => {
let out = StringBuilder::new()
out.write_string("(module")
if id is Some(x) {
out.write_char(' ')
out.write_string(x)
}
out.write_string(" binary ")
out.write_string(quote_wast_bytes(bytes))
out.write_string(")\n")
out.to_string()
}
ModuleQuote(id, strings) => {
let out = StringBuilder::new()
out.write_string("(module")
if id is Some(x) {
out.write_char(' ')
out.write_string(x)
}
out.write_string(" quote")
for s in strings {
out.write_char(' ')
out.write_string(quote_wast_string(s))
}
out.write_string(")\n")
out.to_string()
}
Register(name, module_id) => {
let out = StringBuilder::new()
out.write_string("(register ")
out.write_string(quote_wast_string(name))
if module_id is Some(id) {
out.write_char(' ')
out.write_string(id)
}
out.write_string(")\n")
out.to_string()
}
Action(action) => "\{render_action(action)}\n"
AssertReturn(action, results) => {
let out = StringBuilder::new()
out.write_string("(assert_return ")
out.write_string(render_action(action))
for r in results {
out.write_char(' ')
out.write_string(render_wast_result(r))
}
out.write_string(")\n")
out.to_string()
}
AssertTrap(action, msg) =>
"(assert_trap \{render_action(action)} \{quote_wast_string(msg)})\n"
AssertException(action) => "(assert_exception \{render_action(action)})\n"
AssertExhaustion(action, msg) =>
"(assert_exhaustion \{render_action(action)} \{quote_wast_string(msg)})\n"
AssertMalformed(mod_def, msg) =>
"(assert_malformed \{render_module_def(mod_def)} \{quote_wast_string(msg)})\n"
AssertInvalid(mod_def, msg) =>
"(assert_invalid \{render_module_def(mod_def)} \{quote_wast_string(msg)})\n"
AssertUnlinkable(mod_def, msg) =>
"(assert_unlinkable \{render_module_def(mod_def)} \{quote_wast_string(msg)})\n"
}
}
///|
fn render_module_def(mod_def : WastModuleDef) -> String raise WastTextError {
match mod_def {
Inline(mod) => render_module(mod).trim().to_owned()
Binary(bytes) => "(module binary \{quote_wast_bytes(bytes)})"
Quote(lines) => {
let out = StringBuilder::new()
out.write_string("(module quote")
for line in lines {
out.write_char(' ')
out.write_string(quote_wast_string(line))
}
out.write_char(')')
out.to_string()
}
}
}
///|
fn render_script(script : WastScript) -> String raise WastTextError {
let out = StringBuilder::new()
for cmd in script.commands {
out.write_string(render_command(cmd))
}
out.to_string()
}
///|
pub fn module_to_wast(mod : Module) -> Result[String, String] {
Result::Ok(render_module(mod)) catch {
WastTextError::Unsupported(msg) => Err(msg)
}
}
///|
pub fn module_to_wast_with_context(
mod : Module,
ctx : PrettyPrintContext,
) -> Result[String, String] {
match module_to_wast(mod) {
Ok(text) => Ok(apply_pretty_context(text, ctx))
Err(msg) => Err(msg)
}
}
///|
pub fn script_to_wast(script : WastScript) -> Result[String, String] {
Result::Ok(render_script(script)) catch {
WastTextError::Unsupported(msg) => Err(msg)
}
}
///|
pub fn script_to_wast_with_context(
script : WastScript,
ctx : PrettyPrintContext,
) -> Result[String, String] {
match script_to_wast(script) {
Ok(text) => Ok(apply_pretty_context(text, ctx))
Err(msg) => Err(msg)
}
}
///|
pub impl Show for Module with fn output(self, logger) {
match module_to_wast(self) {
Ok(text) => logger.write_string(text)
Err(msg) => {
logger.write_string("(module ;; ")
logger.write_string(msg)
logger.write_string(")")
}
}
}
///|
pub impl Show for WastScript with fn output(self, logger) {
match script_to_wast(self) {
Ok(text) => logger.write_string(text)
Err(msg) => {
logger.write_string(";; ")
logger.write_string(msg)
}
}
}
///|
pub fn wast_to_module(
source : String,
filename~ : String = "",
) -> Result[Module, String] {
let lexer = WastLexer::new(string_to_bytes_for_parser(source), filename)
let parser = WastParser::new(lexer)
try {
let module_ = parser.parse_module()
if !parser.check(Eof) {
raise ParserError::UnexpectedToken(
parser.current,
"expected end of input",
)
}
Result::Ok(module_)
} catch {
ParserError::UnexpectedToken(tok, msg) =>
Err(
"\{msg} at \{tok.location.filename}:\{tok.location.line}:\{tok.location.first_column}",
)
ParserError::InvalidInstruction(msg) => Err(msg)
ParserError::Custom(msg) => Err(msg)
}
}
///|
pub fn wast_to_script(
source : String,
filename~ : String = "",
) -> Result[WastScript, String] {
let lexer = WastLexer::new(string_to_bytes_for_parser(source), filename)
let parser = WastParser::new(lexer)
Result::Ok(parser.parse_script()) catch {
ParserError::UnexpectedToken(tok, msg) =>
Err(
"\{msg} at \{tok.location.filename}:\{tok.location.line}:\{tok.location.first_column}",
)
ParserError::InvalidInstruction(msg) => Err(msg)
ParserError::Custom(msg) => Err(msg)
}
}
///|
fn atomic_order_pair_text(order : AtomicOrder) -> String {
match order {
SeqCst => ""
_ => " \{atomic_order_text(order)} \{atomic_order_text(order)}"
}
}