// ============================================================================
// AST Types
// ============================================================================
///|
pub enum Index {
Num(UInt32)
Id(String)
} derive(Eq, Debug)
///|
pub impl Show for Index with fn output(self, logger) {
match self {
Num(val) => {
logger.write_string("Num(")
val.output(logger)
logger.write_char(')')
}
Id(val) => {
logger.write_string("Id(")
val.output(logger)
logger.write_char(')')
}
}
}
///|
pub struct Limits {
min : UInt32
max : UInt32?
} derive(Eq, Debug)
///|
pub impl Show for Limits with fn output(self, logger) {
logger.write_string("Limits {min: ")
self.min.output(logger)
logger.write_string(", max:")
logger.write_string(@debug.to_string(self.max))
logger.write_char('}')
}
///|
pub struct FuncType {
params : Array[ValueType]
results : Array[ValueType]
} derive(Eq, Debug)
///|
pub struct TypeUse {
type_index : Index?
func_type : FuncType
param_ids : Array[String?]
} derive(Eq, Debug)
///|
pub struct Annotation {
name : String
args : Array[String]
} derive(Eq, Debug)
///|
pub enum InlineExport {
Export(String)
} derive(Eq, Debug)
///|
pub enum ImportDesc {
Func(TypeUse)
Table(TableType)
Memory(MemoryType)
Global(GlobalType)
Tag(TypeUse)
} derive(Eq, Debug)
///|
pub struct Import {
module_name : String
field_name : String
desc : ImportDesc
id : String?
annotations : Array[Annotation]
} derive(Eq, Debug)
///|
pub enum ExportDesc {
Func(Index)
Table(Index)
Memory(Index)
Global(Index)
Tag(Index)
} derive(Eq, Debug)
///|
pub struct Export {
name : String
desc : ExportDesc
} derive(Eq, Debug)
///|
pub struct TableType {
limits : Limits
elem_type : ValueType
} derive(Eq, Debug)
///|
pub struct MemoryType {
min : UInt64
max : UInt64?
shared : Bool
memory64 : Bool
} derive(Eq, Debug)
///|
pub fn MemoryType::new(
min : UInt64,
max~ : UInt64? = None,
shared~ : Bool = false,
memory64~ : Bool = false,
) -> MemoryType {
{ min, max, shared, memory64, }
}
///|
pub struct GlobalType {
val_type : ValueType
mutable_ : Bool
} derive(Eq, Debug)
///|
pub struct Local {
id : String?
val_type : ValueType
} derive(Eq, Debug)
///|
pub enum AtomicOrder {
SeqCst
AcqRel
Relaxed
} derive(Eq, Debug)
///|
pub struct Func {
id : String?
annotations : Array[Annotation]
exports : Array[InlineExport]
type_use : TypeUse
locals : Array[Local]
body : Array[Instruction]
} derive(Eq, Debug)
///|
pub struct Global {
id : String?
exports : Array[InlineExport]
global_type : GlobalType
init : Array[Instruction]
} derive(Eq, Debug)
///|
pub struct Table {
id : String?
exports : Array[InlineExport]
table_type : TableType
elem_init : Array[Index]?
elem_exprs : Array[ElemInitExpr]?
elem_init_type : ValueType?
elem_offset : Array[Instruction]?
} derive(Eq, Debug)
///|
pub struct Memory {
id : String?
exports : Array[InlineExport]
memory_type : MemoryType
} derive(Eq, Debug)
///|
pub struct Tag {
id : String?
exports : Array[InlineExport]
type_use : TypeUse
} derive(Eq, Debug)
///|
pub enum FieldStorage {
Value(ValueType)
I8
I16
} derive(Eq, Debug)
///|
pub struct StructFieldDef {
storage : FieldStorage
mutable_ : Bool
} derive(Eq, Debug)
///|
pub enum TypeDefBody {
Func(FuncType)
Struct(Array[StructFieldDef])
Array(StructFieldDef)
Shared(TypeDefBody)
} derive(Eq, Debug)
///|
pub struct TypeDefMetadata {
describes : Index?
descriptor : Index?
} derive(Eq, Debug)
///|
pub struct TypeDef {
id : String?
is_sub : Bool
final_ : Bool
supers : Array[Index]
metadata : TypeDefMetadata
body : TypeDefBody
} derive(Eq, Debug)
///|
pub struct DataSegment {
id : String?
memory_index : Index
offset : Array[Instruction]
data : Bytes
} derive(Eq, Debug)
///|
pub struct ElemSegment {
id : String?
declarative : Bool
table_index : Index
offset : Array[Instruction]
func_indices : Array[Index]
elem_exprs : Array[ElemInitExpr]
elem_type : ValueType?
} derive(Eq, Debug)
///|
pub struct ElemInitExpr {
instrs : Array[Instruction]
explicit_item : Bool
} derive(Eq, Debug)
///|
pub struct Start {
func_index : Index
} derive(Eq, Debug)
///|
pub enum ModuleField {
TypeField(TypeDef)
RecField(Array[TypeDef])
FuncField(Func)
TableField(Table)
MemoryField(Memory)
GlobalField(Global)
TagField(Tag)
ImportField(Import)
ExportField(Export)
StartField(Start)
ElemField(ElemSegment)
DataField(DataSegment)
} derive(Eq, Debug)
///|
pub struct Module {
id : String?
fields : Array[ModuleField]
} derive(Eq, Debug)
///|
pub fn Module::new(fields : Array[ModuleField], id~ : String? = None) -> Module {
{ id, fields, }
}
// ============================================================================
// Instruction AST
// ============================================================================
///|
pub struct MemArg {
align : UInt64
offset : UInt64
} derive(Eq, Debug)
///|
pub struct BlockType {
label : String?
type_use : TypeUse?
} derive(Eq, Debug)
// ============================================================================
// instruction.mbt - Extended Instruction AST for SIMD
// ============================================================================
///|
pub struct V128Const {
shape : SimdShape
values : Array[String]
} derive(Eq, Debug)
///|
pub enum SimdShape {
I8x16
I16x8
I32x4
I64x2
F32x4
F64x2
} derive(Eq, Debug)
///|
pub impl Show for SimdShape with fn output(self, logger) {
match self {
I8x16 => logger.write_string("I8x16")
I16x8 => logger.write_string("I16x8")
I32x4 => logger.write_string("I32x4")
I64x2 => logger.write_string("I64x2")
F32x4 => logger.write_string("F32x4")
F64x2 => logger.write_string("F64x2")
}
}
///|
pub struct ShuffleLanes {
lanes : FixedArray[UInt32] // 16 lane indices
} derive(Eq, Debug)
///|
pub impl Show for ShuffleLanes with fn output(self, logger) {
logger.write_string("ShuffleLanes {lanes: ")
self.lanes.output(logger)
logger.write_string("}")
}
///|
pub enum CatchClause {
CatchClause(Index, Index)
CatchRefClause(Index, Index)
CatchAllClause(Index)
CatchAllRefClause(Index)
} derive(Eq, Debug)
///|
pub impl Show for CatchClause with fn output(self, logger) {
match self {
CatchClause(idx1, idx2) => {
logger.write_string("CatchClause(")
idx1.output(logger)
logger.write_string(", ")
idx2.output(logger)
logger.write_char(')')
}
CatchRefClause(idx1, idx2) => {
logger.write_string("CatchRefClause(")
idx1.output(logger)
logger.write_string(", ")
idx2.output(logger)
logger.write_char(')')
}
CatchAllClause(idx) => {
logger.write_string("CatchAllClause(")
idx.output(logger)
logger.write_char(')')
}
CatchAllRefClause(idx) => {
logger.write_string("CatchAllRefClause(")
idx.output(logger)
logger.write_char(')')
}
}
}
///|
pub enum LegacyCatchClause {
LegacyCatch(Index, Array[Instruction])
LegacyCatchAll(Array[Instruction])
} derive(Eq, Debug)
///|
pub enum Instruction {
// Control
Unreachable
Nop
Block(BlockType, Array[Instruction])
Loop(BlockType, Array[Instruction])
If(BlockType, Array[Instruction], Array[Instruction])
Br(Index)
BrIf(Index)
BrOnNull(Index)
BrOnNonNull(Index)
BrOnCastDescEq(Index, ValueType, ValueType)
BrOnCastDescEqFail(Index, ValueType, ValueType)
BrTable(Array[Index], Index)
Return
Call(Index)
CallIndirect(Index, TypeUse)
ReturnCall(Index)
ReturnCallIndirect(Index, TypeUse)
CallRef(TypeUse)
ReturnCallRef(TypeUse)
Throw(Index)
ThrowRef
Rethrow(Index)
TryTable(BlockType, Array[CatchClause], Array[Instruction])
TryLegacy(BlockType, Array[Instruction], Array[LegacyCatchClause], Index?)
// Parametric
Drop
Select(Array[ValueType]?)
// Variable
LocalGet(Index)
LocalSet(Index)
LocalTee(Index)
GlobalGet(Index)
GlobalSet(Index)
// Table
TableGet(Index)
TableSet(Index)
TableSize(Index)
TableGrow(Index)
TableFill(Index)
TableCopy(Index, Index)
TableInit(Index, Index)
ElemDrop(Index)
// Memory
MemoryAtomic(MemoryAtomicOp, AtomicOrder, Index?, MemArg)
AtomicFence(AtomicOrder)
Load(Opcode, MemArg)
Store(Opcode, MemArg)
ArrayLoad(Opcode, Index, MemArg)
ArrayStore(Opcode, Index, MemArg)
MemorySize
MemoryGrow
MemoryFill
MemoryCopy
MemoryInit(Index)
DataDrop(Index)
// Numeric
Const(Opcode, String)
Unary(Opcode)
Binary(Opcode)
Test(Opcode)
Compare(Opcode)
Convert(Opcode)
// Reference
RefNull(ValueType)
RefIsNull
RefFunc(Index)
StringConst(Bytes)
StringMeasureWtf16
StringConcat
StringEq
StringAsWtf16
StringViewWtf16GetCodeunit
StringViewWtf16Slice
StringNewUtf8Array
StringNewWtf16Array
StringEncodeUtf8Array
StringEncodeWtf16Array
StringNewLossyUtf8Array
StringNewWtf8Array
StringEncodeLossyUtf8Array
StringEncodeWtf8Array
StructNew(Index)
StructNewDefault(Index)
ArrayNew(Index)
ArrayNewDefault(Index)
ArrayNewFixed(Index, UInt32)
ArrayNewData(Index, Index)
ArrayNewElem(Index, Index)
StructGet(Index, UInt32)
StructGetS(Index, UInt32)
StructGetU(Index, UInt32)
WaitqueueNew
WaitqueueNotify
StructWait(Index, UInt32)
Publish
StructAtomicSet(AtomicOrder, Index, UInt32)
ArrayAtomicSet(AtomicOrder, Index)
StructAtomicGet(AtomicOrder, Index, UInt32)
StructAtomicGetS(AtomicOrder, Index, UInt32)
StructAtomicGetU(AtomicOrder, Index, UInt32)
StructAtomicRmw(AtomicOrder, @lib.AggregateAtomicRmwOp, Index, UInt32)
StructAtomicCmpxchg(AtomicOrder, Index, UInt32)
ArrayAtomicGet(AtomicOrder, Index)
ArrayAtomicGetS(AtomicOrder, Index)
ArrayAtomicGetU(AtomicOrder, Index)
ArrayAtomicRmw(AtomicOrder, @lib.AggregateAtomicRmwOp, Index)
ArrayAtomicCmpxchg(AtomicOrder, Index)
StructNewDesc(Index)
StructNewDefaultDesc(Index)
RefGetDesc(Index)
RefTestDesc(Bool, Index)
RefCastDescEq(Bool, Index)
// ========== SIMD Instructions ==========
// V128 Const
V128Const(V128Const)
// V128 Memory
V128Load(MemArg)
V128Load8x8S(MemArg)
V128Load8x8U(MemArg)
V128Load16x4S(MemArg)
V128Load16x4U(MemArg)
V128Load32x2S(MemArg)
V128Load32x2U(MemArg)
V128Load8Splat(MemArg)
V128Load16Splat(MemArg)
V128Load32Splat(MemArg)
V128Load64Splat(MemArg)
V128Load32Zero(MemArg)
V128Load64Zero(MemArg)
V128Store(MemArg)
V128Load8Lane(MemArg, UInt32) // memarg, lane
V128Load16Lane(MemArg, UInt32)
V128Load32Lane(MemArg, UInt32)
V128Load64Lane(MemArg, UInt32)
V128Store8Lane(MemArg, UInt32)
V128Store16Lane(MemArg, UInt32)
V128Store32Lane(MemArg, UInt32)
V128Store64Lane(MemArg, UInt32)
// V128 Shuffle/Swizzle
I8x16Shuffle(ShuffleLanes)
I8x16Swizzle
// V128 Splat
I8x16Splat
I16x8Splat
I32x4Splat
I64x2Splat
F32x4Splat
F64x2Splat
// V128 Extract/Replace Lane
I8x16ExtractLaneS(UInt32)
I8x16ExtractLaneU(UInt32)
I8x16ReplaceLane(UInt32)
I16x8ExtractLaneS(UInt32)
I16x8ExtractLaneU(UInt32)
I16x8ReplaceLane(UInt32)
I32x4ExtractLane(UInt32)
I32x4ReplaceLane(UInt32)
I64x2ExtractLane(UInt32)
I64x2ReplaceLane(UInt32)
F32x4ExtractLane(UInt32)
F32x4ReplaceLane(UInt32)
F64x2ExtractLane(UInt32)
F64x2ReplaceLane(UInt32)
// V128 Unary operations (no operands beyond the v128)
SimdUnary(Opcode)
// V128 Binary operations
SimdBinary(Opcode)
// V128 Ternary operations (bitselect, relaxed laneselect, fma)
SimdTernary(Opcode)
// V128 Shift operations (take i32 shift amount)
SimdShift(Opcode)
// V128 Test operations (return i32)
SimdTest(Opcode)
// V128 Comparison operations (return v128 mask)
SimdCompare(Opcode)
// V128 Conversion operations
SimdConvert(Opcode)
// Relaxed SIMD
SimdRelaxed(Opcode)
} derive(Eq, Debug)
///|
pub enum WastValue {
I32(String)
I64(String)
F32(String)
F64(String)
RefNull(ValueType)
RefFunc
} derive(Eq, Debug)
///|
pub enum WastResult {
Value(WastValue)
F32NanCanonical
F32NanArithmetic
F64NanCanonical
F64NanArithmetic
RefNull(ValueType?)
RefFunc
} derive(Eq, Debug)
///|
pub struct WastAction {
module_name : String?
name : String
args : Array[WastValue]
} derive(Eq, Debug)
///|
pub enum WastActionType {
Invoke(WastAction)
Get(String?, String) // module_name?, global_name
} derive(Eq, Debug)
///|
pub enum WastCommand {
Module(Module)
ModuleBinary(String?, Bytes)
ModuleQuote(String?, Array[String])
Register(String, String?) // name, module_id
Action(WastActionType)
AssertReturn(WastActionType, Array[WastResult])
AssertTrap(WastActionType, String)
AssertException(WastActionType)
AssertExhaustion(WastActionType, String)
AssertMalformed(WastModuleDef, String)
AssertInvalid(WastModuleDef, String)
AssertUnlinkable(WastModuleDef, String)
} derive(Eq, Debug)
///|
pub enum WastModuleDef {
Inline(Module)
Binary(Bytes)
Quote(Array[String])
} derive(Eq, Debug)
///|
pub struct WastScript {
commands : Array[WastCommand]
} derive(Eq, Debug)
// ============================================================================
// Parser Error Types
// ============================================================================
///|
pub struct ParseError {
location : Location
message : String
} derive(Eq, Debug)
///|
pub suberror ParserError {
UnexpectedToken(Token, String)
InvalidInstruction(String)
Custom(String)
} derive(Eq, Debug)
///|
pub impl Show for ParserError with fn output(self, logger) {
match self {
UnexpectedToken(tok, msg) => {
logger.write_string("UnexpectedToken(")
tok.output(logger)
logger.write_string(", ")
msg.output(logger)
logger.write_char(')')
}
InvalidInstruction(msg) => {
logger.write_string("InvalidInstruction(")
msg.output(logger)
logger.write_char(')')
}
Custom(msg) => {
logger.write_string("Custom(")
msg.output(logger)
logger.write_char(')')
}
}
}
// ============================================================================
// Lexer State for Rollback
// ============================================================================
///|
pub struct LexerState {
cursor : Int32
line : Int32
line_start : Int32
}
// ============================================================================
// Parser Structure
// ============================================================================
///|
pub struct WastParser {
lexer : WastLexer
mut current : Token
errors : Array[ParseError]
}
///|
pub fn WastParser::new(lexer : WastLexer) -> WastParser {
let parser = {
lexer,
current: {
location: { filename: "", line: 0, first_column: 0, last_column: 0, },
token_type: Eof,
value: None,
},
errors: [],
}
ignore(parser.advance())
parser
}
// ============================================================================
// Parser Utilities
// ============================================================================
///|
fn WastParser::advance(self : WastParser) -> Token {
let prev = self.current
self.current = self.lexer.get_token()
prev
}
///|
fn WastParser::check(self : WastParser, token_type : TokenType) -> Bool {
match (self.current.token_type, token_type) {
// Match token "families" so callers can check ValueType/Opcode kinds
// without knowing the exact payload.
(ValueType(_), ValueType(_)) => true
(Opcode(_), Opcode(_)) => true
(SimdShapeToken(_), SimdShapeToken(_)) => true
_ => self.current.token_type == token_type
}
}
///|
fn WastParser::match_token(self : WastParser, token_type : TokenType) -> Bool {
if self.check(token_type) {
let _ = self.advance()
true
} else {
false
}
}
///|
fn WastParser::expect(
self : WastParser,
token_type : TokenType,
msg : String,
) -> Token raise ParserError {
if self.check(token_type) {
self.advance()
} else {
raise UnexpectedToken(self.current, msg)
}
}
///|
fn WastParser::expect_lpar(self : WastParser) -> Unit raise ParserError {
let _ = self.expect(Lpar, "expected '('")
}
///|
fn WastParser::expect_rpar(self : WastParser) -> Unit raise ParserError {
let _ = self.expect(Rpar, "expected ')'")
}
///|
fn WastParser::get_text(_ : WastParser, token : Token) -> String {
match token.value {
Text(s) => s
_ => ""
}
}
///|
fn WastParser::get_literal_text(_ : WastParser, token : Token) -> String {
match token.value {
Literal(lit) => lit.text
_ => ""
}
}
///|
fn WastParser::get_annotation_arg_text(
self : WastParser,
token : Token,
) -> String raise ParserError {
let text = self.get_text(token)
if text.length() > 0 {
return text
}
let literal = self.get_literal_text(token)
if literal.length() > 0 {
return literal
}
raise UnexpectedToken(token, "expected annotation argument")
}
///|
fn WastParser::parse_annotation(
self : WastParser,
) -> Annotation raise ParserError {
let token = self.expect(LparAnn, "expected annotation")
let name = self.get_text(token)
let args : Array[String] = []
while !self.check(Rpar) {
args.push(self.get_annotation_arg_text(self.advance()))
}
self.expect_rpar()
{ name, args, }
}
///|
fn WastParser::attach_annotations(
_ : WastParser,
field : ModuleField,
annotations : Array[Annotation],
) -> ModuleField raise ParserError {
if annotations.length() == 0 {
return field
}
match field {
FuncField(func) => ModuleField::FuncField({ ..func, annotations, })
ImportField(im) =>
match im.desc {
Func(_) => ModuleField::ImportField({ ..im, annotations, })
_ =>
raise Custom(
"function annotations are only supported on functions and func imports",
)
}
_ =>
raise Custom(
"function annotations are only supported on functions and func imports",
)
}
}
///|
fn WastParser::parse_annotated_module_fields(
self : WastParser,
) -> Array[ModuleField] raise ParserError {
let annotations : Array[Annotation] = []
while self.check(LparAnn) {
annotations.push(self.parse_annotation())
}
self.expect_lpar()
if self.check(Import) {
let imports = self.parse_imports()
self.expect_rpar()
let fields : Array[ModuleField] = []
for im in imports {
fields.push(
self.attach_annotations(ModuleField::ImportField(im), annotations),
)
}
fields
} else {
let field = self.parse_module_field()
self.expect_rpar()
[self.attach_annotations(field, annotations)]
}
}
// ============================================================================
// Lexer State Management
// ============================================================================
///|
fn WastParser::save_lexer_state(self : WastParser) -> LexerState {
{
cursor: self.lexer.cursor,
line: self.lexer.line,
line_start: self.lexer.line_start,
}
}
///|
fn WastParser::restore_lexer_state(
self : WastParser,
state : LexerState,
) -> Unit {
self.lexer.cursor = state.cursor
self.lexer.line = state.line
self.lexer.line_start = state.line_start
}
// ============================================================================
// Index Parsing
// ============================================================================
///|
fn WastParser::parse_index(self : WastParser) -> Index raise ParserError {
match self.current.token_type {
Nat => {
let token = self.advance()
let text = self.get_literal_text(token)
Index::Num(parse_uint(text))
}
Var => {
let token = self.advance()
let text = self.get_text(token)
Index::Id(text)
}
_ => raise UnexpectedToken(self.current, "expected index")
}
}
///|
fn WastParser::try_parse_index(self : WastParser) -> Index? raise ParserError {
match self.current.token_type {
Nat | Var => Some(self.parse_index())
_ => None
}
}
///|
fn parse_unsigned_literal(
s : String,
limit : UInt64,
) -> UInt64 raise ParserError {
let hex = s.has_prefix("0x") || s.has_prefix("0X")
let radix = if hex { 16UL } else { 10UL }
let mut result = 0UL
let mut has_digit = false
for i = (if hex { 2 } else { 0 }); i < s.length(); i = i + 1 {
let c = s[i]
if c == '_' {
continue
}
let digit = if c >= '0' && c <= '9' {
(c.to_int() - 48).to_uint64()
} else if c >= 'a' && c <= 'f' {
(c.to_int() - 87).to_uint64()
} else if c >= 'A' && c <= 'F' {
(c.to_int() - 55).to_uint64()
} else {
raise Custom("invalid unsigned literal: \{s}")
}
if digit >= radix || result > (limit - digit) / radix {
raise Custom("unsigned literal exceeds range: \{s}")
}
result = result * radix + digit
has_digit = true
}
if !has_digit {
raise Custom("invalid unsigned literal: \{s}")
}
result
}
///|
fn parse_uint(s : String) -> UInt32 raise ParserError {
parse_unsigned_literal(s, 4294967295UL).to_uint()
}
///|
fn WastParser::parse_nat(
self : WastParser,
what : String,
) -> UInt32 raise ParserError {
let token = self.expect(Nat, what)
parse_uint(self.get_literal_text(token))
}
///|
fn parse_uint64(s : String) -> UInt64 raise ParserError {
parse_unsigned_literal(s, 18446744073709551615UL)
}
// ============================================================================
// Value Type Parsing
// ============================================================================
///|
fn WastParser::is_reserved_word(self : WastParser, word : String) -> Bool {
match self.current.token_type {
Reserved => self.get_text(self.current) == word
_ => false
}
}
///|
fn WastParser::try_parse_atomic_order(self : WastParser) -> AtomicOrder? {
if self.is_reserved_word("relaxed") {
let _ = self.advance()
return Some(Relaxed)
}
if self.is_reserved_word("seq_cst") || self.is_reserved_word("seqcst") {
let _ = self.advance()
return Some(SeqCst)
}
if self.is_reserved_word("acq_rel") || self.is_reserved_word("acqrel") {
let _ = self.advance()
return Some(AcqRel)
}
None
}
///|
fn WastParser::is_heap_type_token(self : WastParser) -> Bool {
match self.current.token_type {
ValueType(vt) =>
match vt {
FuncRef | ExternRef | ExnRef => true
_ => false
}
Func | Var | Nat => true
Reserved => {
let text = self.get_text(self.current)
text == "any" ||
text == "array" ||
text == "eq" ||
text == "exn" ||
text == "extern" ||
text == "func" ||
text == "i31" ||
text == "none" ||
text == "nofunc" ||
text == "noextern" ||
text == "noexn" ||
text == "struct"
}
_ => false
}
}
///|
fn heap_type_ref_from_index(idx : Index) -> HeapTypeRef {
match idx {
Num(n) => HeapTypeRef::TypeNum(n)
Id(id) => HeapTypeRef::TypeId(id)
}
}
///|
fn exact_heap_type_ref_from_index(idx : Index) -> HeapTypeRef {
match idx {
Num(n) => HeapTypeRef::ExactTypeNum(n)
Id(id) => HeapTypeRef::ExactTypeId(id)
}
}
///|
fn heap_type_ref_from_reserved_keyword(text : String) -> HeapTypeRef? {
match text {
"any" => Some(HeapTypeRef::Any)
"string" => Some(HeapTypeRef::String)
"array" => Some(HeapTypeRef::Array)
"eq" => Some(HeapTypeRef::Eq)
"i31" => Some(HeapTypeRef::I31)
"none" => Some(HeapTypeRef::None_)
"nofunc" => Some(HeapTypeRef::NoFunc)
"func" => Some(HeapTypeRef::Func)
"noextern" => Some(HeapTypeRef::NoExtern)
"noexn" => Some(HeapTypeRef::NoExn)
"waitqueue" => Some(HeapTypeRef::Waitqueue)
"nowaitqueue" => Some(HeapTypeRef::NoWaitqueue)
"struct" => Some(HeapTypeRef::Struct)
_ => None
}
}
///|
fn legacy_alias_value_type_from_reserved_keyword(text : String) -> ValueType? {
match text {
"anyref" => Some(TypedFuncRef(HeapTypeRef::Any))
"stringref" => Some(TypedFuncRef(HeapTypeRef::String))
"arrayref" => Some(TypedFuncRef(HeapTypeRef::Array))
"eqref" => Some(TypedFuncRef(HeapTypeRef::Eq))
"i31ref" => Some(TypedFuncRef(HeapTypeRef::I31))
"nullref" => Some(TypedFuncRef(HeapTypeRef::None_))
"structref" => Some(TypedFuncRef(HeapTypeRef::Struct))
_ => None
}
}
///|
fn WastParser::can_start_value_type(self : WastParser) -> Bool {
match self.current.token_type {
ValueType(_) | Func => true
Reserved => {
let text = self.get_text(self.current)
text == "exn" ||
text == "extern" ||
legacy_alias_value_type_from_reserved_keyword(text) is Some(_)
}
Lpar => true
_ => false
}
}
///|
fn ref_value_type_with_nullability(
base : ValueType,
nullable : Bool,
) -> ValueType {
if nullable {
match base {
ExternRef | NonNullExternRef => ExternRef
ExnRef | NonNullExnRef => ExnRef
_ => FuncRef
}
} else {
match base {
ExternRef | NonNullExternRef => NonNullExternRef
ExnRef | NonNullExnRef => NonNullExnRef
TypedFuncRef(ht) | NonNullTypedFuncRef(ht) => NonNullTypedFuncRef(ht)
_ => NonNullFuncRef
}
}
}
///|
fn WastParser::parse_heap_type_ref(
self : WastParser,
) -> HeapTypeRef raise ParserError {
match self.current.token_type {
Func => {
let _ = self.advance()
HeapTypeRef::Func
}
Var | Nat => {
let idx = self.parse_index()
heap_type_ref_from_index(idx)
}
Reserved => {
let token = self.advance()
let text = self.get_text(token)
if text == "extern" || text == "exn" {
raise UnexpectedToken(token, "expected heap type")
}
match heap_type_ref_from_reserved_keyword(text) {
Some(heap_ref) => heap_ref
None => raise UnexpectedToken(token, "expected heap type")
}
}
Lpar => {
self.expect_lpar()
if self.is_reserved_word("shared") {
let _ = self.advance()
let heap = self.parse_heap_type_ref()
self.expect_rpar()
return HeapTypeRef::Shared(heap)
}
if !self.is_reserved_word("exact") {
raise UnexpectedToken(self.current, "expected heap type")
}
let _ = self.advance()
let idx = self.parse_index()
self.expect_rpar()
exact_heap_type_ref_from_index(idx)
}
_ => raise UnexpectedToken(self.current, "expected heap type")
}
}
///|
fn WastParser::parse_heap_type_value_type(
self : WastParser,
nullable : Bool,
) -> ValueType raise ParserError {
match self.current.token_type {
ValueType(vt) => {
let _ = self.advance()
ref_value_type_with_nullability(vt, nullable)
}
Func => {
let _ = self.advance()
ref_value_type_with_nullability(FuncRef, nullable)
}
Reserved => {
let token = self.advance()
let text = self.get_text(token)
if text == "exn" {
ref_value_type_with_nullability(ExnRef, nullable)
} else if text == "extern" {
ref_value_type_with_nullability(ExternRef, nullable)
} else if text == "func" {
ref_value_type_with_nullability(FuncRef, nullable)
} else {
match heap_type_ref_from_reserved_keyword(text) {
Some(heap_ref) =>
if nullable {
TypedFuncRef(heap_ref)
} else {
NonNullTypedFuncRef(heap_ref)
}
None => raise UnexpectedToken(token, "expected heap type")
}
}
}
Var | Nat | Lpar => {
let heap_ref = self.parse_heap_type_ref()
if nullable {
TypedFuncRef(heap_ref)
} else {
NonNullTypedFuncRef(heap_ref)
}
}
_ => raise UnexpectedToken(self.current, "expected heap type")
}
}
///|
fn WastParser::try_parse_ref_value_type(
self : WastParser,
) -> ValueType? raise ParserError {
if !self.check(Lpar) {
return None
}
let saved_state = self.save_lexer_state()
let saved_token = self.current
self.expect_lpar()
if !self.is_reserved_word("ref") {
self.restore_lexer_state(saved_state)
self.current = saved_token
return None
}
let _ = self.advance()
let nullable = if self.is_reserved_word("null") {
let _ = self.advance()
true
} else {
false
}
if !(self.is_heap_type_token() || self.check(Lpar)) {
self.restore_lexer_state(saved_state)
self.current = saved_token
return None
}
let vt = self.parse_heap_type_value_type(nullable)
self.expect_rpar()
Some(vt)
}
///|
fn WastParser::parse_value_type(
self : WastParser,
) -> ValueType raise ParserError {
match self.current.token_type {
ValueType(vt) => {
let _ = self.advance()
vt
}
// 'func' is an abbreviation for funcref in heap type contexts (e.g., ref.null func)
Func => {
let _ = self.advance()
FuncRef
}
Reserved => {
let text = self.get_text(self.current)
if text == "exn" {
let _ = self.advance()
ExnRef
} else if text == "extern" {
let _ = self.advance()
ExternRef
} else {
match legacy_alias_value_type_from_reserved_keyword(text) {
Some(vt) => {
let _ = self.advance()
vt
}
None => raise UnexpectedToken(self.current, "expected value type")
}
}
}
Lpar =>
match self.try_parse_ref_value_type() {
Some(vt) => vt
None => raise UnexpectedToken(self.current, "expected value type")
}
_ => raise UnexpectedToken(self.current, "expected value type")
}
}
// ============================================================================
// Limits Parsing
// ============================================================================
///|
fn WastParser::parse_limits(self : WastParser) -> Limits raise ParserError {
let min_token = self.expect(Nat, "expected minimum limit")
let min = parse_uint(self.get_literal_text(min_token))
let max : UInt32? = if self.check(Nat) {
let max_token = self.advance()
Some(parse_uint(self.get_literal_text(max_token)))
} else {
None
}
{ min, max, }
}
// ============================================================================
// Type Use Parsing
// ============================================================================
///|
fn WastParser::parse_call_ref_type_use(
self : WastParser,
) -> TypeUse raise ParserError {
match self.try_parse_index() {
Some(index) =>
{
type_index: Some(index),
func_type: { params: [], results: [], },
param_ids: [],
}
None => self.parse_type_use()
}
}
///|
fn WastParser::parse_type_use(self : WastParser) -> TypeUse raise ParserError {
let mut type_index : Index? = None
let params : Array[ValueType] = []
let results : Array[ValueType] = []
let param_ids : Array[String?] = []
// Check for (type $id) or (type 0)
if self.check(Lpar) {
let saved_state = self.save_lexer_state()
let saved_token = self.current
self.expect_lpar()
if self.check(Type) {
let _ = self.advance() // consume 'type'
type_index = Some(self.parse_index())
self.expect_rpar()
} else {
// Not a type reference, rollback
self.restore_lexer_state(saved_state)
self.current = saved_token
}
}
// Parse inline params (only if we didn't find a type reference)
while self.check(Lpar) {
let saved_state = self.save_lexer_state()
let saved_token = self.current
self.expect_lpar()
if self.check(Param) {
let _ = self.advance() // consume 'param'
// Check for named param: (param $name type)
if self.current.token_type is Var {
let id_token = self.advance()
let param_id : String? = Some(self.get_text(id_token))
params.push(self.parse_value_type())
param_ids.push(param_id)
} else {
// Abbreviated: (param type*)
while self.can_start_value_type() {
params.push(self.parse_value_type())
param_ids.push(None)
}
}
self.expect_rpar()
} else {
// Not a param, rollback and continue
self.restore_lexer_state(saved_state)
self.current = saved_token
break
}
}
// Parse inline results
while self.check(Lpar) {
let saved_state = self.save_lexer_state()
let saved_token = self.current
self.expect_lpar()
if self.check(Result) {
let _ = self.advance() // consume 'result'
while self.can_start_value_type() {
results.push(self.parse_value_type())
}
self.expect_rpar()
} else {
// Not a result, rollback and stop
self.restore_lexer_state(saved_state)
self.current = saved_token
break
}
}
{ type_index, func_type: { params, results, }, param_ids, }
}
// ============================================================================
// Instruction Parsing
// ============================================================================
///|
fn WastParser::parse_mem_arg(
self : WastParser,
default_align : UInt64,
) -> MemArg raise ParserError {
let mut offset : UInt64 = 0UL
let mut align = default_align
// Parse offset=N and align=N in any order
while true {
match self.current.token_type {
OffsetEqNat => {
let token = self.advance()
offset = parse_uint64(self.get_text(token))
}
AlignEqNat => {
let token = self.advance()
align = parse_uint64(self.get_text(token))
}
_ => break
}
}
{ align, offset, }
}
///|
fn WastParser::parse_block_type(
self : WastParser,
) -> BlockType raise ParserError {
let label : String? = if self.current.token_type is Var {
let token = self.advance()
Some(self.get_text(token))
} else {
None
}
// Try to parse type use for block
let type_use : TypeUse? = if self.check(Lpar) {
let saved_state = self.save_lexer_state()
let saved_token = self.current
self.expect_lpar()
if self.check(Type) || self.check(Param) || self.check(Result) {
// It's a type use, parse it
self.restore_lexer_state(saved_state)
self.current = saved_token
Some(self.parse_type_use())
} else {
// Not a type use, restore
self.restore_lexer_state(saved_state)
self.current = saved_token
None
}
} else if self.can_start_value_type() {
// Shorthand: just a result type (no parens)
let vt = self.parse_value_type()
Some({
type_index: None,
func_type: { params: [], results: [vt], },
param_ids: [],
})
} else {
None
}
{ label, type_use, }
}
///|
fn WastParser::parse_instructions(
self : WastParser,
) -> Array[Instruction] raise ParserError {
let instrs : Array[Instruction] = []
while true {
match self.current.token_type {
Rpar | Else | End | Eof => break
Lpar => {
self.expect_lpar()
let folded = self.parse_folded_instruction()
for instr in folded {
instrs.push(instr)
}
self.expect_rpar()
}
_ =>
match self.try_parse_plain_instruction() {
Some(instr) => instrs.push(instr)
None => break
}
}
}
instrs
}
///|
fn WastParser::parse_folded_instruction(
self : WastParser,
) -> Array[Instruction] raise ParserError {
match self.current.token_type {
Block => {
let _ = self.advance()
let block_type = self.parse_block_type()
let body = self.parse_instructions()
[Block(block_type, body)]
}
Loop => {
let _ = self.advance()
let block_type = self.parse_block_type()
let body = self.parse_instructions()
[Loop(block_type, body)]
}
If => {
let _ = self.advance()
let block_type = self.parse_block_type()
// Collect condition operands first
let result : Array[Instruction] = []
// Parse condition operands (folded instructions before then/else)
while self.check(Lpar) {
let saved = self.save_lexer_state()
let saved_token = self.current
self.expect_lpar()
if self.check(Then) || self.check(Else) {
self.restore_lexer_state(saved)
self.current = saved_token
break
}
self.restore_lexer_state(saved)
self.current = saved_token
// Parse the operand
self.expect_lpar()
let nested = self.parse_folded_instruction()
for i in nested {
result.push(i)
}
self.expect_rpar()
}
let has_folded_then = self.check(Lpar) &&
({
let saved = self.save_lexer_state()
let saved_token = self.current
self.expect_lpar()
let is_then = self.check(Then)
self.restore_lexer_state(saved)
self.current = saved_token
is_then
})
let mut then_body : Array[Instruction] = []
let mut else_body : Array[Instruction] = []
if has_folded_then {
// Hybrid syntax
self.expect_lpar()
let _ = self.expect(Then, "expected 'then'")
then_body = self.parse_instructions()
self.expect_rpar()
if self.check(Lpar) {
let saved = self.save_lexer_state()
let saved_token = self.current
self.expect_lpar()
if self.check(Else) {
let _ = self.advance()
else_body = self.parse_instructions()
self.expect_rpar()
} else {
self.restore_lexer_state(saved)
self.current = saved_token
}
}
} else {
// Flat syntax
then_body = self.parse_block_body()
else_body = if self.match_token(Else) {
self.parse_block_body()
} else {
[]
}
let _ = self.expect(End, "expected 'end'")
}
result.push(If(block_type, then_body, else_body))
result
}
Try => {
let _ = self.advance()
[self.parse_legacy_try_instruction(false)]
}
Rethrow => {
let _ = self.advance()
[Rethrow(self.parse_index())]
}
Opcode(op) => {
let _ = self.advance()
if op is TryTable {
return [self.parse_try_table_instruction(false)]
}
let instr = self.parse_opcode_instruction(op)
// Parse nested operand instructions
let result : Array[Instruction] = []
while self.check(Lpar) {
self.expect_lpar()
let nested = self.parse_folded_instruction()
for i in nested {
result.push(i)
}
self.expect_rpar()
}
result.push(instr)
result
}
_ => raise UnexpectedToken(self.current, "expected instruction")
}
}
///|
fn WastParser::try_parse_plain_instruction(
self : WastParser,
) -> Instruction? raise ParserError {
match self.current.token_type {
Block => {
let _ = self.advance()
let block_type = self.parse_block_type()
let body = self.parse_block_body()
let _ = self.expect(End, "expected 'end'")
Some(Block(block_type, body))
}
Loop => {
let _ = self.advance()
let block_type = self.parse_block_type()
let body = self.parse_block_body()
let _ = self.expect(End, "expected 'end'")
Some(Loop(block_type, body))
}
If => {
let _ = self.advance()
let block_type = self.parse_block_type()
let mut used_explicit_syntax = false
// Check if we're using explicit (then ...) syntax
let then_body : Array[Instruction] = if self.check(Lpar) {
let saved_state = self.save_lexer_state()
let saved_token = self.current
self.expect_lpar()
if self.check(Then) {
// Explicit (then ...) form
used_explicit_syntax = true
let _ = self.advance()
let instrs = self.parse_instructions()
self.expect_rpar()
instrs
} else {
// Not a then block, rollback and parse as regular instructions
self.restore_lexer_state(saved_state)
self.current = saved_token
self.parse_block_body()
}
} else {
// Flat form without explicit then
self.parse_block_body()
}
// Parse else block
let else_body : Array[Instruction] = if self.check(Lpar) {
let saved_state = self.save_lexer_state()
let saved_token = self.current
self.expect_lpar()
if self.check(Else) {
// Explicit (else ...) form
used_explicit_syntax = true
let _ = self.advance()
let instrs = self.parse_instructions()
self.expect_rpar()
instrs
} else {
// Not an else block, rollback
self.restore_lexer_state(saved_state)
self.current = saved_token
if self.match_token(Else) {
self.parse_block_body()
} else {
[]
}
}
} else if self.match_token(Else) {
// Bare else keyword in flat form
self.parse_block_body()
} else {
[]
}
// Only expect 'end' for flat form (no explicit then/else blocks)
if !used_explicit_syntax {
let _ = self.expect(End, "expected 'end'")
}
Some(If(block_type, then_body, else_body))
}
Try => {
let _ = self.advance()
Some(self.parse_legacy_try_instruction(true))
}
Rethrow => {
let _ = self.advance()
Some(Rethrow(self.parse_index()))
}
Opcode(TryTable) => {
let _ = self.advance()
Some(self.parse_try_table_instruction(true))
}
Opcode(op) => {
let _ = self.advance()
Some(self.parse_opcode_instruction(op))
}
_ => None
}
}
///|
fn WastParser::parse_block_body(
self : WastParser,
) -> Array[Instruction] raise ParserError {
let instrs : Array[Instruction] = []
while true {
match self.current.token_type {
End | Else | Then | Eof => break
Lpar => {
let saved_state = self.save_lexer_state()
let saved_token = self.current
self.expect_lpar()
// Check for (then or (else which shouldn't be parsed as instructions
if self.check(Then) || self.check(Else) {
self.restore_lexer_state(saved_state)
self.current = saved_token
break
}
let folded = self.parse_folded_instruction()
for instr in folded {
instrs.push(instr)
}
self.expect_rpar()
}
_ =>
match self.try_parse_plain_instruction() {
Some(instr) => instrs.push(instr)
None => break
}
}
}
instrs
}
///|
fn WastParser::parse_try_table_block_type(
self : WastParser,
) -> BlockType raise ParserError {
let type_use : TypeUse? = if self.check(Lpar) {
let saved_state = self.save_lexer_state()
let saved_token = self.current
self.expect_lpar()
if self.check(Type) || self.check(Param) || self.check(Result) {
self.restore_lexer_state(saved_state)
self.current = saved_token
Some(self.parse_type_use())
} else {
self.restore_lexer_state(saved_state)
self.current = saved_token
None
}
} else if self.can_start_value_type() {
let vt = self.parse_value_type()
Some({
type_index: None,
func_type: { params: [], results: [vt], },
param_ids: [],
})
} else {
None
}
{ label: None, type_use, }
}
///|
fn WastParser::parse_try_table_catches(
self : WastParser,
) -> Array[CatchClause] raise ParserError {
let catches : Array[CatchClause] = []
while self.check(Lpar) {
let saved_state = self.save_lexer_state()
let saved_token = self.current
self.expect_lpar()
let parsed : CatchClause? = match self.current.token_type {
Catch => {
let _ = self.advance()
let tag_idx = self.parse_index()
let label_idx = self.parse_index()
Some(CatchClause::CatchClause(tag_idx, label_idx))
}
CatchRef => {
let _ = self.advance()
let tag_idx = self.parse_index()
let label_idx = self.parse_index()
Some(CatchClause::CatchRefClause(tag_idx, label_idx))
}
CatchAll => {
let _ = self.advance()
Some(CatchClause::CatchAllClause(self.parse_index()))
}
CatchAllRef => {
let _ = self.advance()
Some(CatchClause::CatchAllRefClause(self.parse_index()))
}
_ => None
}
match parsed {
Some(catch_clause) => {
catches.push(catch_clause)
self.expect_rpar()
}
None => {
self.restore_lexer_state(saved_state)
self.current = saved_token
break
}
}
}
catches
}
///|
fn WastParser::parse_try_table_instruction(
self : WastParser,
with_end : Bool,
) -> Instruction raise ParserError {
let block_type = self.parse_try_table_block_type()
let catches = self.parse_try_table_catches()
let body = if with_end {
self.parse_block_body()
} else {
self.parse_instructions()
}
if with_end {
let _ = self.expect(End, "expected 'end'")
}
TryTable(block_type, catches, body)
}
///|
fn WastParser::parse_legacy_try_do_clause(
self : WastParser,
) -> Array[Instruction] raise ParserError {
self.expect_lpar()
let _ = self.expect(Do, "expected 'do' clause in legacy try")
let body = self.parse_instructions()
self.expect_rpar()
body
}
///|
fn WastParser::parse_legacy_try_instruction(
self : WastParser,
with_end : Bool,
) -> Instruction raise ParserError {
let block_type = self.parse_block_type()
let body = self.parse_legacy_try_do_clause()
let catches : Array[LegacyCatchClause] = []
let mut delegate_target : Index? = None
let mut saw_catch_all = false
while self.check(Lpar) {
let saved_state = self.save_lexer_state()
let saved_token = self.current
self.expect_lpar()
match self.current.token_type {
Catch => {
if delegate_target is Some(_) {
raise UnexpectedToken(
self.current,
"legacy try cannot mix catch and delegate",
)
}
let _ = self.advance()
let tag_idx = self.parse_index()
let catch_body = self.parse_instructions()
self.expect_rpar()
catches.push(LegacyCatch(tag_idx, catch_body))
}
CatchAll => {
if delegate_target is Some(_) {
raise UnexpectedToken(
self.current,
"legacy try cannot mix catch and delegate",
)
}
if saw_catch_all {
raise UnexpectedToken(
self.current,
"legacy try can only contain one catch_all",
)
}
saw_catch_all = true
let _ = self.advance()
let catch_body = self.parse_instructions()
self.expect_rpar()
catches.push(LegacyCatchAll(catch_body))
}
Delegate => {
if catches.length() > 0 {
raise UnexpectedToken(
self.current,
"legacy try cannot mix catch and delegate",
)
}
if delegate_target is Some(_) {
raise UnexpectedToken(
self.current,
"legacy try can only contain one delegate",
)
}
let _ = self.advance()
delegate_target = Some(self.parse_index())
self.expect_rpar()
}
_ => {
self.restore_lexer_state(saved_state)
self.current = saved_token
break
}
}
}
if with_end {
let _ = self.expect(End, "expected 'end'")
}
TryLegacy(block_type, body, catches, delegate_target)
}
///|
fn WastParser::parse_opcode_instruction(
self : WastParser,
op : Opcode,
) -> Instruction raise ParserError {
match op {
// Control
Unreachable => Unreachable
Nop => Nop
Br => Br(self.parse_index())
BrIf => BrIf(self.parse_index())
BrOnNull => BrOnNull(self.parse_index())
BrOnNonNull => BrOnNonNull(self.parse_index())
BrOnCastDescEq =>
BrOnCastDescEq(
self.parse_index(),
self.parse_value_type(),
self.parse_value_type(),
)
BrOnCastDescEqFail =>
BrOnCastDescEqFail(
self.parse_index(),
self.parse_value_type(),
self.parse_value_type(),
)
BrTable => {
let indices : Array[Index] = []
while self.current.token_type is (Nat | Var) {
indices.push(self.parse_index())
}
if indices.length() == 0 {
raise Custom("br_table requires at least one index")
}
let default = indices.pop().unwrap()
BrTable(indices, default)
}
Return => Return
Call => Call(self.parse_index())
CallIndirect => {
let table_index = self.try_parse_index().unwrap_or(Index::Num(0))
let type_use = self.parse_type_use()
CallIndirect(table_index, type_use)
}
ReturnCall => ReturnCall(self.parse_index())
ReturnCallIndirect => {
let table_index = self.try_parse_index().unwrap_or(Index::Num(0))
let type_use = self.parse_type_use()
ReturnCallIndirect(table_index, type_use)
}
CallRef => CallRef(self.parse_call_ref_type_use())
ReturnCallRef => ReturnCallRef(self.parse_call_ref_type_use())
Throw => Throw(self.parse_index())
ThrowRef => ThrowRef
TryTable => self.parse_try_table_instruction(false)
StructNew => StructNew(self.parse_index())
StructNewDefault => StructNewDefault(self.parse_index())
StructGet => {
let type_idx = self.parse_index()
let field_idx = self.parse_nat("expected struct field index")
StructGet(type_idx, field_idx)
}
StructGetS => {
let type_idx = self.parse_index()
let field_idx = self.parse_nat("expected struct field index")
StructGetS(type_idx, field_idx)
}
StructGetU => {
let type_idx = self.parse_index()
let field_idx = self.parse_nat("expected struct field index")
StructGetU(type_idx, field_idx)
}
WaitqueueNew => WaitqueueNew
ArrayNew => ArrayNew(self.parse_index())
ArrayNewDefault => ArrayNewDefault(self.parse_index())
ArrayNewFixed => {
let type_idx = self.parse_index()
let length = self.parse_nat("expected array.new_fixed length")
ArrayNewFixed(type_idx, length)
}
ArrayNewData => {
let type_idx = self.parse_index()
let data_idx = self.parse_index()
ArrayNewData(type_idx, data_idx)
}
ArrayNewElem => {
let type_idx = self.parse_index()
let elem_idx = self.parse_index()
ArrayNewElem(type_idx, elem_idx)
}
WaitqueueNotify => WaitqueueNotify
StructWait => {
let type_idx = self.parse_index()
let field_idx = self.parse_nat("expected field index")
StructWait(type_idx, field_idx)
}
Publish => Publish
StructAtomicSet => {
let order = self.try_parse_atomic_order().unwrap_or(SeqCst)
let type_idx = self.parse_index()
let field_idx = self.parse_nat("expected struct field index")
StructAtomicSet(order, type_idx, field_idx)
}
ArrayAtomicSet => {
let order = self.try_parse_atomic_order().unwrap_or(SeqCst)
ArrayAtomicSet(order, self.parse_index())
}
StructAtomicGet => {
let order = self.try_parse_atomic_order().unwrap_or(SeqCst)
let type_idx = self.parse_index()
let field_idx = self.parse_nat("expected struct field index")
StructAtomicGet(order, type_idx, field_idx)
}
StructAtomicGetS => {
let order = self.try_parse_atomic_order().unwrap_or(SeqCst)
let type_idx = self.parse_index()
let field_idx = self.parse_nat("expected struct field index")
StructAtomicGetS(order, type_idx, field_idx)
}
StructAtomicGetU => {
let order = self.try_parse_atomic_order().unwrap_or(SeqCst)
let type_idx = self.parse_index()
let field_idx = self.parse_nat("expected struct field index")
StructAtomicGetU(order, type_idx, field_idx)
}
StructAtomicRmwAdd => {
let order = self.parse_atomic_order_pair()
let type_idx = self.parse_index()
let field_idx = self.parse_nat("expected struct field index")
StructAtomicRmw(
order,
@lib.AggregateAtomicRmwOp::add(),
type_idx,
field_idx,
)
}
StructAtomicRmwSub => {
let order = self.parse_atomic_order_pair()
let type_idx = self.parse_index()
let field_idx = self.parse_nat("expected struct field index")
StructAtomicRmw(
order,
@lib.AggregateAtomicRmwOp::sub(),
type_idx,
field_idx,
)
}
StructAtomicRmwAnd => {
let order = self.parse_atomic_order_pair()
let type_idx = self.parse_index()
let field_idx = self.parse_nat("expected struct field index")
StructAtomicRmw(
order,
@lib.AggregateAtomicRmwOp::and_(),
type_idx,
field_idx,
)
}
StructAtomicRmwOr => {
let order = self.parse_atomic_order_pair()
let type_idx = self.parse_index()
let field_idx = self.parse_nat("expected struct field index")
StructAtomicRmw(
order,
@lib.AggregateAtomicRmwOp::or_(),
type_idx,
field_idx,
)
}
StructAtomicRmwXor => {
let order = self.parse_atomic_order_pair()
let type_idx = self.parse_index()
let field_idx = self.parse_nat("expected struct field index")
StructAtomicRmw(
order,
@lib.AggregateAtomicRmwOp::xor(),
type_idx,
field_idx,
)
}
StructAtomicRmwXchg => {
let order = self.parse_atomic_order_pair()
let type_idx = self.parse_index()
let field_idx = self.parse_nat("expected struct field index")
StructAtomicRmw(
order,
@lib.AggregateAtomicRmwOp::xchg(),
type_idx,
field_idx,
)
}
StructAtomicRmwCmpxchg => {
let order = self.parse_atomic_order_pair()
let type_idx = self.parse_index()
let field_idx = self.parse_nat("expected struct field index")
StructAtomicCmpxchg(order, type_idx, field_idx)
}
ArrayAtomicGet => {
let order = self.try_parse_atomic_order().unwrap_or(SeqCst)
ArrayAtomicGet(order, self.parse_index())
}
ArrayAtomicGetS => {
let order = self.try_parse_atomic_order().unwrap_or(SeqCst)
ArrayAtomicGetS(order, self.parse_index())
}
ArrayAtomicGetU => {
let order = self.try_parse_atomic_order().unwrap_or(SeqCst)
ArrayAtomicGetU(order, self.parse_index())
}
ArrayAtomicRmwAdd => {
let order = self.parse_atomic_order_pair()
ArrayAtomicRmw(
order,
@lib.AggregateAtomicRmwOp::add(),
self.parse_index(),
)
}
ArrayAtomicRmwSub => {
let order = self.parse_atomic_order_pair()
ArrayAtomicRmw(
order,
@lib.AggregateAtomicRmwOp::sub(),
self.parse_index(),
)
}
ArrayAtomicRmwAnd => {
let order = self.parse_atomic_order_pair()
ArrayAtomicRmw(
order,
@lib.AggregateAtomicRmwOp::and_(),
self.parse_index(),
)
}
ArrayAtomicRmwOr => {
let order = self.parse_atomic_order_pair()
ArrayAtomicRmw(
order,
@lib.AggregateAtomicRmwOp::or_(),
self.parse_index(),
)
}
ArrayAtomicRmwXor => {
let order = self.parse_atomic_order_pair()
ArrayAtomicRmw(
order,
@lib.AggregateAtomicRmwOp::xor(),
self.parse_index(),
)
}
ArrayAtomicRmwXchg => {
let order = self.parse_atomic_order_pair()
ArrayAtomicRmw(
order,
@lib.AggregateAtomicRmwOp::xchg(),
self.parse_index(),
)
}
ArrayAtomicRmwCmpxchg => {
let order = self.parse_atomic_order_pair()
ArrayAtomicCmpxchg(order, self.parse_index())
}
StructNewDesc => StructNewDesc(self.parse_index())
StructNewDefaultDesc => StructNewDefaultDesc(self.parse_index())
RefGetDesc => RefGetDesc(self.parse_index())
RefTestDesc => RefTestDesc(false, self.parse_index())
RefTestDescNull => RefTestDesc(true, self.parse_index())
RefCastDescEq => RefCastDescEq(false, self.parse_index())
RefCastDescEqNull => RefCastDescEq(true, self.parse_index())
// Parametric
Drop => Drop
Select => {
// Check for typed select
let types : Array[ValueType]? = if self.check(Lpar) {
let saved_state = self.save_lexer_state()
let saved_token = self.current
self.expect_lpar()
if self.check(Result) {
let _ = self.advance()
let ts : Array[ValueType] = []
while self.can_start_value_type() {
ts.push(self.parse_value_type())
}
self.expect_rpar()
Some(ts)
} else {
self.restore_lexer_state(saved_state)
self.current = saved_token
None
}
} else {
None
}
Select(types)
}
// Variable
LocalGet => LocalGet(self.parse_index())
LocalSet => LocalSet(self.parse_index())
LocalTee => LocalTee(self.parse_index())
GlobalGet => GlobalGet(self.parse_index())
GlobalSet => GlobalSet(self.parse_index())
// Table
TableGet => TableGet(self.try_parse_index().unwrap_or(Index::Num(0)))
TableSet => TableSet(self.try_parse_index().unwrap_or(Index::Num(0)))
TableSize => TableSize(self.try_parse_index().unwrap_or(Index::Num(0)))
TableGrow => TableGrow(self.try_parse_index().unwrap_or(Index::Num(0)))
TableFill => TableFill(self.try_parse_index().unwrap_or(Index::Num(0)))
TableCopy => {
let dst = self.try_parse_index().unwrap_or(Index::Num(0))
let src = self.try_parse_index().unwrap_or(Index::Num(0))
TableCopy(dst, src)
}
TableInit => {
// table.init can have either:
// table.init elemidx -> table defaults to 0
// table.init tableidx elemidx -> explicit table index
let first = self.try_parse_index()
match first {
Some(idx1) => {
let second = self.try_parse_index()
match second {
Some(idx2) => TableInit(idx1, idx2) // tableidx, elemidx
None => TableInit(Index::Num(0), idx1) // default table 0, elemidx
}
}
None => raise Custom("table.init requires at least one index")
}
}
ElemDrop => ElemDrop(self.parse_index())
// Memory
MemoryAtomic(op) => self.parse_memory_atomic(op)
AtomicFence => AtomicFence(self.try_parse_atomic_order().unwrap_or(SeqCst))
I32Load => self.parse_memory_access(op, 4, false)
I64Load => self.parse_memory_access(op, 8, false)
F32Load => self.parse_memory_access(op, 4, false)
F64Load => self.parse_memory_access(op, 8, false)
I32Load8S | I32Load8U => self.parse_memory_access(op, 1, false)
I32Load16S | I32Load16U => self.parse_memory_access(op, 2, false)
I64Load8S | I64Load8U => self.parse_memory_access(op, 1, false)
I64Load16S | I64Load16U => self.parse_memory_access(op, 2, false)
I64Load32S | I64Load32U => self.parse_memory_access(op, 4, false)
I32Store => self.parse_memory_access(op, 4, true)
I64Store => self.parse_memory_access(op, 8, true)
F32Store => self.parse_memory_access(op, 4, true)
F64Store => self.parse_memory_access(op, 8, true)
I32Store8 | I64Store8 => self.parse_memory_access(op, 1, true)
I32Store16 | I64Store16 => self.parse_memory_access(op, 2, true)
I64Store32 => self.parse_memory_access(op, 4, true)
MemorySize => MemorySize
MemoryGrow => MemoryGrow
MemoryFill => MemoryFill
MemoryCopy => MemoryCopy
MemoryInit => MemoryInit(self.parse_index())
DataDrop => DataDrop(self.parse_index())
// Const
I32Const | I64Const => {
let token = self.advance()
let text = match token.token_type {
Nat | Int => self.get_literal_text(token)
_ => raise UnexpectedToken(token, "expected integer literal")
}
Const(op, text)
}
F32Const | F64Const => {
let token = self.advance()
let text = match token.token_type {
Nat | Int | Float => self.get_literal_text(token)
_ => raise UnexpectedToken(token, "expected numeric literal")
}
Const(op, text)
}
// Unary
I32Clz
| I32Ctz
| I32Popcnt
| I64Clz
| I64Ctz
| I64Popcnt
| F32Abs
| F32Neg
| F32Ceil
| F32Floor
| F32Trunc
| F32Nearest
| F32Sqrt
| F64Abs
| F64Neg
| F64Ceil
| F64Floor
| F64Trunc
| F64Nearest
| F64Sqrt
| I32Extend8S
| I32Extend16S
| I64Extend8S
| I64Extend16S
| I64Extend32S
| RefAsNonNull
| RefI31
| RefI31Shared
| I31GetS
| I31GetU => Unary(op)
// Binary
I32Add
| I32Sub
| I32Mul
| I32DivS
| I32DivU
| I32RemS
| I32RemU
| I32And
| I32Or
| I32Xor
| I32Shl
| I32ShrS
| I32ShrU
| I32Rotl
| I32Rotr
| I64Add
| I64Sub
| I64Mul
| I64DivS
| I64DivU
| I64RemS
| I64RemU
| I64And
| I64Or
| I64Xor
| I64Shl
| I64ShrS
| I64ShrU
| I64Rotl
| I64Rotr
| F32Add
| F32Sub
| F32Mul
| F32Div
| F32Min
| F32Max
| F32Copysign
| F64Add
| F64Sub
| F64Mul
| F64Div
| F64Min
| F64Max
| F64Copysign => Binary(op)
// Test
I32Eqz | I64Eqz => Test(op)
// Compare
I32Eq
| I32Ne
| I32LtS
| I32LtU
| I32GtS
| I32GtU
| I32LeS
| I32LeU
| I32GeS
| I32GeU
| I64Eq
| I64Ne
| I64LtS
| I64LtU
| I64GtS
| I64GtU
| I64LeS
| I64LeU
| I64GeS
| I64GeU
| F32Eq
| F32Ne
| F32Lt
| F32Gt
| F32Le
| F32Ge
| F64Eq
| F64Ne
| F64Lt
| F64Gt
| F64Le
| F64Ge
| RefEq => Compare(op)
// Conversion
I32WrapI64
| I32TruncF32S
| I32TruncF32U
| I32TruncF64S
| I32TruncF64U
| I64ExtendI32S
| I64ExtendI32U
| I64TruncF32S
| I64TruncF32U
| I64TruncF64S
| I64TruncF64U
| F32ConvertI32S
| F32ConvertI32U
| F32ConvertI64S
| F32ConvertI64U
| F32DemoteF64
| F64ConvertI32S
| F64ConvertI32U
| F64ConvertI64S
| F64ConvertI64U
| F64PromoteF32
| I32ReinterpretF32
| I64ReinterpretF64
| F32ReinterpretI32
| F64ReinterpretI64
| I32TruncSatF32S
| I32TruncSatF32U
| I32TruncSatF64S
| I32TruncSatF64U
| I64TruncSatF32S
| I64TruncSatF32U
| I64TruncSatF64S
| I64TruncSatF64U
| AnyConvertExtern
| ExternConvertAny => Convert(op)
// Reference
RefNull => {
let vt = self.parse_heap_type_value_type(true)
RefNull(vt)
}
RefIsNull => RefIsNull
RefFunc => RefFunc(self.parse_index())
StringConst => {
let text = self.expect(Text, "expected string.const literal")
StringConst(self.decode_string(self.get_text(text)))
}
StringMeasureWtf16 => StringMeasureWtf16
StringConcat => StringConcat
StringEq => StringEq
StringAsWtf16 => StringAsWtf16
StringViewWtf16GetCodeunit => StringViewWtf16GetCodeunit
StringViewWtf16Slice => StringViewWtf16Slice
StringNewUtf8Array => StringNewUtf8Array
StringNewWtf16Array => StringNewWtf16Array
StringEncodeUtf8Array => StringEncodeUtf8Array
StringEncodeWtf16Array => StringEncodeWtf16Array
StringNewLossyUtf8Array => StringNewLossyUtf8Array
StringNewWtf8Array => StringNewWtf8Array
StringEncodeLossyUtf8Array => StringEncodeLossyUtf8Array
StringEncodeWtf8Array => StringEncodeWtf8Array
// SIMD - delegate to SIMD parser
_ => self.parse_simd_instruction(op)
}
}
// ============================================================================
// Module Field Parsing
// ============================================================================
///|
fn WastParser::parse_type_def(self : WastParser) -> TypeDef raise ParserError {
let _ = self.expect(Type, "expected 'type'")
let id : String? = if self.current.token_type is Var {
let token = self.advance()
Some(self.get_text(token))
} else {
None
}
match self.try_parse_type_sub_wrapper() {
Some((final_, supers, metadata)) => {
let body = self.parse_type_def_body()
self.expect_rpar()
return { id, is_sub: true, final_, supers, metadata, body, }
}
None => ()
}
let metadata = self.parse_type_metadata_clauses()
let body = self.parse_type_def_body()
{ id, is_sub: false, final_: false, supers: [], metadata, body, }
}
///|
fn WastParser::try_parse_type_sub_wrapper(
self : WastParser,
) -> (Bool, Array[Index], TypeDefMetadata)? raise ParserError {
if !self.check(Lpar) {
return None
}
let saved_state = self.save_lexer_state()
let saved_token = self.current
self.expect_lpar()
if !self.is_reserved_word("sub") {
self.restore_lexer_state(saved_state)
self.current = saved_token
return None
}
let _ = self.advance()
let mut final_ = false
if self.is_reserved_word("final") {
let _ = self.advance()
final_ = true
}
let supers : Array[Index] = []
while self.current.token_type is Var || self.current.token_type is Nat {
supers.push(self.parse_index())
}
let metadata = self.parse_type_metadata_clauses()
Some((final_, supers, metadata))
}
///|
fn WastParser::try_parse_type_metadata_clause(
self : WastParser,
) -> (String, Index)? raise ParserError {
if !self.check(Lpar) {
return None
}
let saved_state = self.save_lexer_state()
let saved_token = self.current
self.expect_lpar()
let kind = if self.is_reserved_word("describes") {
let _ = self.advance()
"describes"
} else if self.is_reserved_word("descriptor") {
let _ = self.advance()
"descriptor"
} else {
self.restore_lexer_state(saved_state)
self.current = saved_token
return None
}
let idx = self.parse_index()
self.expect_rpar()
Some((kind, idx))
}
///|
fn WastParser::parse_type_metadata_clauses(
self : WastParser,
) -> TypeDefMetadata raise ParserError {
let mut describes : Index? = None
let mut descriptor : Index? = None
while true {
match self.try_parse_type_metadata_clause() {
Some((kind, idx)) =>
if kind == "describes" {
if descriptor is Some(_) {
raise InvalidInstruction(
"describes clause must precede descriptor clause in type definition",
)
}
if describes is Some(_) {
raise InvalidInstruction(
"duplicate describes clause in type definition",
)
}
describes = Some(idx)
} else {
if descriptor is Some(_) {
raise InvalidInstruction(
"duplicate descriptor clause in type definition",
)
}
descriptor = Some(idx)
}
None => break
}
}
{ describes, descriptor, }
}
///|
fn WastParser::parse_type_def_body(
self : WastParser,
) -> TypeDefBody raise ParserError {
self.expect_lpar()
if self.is_reserved_word("shared") {
let _ = self.advance()
let body = self.parse_type_def_body()
self.expect_rpar()
if body is TypeDefBody::Shared(_) {
raise InvalidInstruction("duplicate shared type wrapper")
}
return TypeDefBody::Shared(body)
}
if self.check(Func) {
let _ = self.advance()
let func_type = self.parse_func_type_signature()
self.expect_rpar()
return TypeDefBody::Func(func_type)
}
if self.is_reserved_word("struct") {
let _ = self.advance()
let fields : Array[StructFieldDef] = []
while self.check(Lpar) {
fields.append(self.parse_struct_field_group())
}
self.expect_rpar()
return TypeDefBody::Struct(fields)
}
if self.is_reserved_word("array") {
let _ = self.advance()
let field = self.parse_struct_field_item()
self.expect_rpar()
return TypeDefBody::Array(field)
}
raise UnexpectedToken(self.current, "expected type body")
}
///|
fn WastParser::parse_func_type_signature(
self : WastParser,
) -> FuncType raise ParserError {
let params : Array[ValueType] = []
let results : Array[ValueType] = []
while self.check(Lpar) {
let saved_state = self.save_lexer_state()
let saved_token = self.current
self.expect_lpar()
if self.check(Param) {
let _ = self.advance()
if self.current.token_type is Var {
let _ = self.advance()
params.push(self.parse_value_type())
} else {
while self.can_start_value_type() {
params.push(self.parse_value_type())
}
}
self.expect_rpar()
} else {
self.restore_lexer_state(saved_state)
self.current = saved_token
break
}
}
while self.check(Lpar) {
let saved_state = self.save_lexer_state()
let saved_token = self.current
self.expect_lpar()
if self.check(Result) {
let _ = self.advance()
while self.can_start_value_type() {
results.push(self.parse_value_type())
}
self.expect_rpar()
} else {
self.restore_lexer_state(saved_state)
self.current = saved_token
break
}
}
{ params, results, }
}
///|
fn WastParser::parse_field_storage(
self : WastParser,
) -> FieldStorage raise ParserError {
match self.current.token_type {
Reserved => {
let text = self.get_text(self.current)
if text == "i8" {
let _ = self.advance()
FieldStorage::I8
} else if text == "i16" {
let _ = self.advance()
FieldStorage::I16
} else {
FieldStorage::Value(self.parse_value_type())
}
}
_ => FieldStorage::Value(self.parse_value_type())
}
}
///|
fn WastParser::parse_struct_field_item(
self : WastParser,
) -> StructFieldDef raise ParserError {
if self.check(Lpar) {
let saved_state = self.save_lexer_state()
let saved_token = self.current
self.expect_lpar()
if self.check(Mut) {
let _ = self.advance()
let storage = self.parse_field_storage()
self.expect_rpar()
return { storage, mutable_: true, }
}
self.restore_lexer_state(saved_state)
self.current = saved_token
}
let storage = self.parse_field_storage()
{ storage, mutable_: false, }
}
///|
fn WastParser::parse_struct_field_group(
self : WastParser,
) -> Array[StructFieldDef] raise ParserError {
self.expect_lpar()
if !self.is_reserved_word("field") {
raise UnexpectedToken(self.current, "expected field")
}
let _ = self.advance()
let fields : Array[StructFieldDef] = []
while !self.check(Rpar) {
fields.push(self.parse_struct_field_item())
}
self.expect_rpar()
fields
}
///|
fn WastParser::parse_inline_exports(
self : WastParser,
) -> Array[InlineExport] raise ParserError {
let exports : Array[InlineExport] = []
while self.check(Lpar) {
let saved_state = self.save_lexer_state()
let saved_token = self.current
self.expect_lpar()
if self.check(Export) {
let _ = self.advance()
let name_token = self.expect(Text, "expected export name")
let name = self.decode_name(self.get_text(name_token))
exports.push(InlineExport::Export(name))
self.expect_rpar()
} else {
self.restore_lexer_state(saved_state)
self.current = saved_token
break
}
}
exports
}
///|
fn WastParser::parse_func(self : WastParser) -> ModuleField raise ParserError {
let _ = self.expect(Func, "expected 'func'")
let id : String? = if self.current.token_type is Var {
let token = self.advance()
Some(self.get_text(token))
} else {
None
}
let exports = self.parse_inline_exports()
// Inline func import shorthand: (func $id (import "m" "f") (type ...))
if self.check(Lpar) {
let saved_state = self.save_lexer_state()
let saved_token = self.current
self.expect_lpar()
if self.check(Import) {
let _ = self.advance()
let module_token = self.expect(Text, "expected import module name")
let field_token = self.expect(Text, "expected import field name")
let module_name = self.decode_name(self.get_text(module_token))
let field_name = self.decode_name(self.get_text(field_token))
self.expect_rpar()
if exports.length() > 0 {
raise Custom(
"inline exports are not supported on inline func import shorthand; use an explicit (export ...) field",
)
}
let type_use = self.parse_type_use()
return ModuleField::ImportField({
module_name,
field_name,
desc: ImportDesc::Func(type_use),
id,
annotations: [],
})
}
self.restore_lexer_state(saved_state)
self.current = saved_token
}
let type_use = self.parse_type_use()
// Parse locals
let locals : Array[Local] = []
while self.check(Lpar) {
let saved_state = self.save_lexer_state()
let saved_token = self.current
self.expect_lpar()
if self.check(Local) {
let _ = self.advance()
if self.current.token_type is Var {
let id_token = self.advance()
let local_id : String? = Some(self.get_text(id_token))
let vt = self.parse_value_type()
locals.push({ id: local_id, val_type: vt, })
} else {
while self.can_start_value_type() {
let vt = self.parse_value_type()
locals.push({ id: None, val_type: vt, })
}
}
self.expect_rpar()
} else {
self.restore_lexer_state(saved_state)
self.current = saved_token
break
}
}
let body = self.parse_instructions()
ModuleField::FuncField({
id,
annotations: [],
exports,
type_use,
locals,
body,
})
}
///|
fn WastParser::parse_elem_item_instrs(
self : WastParser,
) -> ElemInitExpr raise ParserError {
self.expect_lpar()
let explicit_item = if self.check(Item) {
let _ = self.advance()
true
} else {
false
}
let instrs = self.parse_instructions()
self.expect_rpar()
{ instrs, explicit_item, }
}
///|
fn WastParser::parse_table(self : WastParser) -> Table raise ParserError {
let _ = self.expect(Table, "expected 'table'")
let id : String? = if self.current.token_type is Var {
let token = self.advance()
Some(self.get_text(token))
} else {
None
}
let exports = self.parse_inline_exports()
fn parse_table_elem_init() -> (
Array[Index],
Array[ElemInitExpr],
ValueType?,
Array[Instruction]?,
) raise ParserError {
let mut offset : Array[Instruction]? = None
if self.check(Lpar) {
let saved_state = self.save_lexer_state()
let saved_token = self.current
self.expect_lpar()
if self.check(Offset) {
let _ = self.advance()
offset = Some(self.parse_instructions())
self.expect_rpar()
} else if self.check(Item) {
self.restore_lexer_state(saved_state)
self.current = saved_token
} else if (match self.current.token_type {
Opcode(RefFunc) | Opcode(RefNull) => true
_ => false
}) {
self.restore_lexer_state(saved_state)
self.current = saved_token
} else {
offset = Some(self.parse_instructions())
self.expect_rpar()
}
}
let mut elem_init_type : ValueType? = None
if self.check(Func) {
let _ = self.advance()
} else if self.can_start_value_type() {
let saved_state = self.save_lexer_state()
let saved_token = self.current
let parsed_type : ValueType? = Some(self.parse_value_type()) catch {
_ => {
self.restore_lexer_state(saved_state)
self.current = saved_token
None
}
}
match parsed_type {
Some(v) => elem_init_type = Some(v)
None => ()
}
}
let func_indices : Array[Index] = []
while self.current.token_type is (Nat | Var) {
func_indices.push(self.parse_index())
}
let elem_exprs : Array[ElemInitExpr] = []
while self.check(Lpar) {
elem_exprs.push(self.parse_elem_item_instrs())
}
(func_indices, elem_exprs, elem_init_type, offset)
}
if self.current.token_type is ValueType(_) {
// Abbreviation: (table funcref (elem ...))
let elem_type = self.parse_value_type()
self.expect_lpar()
let _ = self.expect(Elem, "expected 'elem' table initializer")
let (func_indices, elem_exprs, elem_init_type, elem_offset) = parse_table_elem_init()
self.expect_rpar()
let limits : Limits = {
min: (func_indices.length() + elem_exprs.length()).reinterpret_as_uint(),
max: None,
}
return {
id,
exports,
table_type: { limits, elem_type, },
elem_init: Some(func_indices),
elem_exprs: Some(elem_exprs),
elem_init_type,
elem_offset: Some(elem_offset.unwrap_or([Const(I32Const, "0")])),
}
}
let limits = self.parse_limits()
let elem_type = self.parse_value_type()
let (elem_init, elem_exprs, elem_init_type, elem_offset) : (
Array[Index]?,
Array[ElemInitExpr]?,
ValueType?,
Array[Instruction]?,
) = if self.check(Lpar) {
let saved_state = self.save_lexer_state()
let saved_token = self.current
self.expect_lpar()
if self.check(Elem) {
let _ = self.advance()
let (func_indices, parsed_exprs, parsed_elem_type, parsed_offset) = parse_table_elem_init()
self.expect_rpar()
(
Some(func_indices),
Some(parsed_exprs),
parsed_elem_type,
Some(parsed_offset.unwrap_or([Const(I32Const, "0")])),
)
} else {
self.restore_lexer_state(saved_state)
self.current = saved_token
(None, None, None, None)
}
} else {
(None, None, None, None)
}
{
id,
exports,
table_type: { limits, elem_type, },
elem_init,
elem_exprs,
elem_init_type,
elem_offset,
}
}
///|
fn WastParser::parse_memory(self : WastParser) -> Memory raise ParserError {
let _ = self.expect(Memory, "expected 'memory'")
let id : String? = if self.current.token_type is Var {
let token = self.advance()
Some(self.get_text(token))
} else {
None
}
let exports = self.parse_inline_exports()
let memory_type = self.parse_memory_type()
{ id, exports, memory_type, }
}
///|
fn WastParser::parse_tag_type_use_or_default(
self : WastParser,
) -> TypeUse raise ParserError {
if self.check(Lpar) {
let saved_state = self.save_lexer_state()
let saved_token = self.current
self.expect_lpar()
if self.check(Type) || self.check(Param) || self.check(Result) {
self.restore_lexer_state(saved_state)
self.current = saved_token
return self.parse_type_use()
}
self.restore_lexer_state(saved_state)
self.current = saved_token
}
{ type_index: None, func_type: { params: [], results: [], }, param_ids: [], }
}
///|
fn WastParser::parse_tag_field(
self : WastParser,
) -> ModuleField raise ParserError {
let _ = self.expect(Tag, "expected 'tag'")
let id : String? = if self.current.token_type is Var {
let token = self.advance()
Some(self.get_text(token))
} else {
None
}
let exports = self.parse_inline_exports()
let inline_import : (String, String)? = if self.check(Lpar) {
let saved_state = self.save_lexer_state()
let saved_token = self.current
self.expect_lpar()
if self.check(Import) {
let _ = self.advance()
let module_token = self.expect(Text, "expected module name")
let module_name = self.decode_name(self.get_text(module_token))
let field_token = self.expect(Text, "expected field name")
let field_name = self.decode_name(self.get_text(field_token))
self.expect_rpar()
Some((module_name, field_name))
} else {
self.restore_lexer_state(saved_state)
self.current = saved_token
None
}
} else {
None
}
let type_use = self.parse_tag_type_use_or_default()
match inline_import {
Some((module_name, field_name)) => {
if exports.length() > 0 {
raise Custom(
"inline exports are not supported on inline tag import shorthand; use an explicit (export ...) field",
)
}
ModuleField::ImportField({
module_name,
field_name,
desc: ImportDesc::Tag(type_use),
id,
annotations: [],
})
}
None => ModuleField::TagField({ id, exports, type_use, })
}
}
///|
fn WastParser::parse_global(self : WastParser) -> Global raise ParserError {
let _ = self.expect(Global, "expected 'global'")
let id : String? = if self.current.token_type is Var {
let token = self.advance()
Some(self.get_text(token))
} else {
None
}
let exports = self.parse_inline_exports()
let global_type = self.parse_global_type()
let init = self.parse_instructions()
{ id, exports, global_type, init, }
}
///|
fn WastParser::parse_global_type(
self : WastParser,
) -> GlobalType raise ParserError {
let (val_type, mutable_) = if self.check(Lpar) {
let saved_state = self.save_lexer_state()
let saved_token = self.current
self.expect_lpar()
if self.check(Mut) {
let _ = self.advance()
let vt = self.parse_value_type()
self.expect_rpar()
(vt, true)
} else {
self.restore_lexer_state(saved_state)
self.current = saved_token
(self.parse_value_type(), false)
}
} else {
(self.parse_value_type(), false)
}
{ val_type, mutable_, }
}
///|
fn WastParser::parse_import_desc(
self : WastParser,
) -> (ImportDesc, String?) raise ParserError {
self.expect_lpar()
let (desc, id) = match self.current.token_type {
Func => {
let _ = self.advance()
let func_id : String? = if self.current.token_type is Var {
let token = self.advance()
Some(self.get_text(token))
} else {
None
}
let type_use = self.parse_type_use()
(ImportDesc::Func(type_use), func_id)
}
Table => {
let _ = self.advance()
let table_id : String? = if self.current.token_type is Var {
let token = self.advance()
Some(self.get_text(token))
} else {
None
}
let limits = self.parse_limits()
let elem_type = self.parse_value_type()
(ImportDesc::Table({ limits, elem_type, }), table_id)
}
Memory => {
let _ = self.advance()
let mem_id : String? = if self.current.token_type is Var {
let token = self.advance()
Some(self.get_text(token))
} else {
None
}
let memory_type = self.parse_memory_type()
(ImportDesc::Memory(memory_type), mem_id)
}
Global => {
let _ = self.advance()
let global_id : String? = if self.current.token_type is Var {
let token = self.advance()
Some(self.get_text(token))
} else {
None
}
(ImportDesc::Global(self.parse_global_type()), global_id)
}
Tag => {
let _ = self.advance()
let tag_id : String? = if self.current.token_type is Var {
let token = self.advance()
Some(self.get_text(token))
} else {
None
}
let type_use = self.parse_tag_type_use_or_default()
(ImportDesc::Tag(type_use), tag_id)
}
_ => raise UnexpectedToken(self.current, "expected import descriptor")
}
self.expect_rpar()
(desc, id)
}
///|
fn WastParser::parse_imports(
self : WastParser,
) -> Array[Import] raise ParserError {
let _ = self.expect(Import, "expected 'import'")
let module_token = self.expect(Text, "expected module name")
let module_name = self.decode_name(self.get_text(module_token))
if self.check(Text) {
let field_token = self.advance()
let field_name = self.decode_name(self.get_text(field_token))
let (desc, id) = self.parse_import_desc()
return [{ module_name, field_name, desc, id, annotations: [], }]
}
let items : Array[(String, String?, ImportDesc?)] = []
let mut has_individual_desc = false
let mut has_shared_desc = false
while self.check(Lpar) {
let saved_state = self.save_lexer_state()
let saved_token = self.current
self.expect_lpar()
if !self.check(Item) {
self.restore_lexer_state(saved_state)
self.current = saved_token
break
}
let _ = self.advance()
let id : String? = if self.check(Var) {
Some(self.get_text(self.advance()))
} else {
None
}
let token = self.expect(Text, "expected compact import item name")
let field_name = self.decode_name(self.get_text(token))
let desc : ImportDesc? = if self.check(Lpar) {
let (desc, desc_id) = self.parse_import_desc()
if desc_id is Some(_) {
raise Custom("compact import IDs belong on items")
}
has_individual_desc = true
Some(desc)
} else {
has_shared_desc = true
None
}
self.expect_rpar()
items.push((field_name, id, desc))
}
if has_individual_desc && has_shared_desc {
raise Custom("compact imports cannot mix individual and shared descriptors")
}
let common_desc : ImportDesc? = if has_shared_desc ||
(items.is_empty() && self.check(Lpar)) {
let (desc, id) = self.parse_import_desc()
if id is Some(_) {
raise Custom("compact import IDs belong on items")
}
Some(desc)
} else {
None
}
let out : Array[Import] = []
for item in items {
let (field_name, id, own_desc) = item
let desc = match own_desc {
Some(desc) => desc
None =>
match common_desc {
Some(desc) => desc
None => raise Custom("expected shared compact import descriptor")
}
}
out.push({ module_name, field_name, desc, id, annotations: [], })
}
out
}
///|
fn WastParser::parse_export(self : WastParser) -> Export raise ParserError {
let _ = self.expect(Export, "expected 'export'")
let name_token = self.expect(Text, "expected export name")
let name = self.decode_name(self.get_text(name_token))
self.expect_lpar()
let desc = match self.current.token_type {
Func => {
let _ = self.advance()
ExportDesc::Func(self.parse_index())
}
Table => {
let _ = self.advance()
ExportDesc::Table(self.parse_index())
}
Memory => {
let _ = self.advance()
ExportDesc::Memory(self.parse_index())
}
Global => {
let _ = self.advance()
ExportDesc::Global(self.parse_index())
}
Tag => {
let _ = self.advance()
ExportDesc::Tag(self.parse_index())
}
_ => raise UnexpectedToken(self.current, "expected export descriptor")
}
self.expect_rpar()
{ name, desc, }
}
///|
pub fn WastParser::parse_script(
self : WastParser,
) -> WastScript raise ParserError {
let commands : Array[WastCommand] = []
while !self.check(Eof) {
self.expect_lpar()
let cmd = self.parse_command()
commands.push(cmd)
self.expect_rpar()
}
self.check_lexer_errors()
{ commands, }
}
///|
fn WastParser::parse_command(
self : WastParser,
) -> WastCommand raise ParserError {
match self.current.token_type {
Module => self.parse_module_command()
Register => self.parse_register_command()
Invoke => WastCommand::Action(self.parse_action())
Get => WastCommand::Action(self.parse_action())
AssertReturn => self.parse_assert_return()
AssertTrap => self.parse_assert_trap()
AssertException => self.parse_assert_exception()
AssertExhaustion => self.parse_assert_exhaustion()
AssertMalformed => self.parse_assert_malformed()
AssertInvalid => self.parse_assert_invalid()
AssertUnlinkable => self.parse_assert_unlinkable()
_ => raise UnexpectedToken(self.current, "expected WAST command")
}
}
///|
fn WastParser::parse_module_command(
self : WastParser,
) -> WastCommand raise ParserError {
let _ = self.expect(Module, "expected 'module'")
// Check for optional module id
let id : String? = if self.current.token_type is Var {
let token = self.advance()
Some(self.get_text(token))
} else {
None
}
// Check for binary or quote form
match self.current.token_type {
Binary => {
let _ = self.advance()
let data = self.parse_data_strings()
WastCommand::ModuleBinary(id, data)
}
Quote => {
let _ = self.advance()
let strings = self.parse_quote_strings()
WastCommand::ModuleQuote(id, strings)
}
_ => {
// Regular module - parse fields
let fields : Array[ModuleField] = []
while self.check(Lpar) || self.check(LparAnn) {
for field in self.parse_annotated_module_fields() {
fields.push(field)
}
}
WastCommand::Module({ id, fields, })
}
}
}
///|
fn WastParser::parse_data_strings(self : WastParser) -> Bytes raise ParserError {
let buf = @buffer.new()
while self.check(Text) {
let token = self.advance()
let text = self.get_text(token)
let decoded = self.decode_string(text)
buf.write_bytes(decoded)
}
buf.to_bytes()
}
///|
fn WastParser::parse_quote_strings(
self : WastParser,
) -> Array[String] raise ParserError {
let strings : Array[String] = []
while self.check(Text) {
let token = self.advance()
strings.push(self.decode_name(self.get_text(token)))
}
strings
}
///|
fn WastParser::parse_register_command(
self : WastParser,
) -> WastCommand raise ParserError {
let _ = self.expect(Register, "expected 'register'")
let name_token = self.expect(Text, "expected register name")
let name = self.decode_name(self.get_text(name_token))
let module_id : String? = if self.current.token_type is Var {
let token = self.advance()
Some(self.get_text(token))
} else {
None
}
WastCommand::Register(name, module_id)
}
///|
fn WastParser::parse_action(
self : WastParser,
) -> WastActionType raise ParserError {
match self.current.token_type {
Invoke => {
let _ = self.advance()
let module_name : String? = if self.current.token_type is Var {
let token = self.advance()
Some(self.get_text(token))
} else {
None
}
let name_token = self.expect(Text, "expected function name")
let name = self.decode_name(self.get_text(name_token))
let args = self.parse_wast_values()
WastActionType::Invoke({ module_name, name, args, })
}
Get => {
let _ = self.advance()
let module_name : String? = if self.current.token_type is Var {
let token = self.advance()
Some(self.get_text(token))
} else {
None
}
let name_token = self.expect(Text, "expected global name")
let name = self.decode_name(self.get_text(name_token))
WastActionType::Get(module_name, name)
}
_ => raise UnexpectedToken(self.current, "expected 'invoke' or 'get'")
}
}
///|
fn WastParser::parse_wast_values(
self : WastParser,
) -> Array[WastValue] raise ParserError {
let values : Array[WastValue] = []
while self.check(Lpar) {
self.expect_lpar()
let value = self.parse_wast_value()
values.push(value)
self.expect_rpar()
}
values
}
///|
fn WastParser::parse_wast_value(
self : WastParser,
) -> WastValue raise ParserError {
match self.current.token_type {
Opcode(I32Const) => {
let _ = self.advance()
let token = self.advance()
WastValue::I32(self.get_literal_text(token))
}
Opcode(I64Const) => {
let _ = self.advance()
let token = self.advance()
WastValue::I64(self.get_literal_text(token))
}
Opcode(F32Const) => {
let _ = self.advance()
let token = self.advance()
WastValue::F32(self.get_literal_text(token))
}
Opcode(F64Const) => {
let _ = self.advance()
let token = self.advance()
WastValue::F64(self.get_literal_text(token))
}
Opcode(RefNull) => {
let _ = self.advance()
let vt = self.parse_heap_type_value_type(true)
WastValue::RefNull(vt)
}
Opcode(RefFunc) => {
let _ = self.advance()
WastValue::RefFunc
}
_ => raise UnexpectedToken(self.current, "expected const instruction")
}
}
///|
fn WastParser::parse_wast_results(
self : WastParser,
) -> Array[WastResult] raise ParserError {
let results : Array[WastResult] = []
while self.check(Lpar) {
self.expect_lpar()
let result = self.parse_wast_result()
results.push(result)
self.expect_rpar()
}
results
}
///|
fn WastParser::parse_wast_result(
self : WastParser,
) -> WastResult raise ParserError {
match self.current.token_type {
Opcode(I32Const) => {
let _ = self.advance()
let token = self.advance()
WastResult::Value(WastValue::I32(self.get_literal_text(token)))
}
Opcode(I64Const) => {
let _ = self.advance()
let token = self.advance()
WastResult::Value(WastValue::I64(self.get_literal_text(token)))
}
Opcode(F32Const) => {
let _ = self.advance()
let token = self.advance()
let text = self.get_literal_text(token)
if text == "nan:canonical" {
WastResult::F32NanCanonical
} else if text == "nan:arithmetic" {
WastResult::F32NanArithmetic
} else {
WastResult::Value(WastValue::F32(text))
}
}
Opcode(F64Const) => {
let _ = self.advance()
let token = self.advance()
let text = self.get_literal_text(token)
if text == "nan:canonical" {
WastResult::F64NanCanonical
} else if text == "nan:arithmetic" {
WastResult::F64NanArithmetic
} else {
WastResult::Value(WastValue::F64(text))
}
}
Opcode(RefNull) => {
let _ = self.advance()
let vt : ValueType? = if self.is_heap_type_token() {
Some(self.parse_heap_type_value_type(true))
} else {
None
}
WastResult::RefNull(vt)
}
Opcode(RefFunc) => {
let _ = self.advance()
WastResult::RefFunc
}
// ref.extern result
_ => raise UnexpectedToken(self.current, "expected result value")
}
}
///|
fn WastParser::parse_assert_return(
self : WastParser,
) -> WastCommand raise ParserError {
let _ = self.expect(AssertReturn, "expected 'assert_return'")
self.expect_lpar()
let action = self.parse_action()
self.expect_rpar()
let results = self.parse_wast_results()
WastCommand::AssertReturn(action, results)
}
///|
fn WastParser::parse_assert_trap(
self : WastParser,
) -> WastCommand raise ParserError {
let _ = self.expect(AssertTrap, "expected 'assert_trap'")
self.expect_lpar()
// Can be either an action or a module
if self.check(Invoke) || self.check(Get) {
let action = self.parse_action()
self.expect_rpar()
let msg_token = self.expect(Text, "expected trap message")
let msg = self.decode_name(self.get_text(msg_token))
WastCommand::AssertTrap(action, msg)
} else {
// Module trap - treat as module definition that traps on instantiation
let module_def = self.parse_module_def()
self.expect_rpar()
let msg_token = self.expect(Text, "expected trap message")
let msg = self.decode_name(self.get_text(msg_token))
// For module traps, we use AssertTrap with a dummy action
// This could be refined with a separate AST variant
WastCommand::AssertUnlinkable(module_def, msg)
}
}
///|
fn WastParser::parse_assert_exception(
self : WastParser,
) -> WastCommand raise ParserError {
let _ = self.expect(AssertException, "expected 'assert_exception'")
self.expect_lpar()
let action = self.parse_action()
self.expect_rpar()
WastCommand::AssertException(action)
}
///|
fn WastParser::parse_assert_exhaustion(
self : WastParser,
) -> WastCommand raise ParserError {
let _ = self.expect(AssertExhaustion, "expected 'assert_exhaustion'")
self.expect_lpar()
let action = self.parse_action()
self.expect_rpar()
let msg_token = self.expect(Text, "expected exhaustion message")
let msg = self.decode_name(self.get_text(msg_token))
WastCommand::AssertExhaustion(action, msg)
}
///|
fn WastParser::parse_module_def(
self : WastParser,
) -> WastModuleDef raise ParserError {
let _ = self.expect(Module, "expected 'module'")
// Skip optional id
if self.current.token_type is Var {
let _ = self.advance()
}
match self.current.token_type {
Binary => {
let _ = self.advance()
let data = self.parse_data_strings()
WastModuleDef::Binary(data)
}
Quote => {
let _ = self.advance()
let strings = self.parse_quote_strings()
WastModuleDef::Quote(strings)
}
_ => {
let fields : Array[ModuleField] = []
while self.check(Lpar) || self.check(LparAnn) {
for field in self.parse_annotated_module_fields() {
fields.push(field)
}
}
WastModuleDef::Inline({ id: None, fields, })
}
}
}
///|
fn WastParser::parse_assert_malformed(
self : WastParser,
) -> WastCommand raise ParserError {
let _ = self.expect(AssertMalformed, "expected 'assert_malformed'")
self.expect_lpar()
let module_def = self.parse_module_def()
self.expect_rpar()
let msg_token = self.expect(Text, "expected error message")
let msg = self.decode_name(self.get_text(msg_token))
WastCommand::AssertMalformed(module_def, msg)
}
///|
fn WastParser::parse_assert_invalid(
self : WastParser,
) -> WastCommand raise ParserError {
let _ = self.expect(AssertInvalid, "expected 'assert_invalid'")
self.expect_lpar()
let module_def = self.parse_module_def()
self.expect_rpar()
let msg_token = self.expect(Text, "expected error message")
let msg = self.decode_name(self.get_text(msg_token))
WastCommand::AssertInvalid(module_def, msg)
}
///|
fn WastParser::parse_assert_unlinkable(
self : WastParser,
) -> WastCommand raise ParserError {
let _ = self.expect(AssertUnlinkable, "expected 'assert_unlinkable'")
self.expect_lpar()
let module_def = self.parse_module_def()
self.expect_rpar()
let msg_token = self.expect(Text, "expected error message")
let msg = self.decode_name(self.get_text(msg_token))
WastCommand::AssertUnlinkable(module_def, msg)
}
///|
fn WastParser::parse_start(self : WastParser) -> Start raise ParserError {
let _ = self.expect(Start, "expected 'start'")
let func_index = self.parse_index()
Start::{ func_index, }
}
///|
fn WastParser::parse_data(self : WastParser) -> DataSegment raise ParserError {
let _ = self.expect(Data, "expected 'data'")
let id : String? = if self.current.token_type is Var {
let token = self.advance()
Some(self.get_text(token))
} else {
None
}
// Memory index (optional, defaults to 0)
let memory_index = self.try_parse_index().unwrap_or(Index::Num(0))
// Offset expression
let offset = if self.check(Lpar) {
self.expect_lpar()
if self.check(Offset) {
let _ = self.advance()
}
let instrs = self.parse_instructions()
self.expect_rpar()
instrs
} else {
[]
}
// Data string(s)
let data_bytes = @buffer.new()
while self.check(Text) {
let token = self.advance()
let text = self.get_text(token)
let decoded = self.decode_string(text)
data_bytes.write_bytes(decoded)
}
{ id, memory_index, offset, data: data_bytes.to_bytes(), }
}
///|
fn WastParser::parse_elem(self : WastParser) -> ElemSegment raise ParserError {
let _ = self.expect(Elem, "expected 'elem'")
let id : String? = if self.current.token_type is Var {
let token = self.advance()
Some(self.get_text(token))
} else {
None
}
// Declarative segments share both the index abbreviation and the typed
// expression grammar with passive segments, but have no table or offset.
let declarative = if self.check(Declare) {
let _ = self.advance()
true
} else {
false
}
if declarative && !self.can_start_value_type() {
raise UnexpectedToken(
self.current,
"expected func or reference type in declarative elem segment",
)
}
let table_index = if declarative {
Index::Num(0)
} else {
self.try_parse_index().unwrap_or(Index::Num(0))
}
let mut elem_type : ValueType? = None
// Offset expression
let offset = if declarative {
[]
} else if self.check(Lpar) {
let saved_state = self.save_lexer_state()
let saved_token = self.current
let parsed_elem_type : ValueType? = Some(self.parse_value_type()) catch {
_ => {
self.restore_lexer_state(saved_state)
self.current = saved_token
None
}
}
match parsed_elem_type {
Some(vt) => {
elem_type = Some(vt)
[]
}
None => {
self.expect_lpar()
if self.check(Offset) {
let _ = self.advance()
}
let instrs = self.parse_instructions()
self.expect_rpar()
instrs
}
}
} else {
[]
}
if elem_type is None {
if self.check(Func) {
let _ = self.advance()
} else if self.can_start_value_type() {
let saved_state = self.save_lexer_state()
let saved_token = self.current
let parsed_type : ValueType? = Some(self.parse_value_type()) catch {
_ => {
self.restore_lexer_state(saved_state)
self.current = saved_token
None
}
}
match parsed_type {
Some(v) => elem_type = Some(v)
None => ()
}
}
}
// Function indices and typed item expressions
let func_indices : Array[Index] = []
while self.current.token_type is (Nat | Var) {
func_indices.push(self.parse_index())
}
let elem_exprs : Array[ElemInitExpr] = []
while self.check(Lpar) {
elem_exprs.push(self.parse_elem_item_instrs())
}
{ id, declarative, table_index, offset, func_indices, elem_exprs, elem_type, }
}
// ============================================================================
// Module Parsing
// ============================================================================
///|
fn WastParser::parse_module_field(
self : WastParser,
) -> ModuleField raise ParserError {
if self.is_reserved_word("rec") {
return ModuleField::RecField(self.parse_rec_group())
}
match self.current.token_type {
Type => ModuleField::TypeField(self.parse_type_def())
Func => self.parse_func()
Tag => self.parse_tag_field()
Table => ModuleField::TableField(self.parse_table())
Memory => ModuleField::MemoryField(self.parse_memory())
Global => ModuleField::GlobalField(self.parse_global())
Export => ModuleField::ExportField(self.parse_export())
Start => ModuleField::StartField(self.parse_start())
Elem => ModuleField::ElemField(self.parse_elem())
Data => ModuleField::DataField(self.parse_data())
_ => raise UnexpectedToken(self.current, "expected module field")
}
}
///|
fn WastParser::parse_rec_group(
self : WastParser,
) -> Array[TypeDef] raise ParserError {
if !self.is_reserved_word("rec") {
raise UnexpectedToken(self.current, "expected rec")
}
let _ = self.advance()
let types : Array[TypeDef] = []
while self.check(Lpar) {
self.expect_lpar()
if !self.check(Type) {
raise UnexpectedToken(self.current, "expected type")
}
types.push(self.parse_type_def())
self.expect_rpar()
}
types
}
///|
fn WastParser::parse_v128_const(
self : WastParser,
) -> V128Const raise ParserError {
// v128.const can have multiple forms:
// v128.const i8x16 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// v128.const i16x8 0 1 2 3 4 5 6 7
// v128.const i32x4 0 1 2 3
// v128.const i64x2 0 1
// v128.const f32x4 0.0 1.0 2.0 3.0
// v128.const f64x2 0.0 1.0
let shape = self.parse_simd_shape()
let count = match shape {
I8x16 => 16
I16x8 => 8
I32x4 => 4
I64x2 => 2
F32x4 => 4
F64x2 => 2
}
let values : Array[String] = []
for i = 0; i < count; i = i + 1 {
let token = self.advance()
let text = match token.token_type {
Nat | Int | Float => self.get_literal_text(token)
_ => raise UnexpectedToken(token, "expected numeric literal")
}
values.push(text)
}
{ shape, values, }
}
///|
fn WastParser::parse_simd_shape(
self : WastParser,
) -> SimdShape raise ParserError {
match self.current.token_type {
SimdShapeToken(shape) => {
let _ = self.advance()
shape
}
_ =>
raise UnexpectedToken(
self.current,
"expected SIMD shape (i8x16, i16x8, etc.)",
)
}
}
///|
fn WastParser::parse_lane_index(self : WastParser) -> UInt32 raise ParserError {
let token = self.expect(Nat, "expected lane index")
parse_uint(self.get_literal_text(token))
}
///|
fn WastParser::parse_shuffle_lanes(
self : WastParser,
) -> ShuffleLanes raise ParserError {
let lanes : FixedArray[UInt32] = FixedArray::make(16, 0U)
for i = 0; i < 16; i = i + 1 {
let token = self.expect(Nat, "expected shuffle lane index")
lanes[i] = parse_uint(self.get_literal_text(token))
}
{ lanes, }
}
///|
fn WastParser::parse_simd_mem_arg(
self : WastParser,
default_align : UInt64,
) -> MemArg raise ParserError {
self.parse_mem_arg(default_align)
}
///|
fn WastParser::parse_simd_instruction(
self : WastParser,
op : Opcode,
) -> Instruction raise ParserError {
match op {
// V128 Const
V128Const => {
let v128_const = self.parse_v128_const()
V128Const(v128_const)
}
// V128 Memory - Load
V128Load => self.parse_memory_access(op, 16, false)
V128Load8x8S => V128Load8x8S(self.parse_simd_mem_arg(8))
V128Load8x8U => V128Load8x8U(self.parse_simd_mem_arg(8))
V128Load16x4S => V128Load16x4S(self.parse_simd_mem_arg(8))
V128Load16x4U => V128Load16x4U(self.parse_simd_mem_arg(8))
V128Load32x2S => V128Load32x2S(self.parse_simd_mem_arg(8))
V128Load32x2U => V128Load32x2U(self.parse_simd_mem_arg(8))
V128Load8Splat => V128Load8Splat(self.parse_simd_mem_arg(1))
V128Load16Splat => V128Load16Splat(self.parse_simd_mem_arg(2))
V128Load32Splat => V128Load32Splat(self.parse_simd_mem_arg(4))
V128Load64Splat => V128Load64Splat(self.parse_simd_mem_arg(8))
V128Load32Zero => V128Load32Zero(self.parse_simd_mem_arg(4))
V128Load64Zero => V128Load64Zero(self.parse_simd_mem_arg(8))
V128Store => self.parse_memory_access(op, 16, true)
// V128 Memory - Lane operations
V128Load8Lane => {
let memarg = self.parse_simd_mem_arg(1)
let lane = self.parse_lane_index()
V128Load8Lane(memarg, lane)
}
V128Load16Lane => {
let memarg = self.parse_simd_mem_arg(2)
let lane = self.parse_lane_index()
V128Load16Lane(memarg, lane)
}
V128Load32Lane => {
let memarg = self.parse_simd_mem_arg(4)
let lane = self.parse_lane_index()
V128Load32Lane(memarg, lane)
}
V128Load64Lane => {
let memarg = self.parse_simd_mem_arg(8)
let lane = self.parse_lane_index()
V128Load64Lane(memarg, lane)
}
V128Store8Lane => {
let memarg = self.parse_simd_mem_arg(1)
let lane = self.parse_lane_index()
V128Store8Lane(memarg, lane)
}
V128Store16Lane => {
let memarg = self.parse_simd_mem_arg(2)
let lane = self.parse_lane_index()
V128Store16Lane(memarg, lane)
}
V128Store32Lane => {
let memarg = self.parse_simd_mem_arg(4)
let lane = self.parse_lane_index()
V128Store32Lane(memarg, lane)
}
V128Store64Lane => {
let memarg = self.parse_simd_mem_arg(8)
let lane = self.parse_lane_index()
V128Store64Lane(memarg, lane)
}
// Shuffle
I8x16Shuffle => {
let lanes = self.parse_shuffle_lanes()
I8x16Shuffle(lanes)
}
I8x16Swizzle => I8x16Swizzle
// Splat
I8x16Splat => I8x16Splat
I16x8Splat => I16x8Splat
I32x4Splat => I32x4Splat
I64x2Splat => I64x2Splat
F32x4Splat => F32x4Splat
F64x2Splat => F64x2Splat
// Extract lane
I8x16ExtractLaneS => I8x16ExtractLaneS(self.parse_lane_index())
I8x16ExtractLaneU => I8x16ExtractLaneU(self.parse_lane_index())
I16x8ExtractLaneS => I16x8ExtractLaneS(self.parse_lane_index())
I16x8ExtractLaneU => I16x8ExtractLaneU(self.parse_lane_index())
I32x4ExtractLane => I32x4ExtractLane(self.parse_lane_index())
I64x2ExtractLane => I64x2ExtractLane(self.parse_lane_index())
F32x4ExtractLane => F32x4ExtractLane(self.parse_lane_index())
F64x2ExtractLane => F64x2ExtractLane(self.parse_lane_index())
// Replace lane
I8x16ReplaceLane => I8x16ReplaceLane(self.parse_lane_index())
I16x8ReplaceLane => I16x8ReplaceLane(self.parse_lane_index())
I32x4ReplaceLane => I32x4ReplaceLane(self.parse_lane_index())
I64x2ReplaceLane => I64x2ReplaceLane(self.parse_lane_index())
F32x4ReplaceLane => F32x4ReplaceLane(self.parse_lane_index())
F64x2ReplaceLane => F64x2ReplaceLane(self.parse_lane_index())
// Unary operations
I8x16Abs
| I8x16Neg
| I8x16Popcnt
| I16x8Abs
| I16x8Neg
| I32x4Abs
| I32x4Neg
| I64x2Abs
| I64x2Neg
| F32x4Abs
| F32x4Neg
| F32x4Sqrt
| F32x4Ceil
| F32x4Floor
| F32x4Trunc
| F32x4Nearest
| F64x2Abs
| F64x2Neg
| F64x2Sqrt
| F64x2Ceil
| F64x2Floor
| F64x2Trunc
| F64x2Nearest
| V128Not => SimdUnary(op)
// Binary operations
I8x16Add
| I8x16AddSatS
| I8x16AddSatU
| I8x16Sub
| I8x16SubSatS
| I8x16SubSatU
| I8x16MinS
| I8x16MinU
| I8x16MaxS
| I8x16MaxU
| I8x16AvgrU
| I16x8Add
| I16x8AddSatS
| I16x8AddSatU
| I16x8Sub
| I16x8SubSatS
| I16x8SubSatU
| I16x8Mul
| I16x8MinS
| I16x8MinU
| I16x8MaxS
| I16x8MaxU
| I16x8AvgrU
| I16x8Q15mulrSatS
| I32x4Add
| I32x4Sub
| I32x4Mul
| I32x4MinS
| I32x4MinU
| I32x4MaxS
| I32x4MaxU
| I32x4DotI16x8S
| I64x2Add
| I64x2Sub
| I64x2Mul
| F32x4Add
| F32x4Sub
| F32x4Mul
| F32x4Div
| F32x4Min
| F32x4Max
| F32x4Pmin
| F32x4Pmax
| F64x2Add
| F64x2Sub
| F64x2Mul
| F64x2Div
| F64x2Min
| F64x2Max
| F64x2Pmin
| F64x2Pmax
| V128And
| V128AndNot
| V128Or
| V128Xor
| I8x16NarrowI16x8S
| I8x16NarrowI16x8U
| I16x8NarrowI32x4S
| I16x8NarrowI32x4U
| I16x8ExtmulLowI8x16S
| I16x8ExtmulHighI8x16S
| I16x8ExtmulLowI8x16U
| I16x8ExtmulHighI8x16U
| I32x4ExtmulLowI16x8S
| I32x4ExtmulHighI16x8S
| I32x4ExtmulLowI16x8U
| I32x4ExtmulHighI16x8U
| I64x2ExtmulLowI32x4S
| I64x2ExtmulHighI32x4S
| I64x2ExtmulLowI32x4U
| I64x2ExtmulHighI32x4U => SimdBinary(op)
// Ternary operations
V128Bitselect
| F32x4RelaxedMadd
| F32x4RelaxedNmadd
| F64x2RelaxedMadd
| F64x2RelaxedNmadd
| I8x16RelaxedLaneselect
| I16x8RelaxedLaneselect
| I32x4RelaxedLaneselect
| I64x2RelaxedLaneselect => SimdTernary(op)
// Shift operations
I8x16Shl
| I8x16ShrS
| I8x16ShrU
| I16x8Shl
| I16x8ShrS
| I16x8ShrU
| I32x4Shl
| I32x4ShrS
| I32x4ShrU
| I64x2Shl
| I64x2ShrS
| I64x2ShrU => SimdShift(op)
// Test operations (return i32)
V128AnyTrue
| I8x16AllTrue
| I8x16Bitmask
| I16x8AllTrue
| I16x8Bitmask
| I32x4AllTrue
| I32x4Bitmask
| I64x2AllTrue
| I64x2Bitmask => SimdTest(op)
// Comparison operations
I8x16Eq
| I8x16Ne
| I8x16LtS
| I8x16LtU
| I8x16GtS
| I8x16GtU
| I8x16LeS
| I8x16LeU
| I8x16GeS
| I8x16GeU
| I16x8Eq
| I16x8Ne
| I16x8LtS
| I16x8LtU
| I16x8GtS
| I16x8GtU
| I16x8LeS
| I16x8LeU
| I16x8GeS
| I16x8GeU
| I32x4Eq
| I32x4Ne
| I32x4LtS
| I32x4LtU
| I32x4GtS
| I32x4GtU
| I32x4LeS
| I32x4LeU
| I32x4GeS
| I32x4GeU
| I64x2Eq
| I64x2Ne
| I64x2LtS
| I64x2GtS
| I64x2LeS
| I64x2GeS
| F32x4Eq
| F32x4Ne
| F32x4Lt
| F32x4Gt
| F32x4Le
| F32x4Ge
| F64x2Eq
| F64x2Ne
| F64x2Lt
| F64x2Gt
| F64x2Le
| F64x2Ge => SimdCompare(op)
// Conversion operations
I16x8ExtendLowI8x16S
| I16x8ExtendHighI8x16S
| I16x8ExtendLowI8x16U
| I16x8ExtendHighI8x16U
| I32x4ExtendLowI16x8S
| I32x4ExtendHighI16x8S
| I32x4ExtendLowI16x8U
| I32x4ExtendHighI16x8U
| I64x2ExtendLowI32x4S
| I64x2ExtendHighI32x4S
| I64x2ExtendLowI32x4U
| I64x2ExtendHighI32x4U
| I32x4TruncSatF32x4S
| I32x4TruncSatF32x4U
| I32x4TruncSatF64x2SZero
| I32x4TruncSatF64x2UZero
| F32x4ConvertI32x4S
| F32x4ConvertI32x4U
| F32x4DemoteF64x2Zero
| F64x2ConvertLowI32x4S
| F64x2ConvertLowI32x4U
| F64x2PromoteLowF32x4 => SimdConvert(op)
// Relaxed SIMD
I8x16RelaxedSwizzle
| I32x4RelaxedTruncF32x4S
| I32x4RelaxedTruncF32x4U
| I32x4RelaxedTruncF64x2SZero
| I32x4RelaxedTruncF64x2UZero
| F32x4RelaxedMin
| F32x4RelaxedMax
| F64x2RelaxedMin
| F64x2RelaxedMax
| I16x8RelaxedQ15mulrS
| I16x8RelaxedDotI8x16I7x16S
| I32x4RelaxedDotI8x16I7x16AddS => SimdRelaxed(op)
_ => raise InvalidInstruction("unhandled SIMD opcode")
}
}
///|
pub fn WastParser::parse_module(self : WastParser) -> Module raise ParserError {
self.expect_lpar()
let _ = self.expect(Module, "expected 'module'")
let id : String? = if self.current.token_type is Var {
let token = self.advance()
Some(self.get_text(token))
} else {
None
}
let fields : Array[ModuleField] = []
while self.check(Lpar) || self.check(LparAnn) {
for field in self.parse_annotated_module_fields() {
fields.push(field)
}
}
self.expect_rpar()
self.check_lexer_errors()
{ id, fields, }
}
// ============================================================================
// Utility Functions
// ============================================================================
///|
fn WastParser::strip_quotes(_ : WastParser, s : String) -> String {
if s.length() >= 2 && s[0] == '"' && s[s.length() - 1] == '"' {
s.sub(start=1, end=s.length() - 1).to_owned()
} else {
s
}
}
///|
fn WastParser::decode_string(
_ : WastParser,
s : String,
) -> Bytes raise ParserError {
let quoted = s.length() >= 2 && s[0] == '"' && s[s.length() - 1] == '"'
let start = if quoted { 1 } else { 0 }
let end = if quoted { s.length() - 1 } else { s.length() }
let buf = @buffer.new(size_hint=end - start)
let mut i = start
while i < end {
let run_start = i
while i < end && s[i] != '\\' {
i += 1
}
if i > run_start {
buf.write_string_utf8(s.sub(start=run_start, end=i))
}
if i == end {
break
}
if end - i < 2 {
raise Custom("incomplete string escape")
}
match s[i + 1] {
'n' => {
buf.write_byte(b'\n')
i += 2
}
't' => {
buf.write_byte(b'\t')
i += 2
}
'r' => {
buf.write_byte(b'\r')
i += 2
}
'"' => {
buf.write_byte(b'"')
i += 2
}
'\'' => {
buf.write_byte(b'\'')
i += 2
}
'\\' => {
buf.write_byte(b'\\')
i += 2
}
'u' => {
if end - i < 3 || s[i + 2] != '{' {
raise Custom("invalid Unicode escape")
}
i += 3
let mut scalar = 0
let mut has_digit = false
while i < end && s[i] != '}' {
if s[i] == '_' {
if !has_digit ||
end - i < 2 ||
hex_val(s[i + 1].unsafe_to_char()) < 0 {
raise Custom("invalid Unicode escape")
}
i += 1
continue
}
let digit = hex_val(s[i].unsafe_to_char())
if digit < 0 || scalar > (0x10ffff - digit) / 16 {
raise Custom("invalid Unicode escape")
}
scalar = scalar * 16 + digit
has_digit = true
i += 1
}
if i == end || !has_digit || (scalar >= 0xd800 && scalar <= 0xdfff) {
raise Custom("invalid Unicode escape")
}
buf.write_char_utf8(scalar.unsafe_to_char())
i += 1
}
_ => {
if end - i < 3 {
raise Custom("incomplete string escape")
}
let h1 = hex_val(s[i + 1].unsafe_to_char())
let h2 = hex_val(s[i + 2].unsafe_to_char())
if h1 < 0 || h2 < 0 {
raise Custom("invalid string escape")
}
buf.write_byte(((h1 << 4) | h2).to_byte())
i += 3
}
}
}
buf.to_bytes()
}
///|
fn WastParser::decode_name(
self : WastParser,
s : String,
) -> String raise ParserError {
if !s.contains("\\") {
return self.strip_quotes(s)
}
@utf8.decode(self.decode_string(s)) catch {
_ => raise Custom("invalid UTF-8 name")
}
}
///|
fn WastParser::check_lexer_errors(self : WastParser) -> Unit raise ParserError {
match self.lexer.get_errors().get(0) {
Some(error) =>
raise Custom(
"\{error.message} at \{error.location.filename}:\{error.location.line}:\{error.location.first_column}",
)
None => ()
}
}
///|
fn hex_val(c : Char) -> Int32 {
if c >= '0' && c <= '9' {
c.to_int() - 0x30
} else if c >= 'a' && c <= 'f' {
c.to_int() - 0x61 + 10
} else if c >= 'A' && c <= 'F' {
c.to_int() - 0x41 + 10
} else {
-1
}
}
///|
pub fn WastParser::has_errors(self : WastParser) -> Bool {
self.errors.length() > 0 || self.lexer.has_errors()
}
///|
pub fn WastParser::get_errors(self : WastParser) -> Array[ParseError] {
self.errors
}
// ============================================================================
// Helper for lexer - make fields accessible
// ============================================================================
// Note: You'll need to make these fields pub on WastLexer if not already:
// pub mut cursor : Int
// pub mut line : Int
// pub mut line_start : Int
// ============================================================================
// Tests
// ============================================================================
///|
test "parse simple module" {
let source =
#|(module
#| (func $add (param i32 i32) (result i32)
#| local.get 0
#| local.get 1
#| i32.add))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let mod = parser.parse_module()
assert_false(parser.has_errors())
@debug.assert_eq(mod.fields.length(), 1)
}
///|
test "parse error struct can be constructed" {
let err : ParseError = {
location: { filename: "test", line: 1, first_column: 1, last_column: 1, },
message: "example",
}
@debug.assert_eq(err.message, "example")
}
///|
test "parse function with locals" {
let source =
#|(module
#| (func $fib (param i32) (result i32)
#| (local i32 i32)
#| local.get 0
#| i32.const 2
#| i32.lt_s
#| if (result i32)
#| (then (local.get 0))
#| (else
#| (i32.add
#| (call $fib (i32.sub (local.get 0) (i32.const 1)))
#| (call $fib (i32.sub (local.get 0) (i32.const 2)))))))
#|)
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse function with type reference" {
let source =
#|(module
#| (type $t0 (func (param i32) (result i32)))
#| (func $id (type $t0)
#| local.get 0))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let mod = parser.parse_module()
assert_false(parser.has_errors())
@debug.assert_eq(mod.fields.length(), 2)
}
///|
test "parse memory and data" {
let source =
#|(module
#| (memory 1)
#| (data (i32.const 0) "hello"))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse import and export" {
let source =
#|(module
#| (import "env" "log" (func $log (param i32)))
#| (export "log" (func $log)))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
// ============================================================================
// Comprehensive Test Suite for WAST Parser
// ============================================================================
// ----------------------------------------------------------------------------
// Module Structure Tests
// ----------------------------------------------------------------------------
///|
test "parse empty module" {
let source = "(module)"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let mod = parser.parse_module()
assert_false(parser.has_errors())
@debug.assert_eq(mod.id, None)
@debug.assert_eq(mod.fields.length(), 0)
}
///|
test "parse named module" {
let source = "(module $my_module)"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let mod = parser.parse_module()
assert_false(parser.has_errors())
@debug.assert_eq(mod.id, Some("$my_module"))
}
// ----------------------------------------------------------------------------
// Type Definition Tests
// ----------------------------------------------------------------------------
///|
test "parse type with no params no results" {
let source = "(module (type $t (func)))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let mod = parser.parse_module()
assert_false(parser.has_errors())
@debug.assert_eq(mod.fields.length(), 1)
}
///|
test "parse type with params only" {
let source = "(module (type $t (func (param i32) (param i64))))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse type with results only" {
let source = "(module (type $t (func (result i32))))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse type with multiple params abbreviated" {
let source = "(module (type $t (func (param i32 i64 f32 f64))))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse type with multiple results" {
let source = "(module (type $t (func (result i32 i64))))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse type with named params" {
let source = "(module (type $t (func (param $a i32) (param $b i64) (result i32))))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse struct type defs with subtype metadata and fields" {
let source =
#|(module
#| (type $base (struct))
#| (type $node
#| (sub final $base
#| (descriptor $node_desc)
#| (struct
#| (field i32)
#| (field (mut (ref null $base))))))
#| (type $node_desc
#| (describes $node)
#| (struct (field (ref $node)))))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let mod = parser.parse_module()
assert_false(parser.has_errors())
match mod.fields {
[TypeField(base), TypeField(node), TypeField(node_desc)] => {
@debug.assert_eq(base.id, Some("$base"))
@debug.assert_eq(base.is_sub, false)
@debug.assert_eq(base.final_, false)
@debug.assert_eq(base.supers, [])
@debug.assert_eq(base.metadata, { describes: None, descriptor: None, })
@debug.assert_eq(base.body, TypeDefBody::Struct([]))
@debug.assert_eq(node.id, Some("$node"))
@debug.assert_eq(node.is_sub, true)
@debug.assert_eq(node.final_, true)
@debug.assert_eq(node.supers, [Id("$base")])
@debug.assert_eq(node.metadata, {
describes: None,
descriptor: Some(Id("$node_desc")),
})
@debug.assert_eq(
node.body,
TypeDefBody::Struct([
{ storage: Value(I32), mutable_: false, },
{
storage: Value(TypedFuncRef(HeapTypeRef::TypeId("$base"))),
mutable_: true,
},
]),
)
@debug.assert_eq(node_desc.id, Some("$node_desc"))
@debug.assert_eq(node_desc.is_sub, false)
@debug.assert_eq(node_desc.final_, false)
@debug.assert_eq(node_desc.supers, [])
@debug.assert_eq(node_desc.metadata, {
describes: Some(Id("$node")),
descriptor: None,
})
@debug.assert_eq(
node_desc.body,
TypeDefBody::Struct([
{
storage: Value(NonNullTypedFuncRef(HeapTypeRef::TypeId("$node"))),
mutable_: false,
},
]),
)
}
_ => fail("expected three type fields in struct subtype parse fixture")
}
}
///|
test "parse array type defs with subtype metadata" {
let source =
#|(module
#| (type $bytes (array i8))
#| (type $arr
#| (sub $bytes
#| (descriptor $arr_desc)
#| (array (mut (ref null $bytes)))))
#| (type $arr_desc
#| (describes $arr)
#| (array i16)))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let mod = parser.parse_module()
assert_false(parser.has_errors())
match mod.fields {
[TypeField(bytes), TypeField(arr), TypeField(arr_desc)] => {
@debug.assert_eq(bytes.id, Some("$bytes"))
@debug.assert_eq(bytes.is_sub, false)
@debug.assert_eq(
bytes.body,
TypeDefBody::Array({ storage: FieldStorage::I8, mutable_: false, }),
)
@debug.assert_eq(arr.id, Some("$arr"))
@debug.assert_eq(arr.is_sub, true)
@debug.assert_eq(arr.supers, [Index::Id("$bytes")])
@debug.assert_eq(arr.metadata, {
describes: None,
descriptor: Some(Index::Id("$arr_desc")),
})
@debug.assert_eq(
arr.body,
TypeDefBody::Array({
storage: FieldStorage::Value(
TypedFuncRef(HeapTypeRef::TypeId("$bytes")),
),
mutable_: true,
}),
)
@debug.assert_eq(arr_desc.id, Some("$arr_desc"))
@debug.assert_eq(arr_desc.metadata, {
describes: Some(Index::Id("$arr")),
descriptor: None,
})
@debug.assert_eq(
arr_desc.body,
TypeDefBody::Array({ storage: FieldStorage::I16, mutable_: false, }),
)
}
_ => fail("expected three type fields in array subtype parse fixture")
}
}
///|
test "parse rec groups with gc type defs" {
let source =
#|(module
#| (rec
#| (type $node
#| (descriptor $node_desc)
#| (struct (field (ref null $node))))
#| (type $node_desc
#| (describes $node)
#| (struct)))
#| (func))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let mod = parser.parse_module()
assert_false(parser.has_errors())
match mod.fields {
[RecField([node, node_desc]), FuncField(_)] => {
@debug.assert_eq(node.id, Some("$node"))
@debug.assert_eq(node.metadata, {
describes: None,
descriptor: Some(Id("$node_desc")),
})
@debug.assert_eq(
node.body,
TypeDefBody::Struct([
{
storage: Value(TypedFuncRef(HeapTypeRef::TypeId("$node"))),
mutable_: false,
},
]),
)
@debug.assert_eq(node_desc.id, Some("$node_desc"))
@debug.assert_eq(node_desc.metadata, {
describes: Some(Id("$node")),
descriptor: None,
})
@debug.assert_eq(node_desc.body, TypeDefBody::Struct([]))
}
_ => fail("expected rec group followed by function field")
}
}
///|
test "parse empty rec groups" {
let source = "(module (rec) (type (func)))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let mod = parser.parse_module()
assert_false(parser.has_errors())
match mod.fields {
[RecField([]), TypeField(td)] =>
@debug.assert_eq(td.body, TypeDefBody::Func({ params: [], results: [], }))
_ => fail("expected empty rec group followed by plain type field")
}
}
// ----------------------------------------------------------------------------
// Function Tests
// ----------------------------------------------------------------------------
///|
test "parse minimal function" {
let source = "(module (func))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse named function" {
let source = "(module (func $foo))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse function with inline export" {
let source =
#|(module (func $foo (export "foo")))|
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse function with multiple inline exports" {
let source =
#|(module (func $foo (export "foo") (export "bar")))|
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse a function with type reference" {
let source =
#|(module
#| (type $sig (func (param i32) (result i32)))
#| (func $f (type $sig) local.get 0))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse function with inline signature" {
let source = "(module (func $add (param i32 i32) (result i32) local.get 0 local.get 1 i32.add))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse function with named params" {
let source = "(module (func $add (param $a i32) (param $b i32) (result i32) local.get $a local.get $b i32.add))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse another function with locals" {
let source = "(module (func $f (local i32) (local i64 f32)))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse function with named locals" {
let source = "(module (func $f (local $x i32) (local $y i64)))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
// ----------------------------------------------------------------------------
// Control Flow Tests - Block
// ----------------------------------------------------------------------------
///|
test "parse empty block flat" {
let source = "(module (func block end))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse empty block folded" {
let source = "(module (func (block)))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse named block" {
let source = "(module (func (block $label nop)))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse block with result type" {
let source = "(module (func (result i32) (block (result i32) i32.const 42)))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse block flat with instructions" {
let source = "(module (func block nop nop end))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse nested blocks" {
let source = "(module (func (block (block (block nop)))))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
// ----------------------------------------------------------------------------
// Control Flow Tests - Loop
// ----------------------------------------------------------------------------
///|
test "parse empty loop flat" {
let source = "(module (func loop end))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse empty loop folded" {
let source = "(module (func (loop)))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse named loop with br" {
let source = "(module (func (loop $continue (br $continue))))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
// ----------------------------------------------------------------------------
// Control Flow Tests - If
// ----------------------------------------------------------------------------
///|
test "parse if flat no else" {
let source = "(module (func (param i32) local.get 0 if nop end))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse if flat with else" {
let source = "(module (func (param i32) local.get 0 if nop else nop end))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse if folded condition only" {
let source = "(module (func (param i32) (if (local.get 0) (then nop))))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse if folded with else" {
let source = "(module (func (param i32) (if (local.get 0) (then nop) (else nop))))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse if with result type" {
let source = "(module (func (param i32) (result i32) (if (result i32) (local.get 0) (then (i32.const 1)) (else (i32.const 0)))))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse nested if" {
let source =
#|(module (func (param i32 i32)
#| (if (local.get 0)
#| (then
#| (if (local.get 1)
#| (then nop)
#| (else nop)))
#| (else nop))))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
// ----------------------------------------------------------------------------
// Control Flow Tests - Branch
// ----------------------------------------------------------------------------
///|
test "parse br" {
let source = "(module (func (block $exit (br $exit))))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse br with numeric index" {
let source = "(module (func (block (br 0))))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse br_if" {
let source = "(module (func (param i32) (block $exit (local.get 0) (br_if $exit))))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse br_table" {
let source = "(module (func (param i32) (block $a (block $b (local.get 0) (br_table $a $b $a)))))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse return" {
let source = "(module (func return))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
// ----------------------------------------------------------------------------
// Call Tests
// ----------------------------------------------------------------------------
///|
test "parse call" {
let source = "(module (func $f) (func (call $f)))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse call with numeric index" {
let source = "(module (func) (func (call 0)))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse call_indirect" {
let source =
#|(module
#| (type $sig (func (param i32) (result i32)))
#| (table 1 funcref)
#| (func (param i32) (result i32)
#| local.get 0
#| i32.const 0
#| call_indirect (type $sig)))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
// ----------------------------------------------------------------------------
// Numeric Instruction Tests
// ----------------------------------------------------------------------------
///|
test "parse i32 const" {
let source = "(module (func (result i32) i32.const 42))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse i32 const negative" {
let source = "(module (func (result i32) i32.const -42))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse i32 const hex" {
let source = "(module (func (result i32) i32.const 0x2a))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse i64 const" {
let source = "(module (func (result i64) i64.const 9223372036854775807))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse f32 const" {
let source = "(module (func (result f32) f32.const 3.14))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse f64 const" {
let source = "(module (func (result f64) f64.const 3.141592653589793))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse i32 arithmetic" {
let source = "(module (func (param i32 i32) (result i32) local.get 0 local.get 1 i32.add))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse i32 arithmetic folded" {
let source = "(module (func (param i32 i32) (result i32) (i32.add (local.get 0) (local.get 1))))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse i32 all binary ops" {
let source =
#|(module (func (param i32 i32) (result i32)
#| (i32.add (local.get 0) (local.get 1))
#| (i32.sub (local.get 0) (local.get 1))
#| (i32.mul (local.get 0) (local.get 1))
#| (i32.div_s (local.get 0) (local.get 1))
#| (i32.div_u (local.get 0) (local.get 1))
#| (i32.rem_s (local.get 0) (local.get 1))
#| (i32.rem_u (local.get 0) (local.get 1))
#| (i32.and (local.get 0) (local.get 1))
#| (i32.or (local.get 0) (local.get 1))
#| (i32.xor (local.get 0) (local.get 1))
#| (i32.shl (local.get 0) (local.get 1))
#| (i32.shr_s (local.get 0) (local.get 1))
#| (i32.shr_u (local.get 0) (local.get 1))
#| (i32.rotl (local.get 0) (local.get 1))
#| (i32.rotr (local.get 0) (local.get 1))
#| drop drop drop drop drop drop drop drop drop drop drop drop drop drop
#| i32.const 0))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse i32 unary ops" {
let source =
#|(module (func (param i32) (result i32)
#| (i32.clz (local.get 0))
#| (i32.ctz (local.get 0))
#| (i32.popcnt (local.get 0))
#| drop drop
#| i32.const 0))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse i32 comparison ops" {
let source =
#|(module (func (param i32 i32) (result i32)
#| (i32.eq (local.get 0) (local.get 1))
#| (i32.ne (local.get 0) (local.get 1))
#| (i32.lt_s (local.get 0) (local.get 1))
#| (i32.lt_u (local.get 0) (local.get 1))
#| (i32.gt_s (local.get 0) (local.get 1))
#| (i32.gt_u (local.get 0) (local.get 1))
#| (i32.le_s (local.get 0) (local.get 1))
#| (i32.le_u (local.get 0) (local.get 1))
#| (i32.ge_s (local.get 0) (local.get 1))
#| (i32.ge_u (local.get 0) (local.get 1))
#| drop drop drop drop drop drop drop drop drop
#| i32.const 0))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse i32 eqz" {
let source = "(module (func (param i32) (result i32) (i32.eqz (local.get 0))))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse f32 ops" {
let source =
#|(module (func (param f32 f32) (result f32)
#| (f32.add (local.get 0) (local.get 1))
#| (f32.sub (local.get 0) (local.get 1))
#| (f32.mul (local.get 0) (local.get 1))
#| (f32.div (local.get 0) (local.get 1))
#| (f32.min (local.get 0) (local.get 1))
#| (f32.max (local.get 0) (local.get 1))
#| (f32.copysign (local.get 0) (local.get 1))
#| drop drop drop drop drop drop
#| f32.const 0))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse f32 unary ops" {
let source =
#|(module (func (param f32) (result f32)
#| (f32.abs (local.get 0))
#| (f32.neg (local.get 0))
#| (f32.ceil (local.get 0))
#| (f32.floor (local.get 0))
#| (f32.trunc (local.get 0))
#| (f32.nearest (local.get 0))
#| (f32.sqrt (local.get 0))
#| drop drop drop drop drop drop
#| f32.const 0))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
// ----------------------------------------------------------------------------
// Conversion Instruction Tests
// ----------------------------------------------------------------------------
///|
test "parse i32 wrap i64" {
let source = "(module (func (param i64) (result i32) (i32.wrap_i64 (local.get 0))))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse i64 extend i32" {
let source =
#|(module (func (param i32) (result i64)
#| (i64.extend_i32_s (local.get 0))
#| (i64.extend_i32_u (local.get 0))
#| drop
#| i64.const 0))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse float conversions" {
let source =
#|(module
#| (func (param i32) (result f32) (f32.convert_i32_s (local.get 0)))
#| (func (param i32) (result f32) (f32.convert_i32_u (local.get 0)))
#| (func (param f64) (result f32) (f32.demote_f64 (local.get 0)))
#| (func (param f32) (result f64) (f64.promote_f32 (local.get 0))))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse reinterpret" {
let source =
#|(module
#| (func (param f32) (result i32) (i32.reinterpret_f32 (local.get 0)))
#| (func (param i32) (result f32) (f32.reinterpret_i32 (local.get 0)))
#| (func (param f64) (result i64) (i64.reinterpret_f64 (local.get 0)))
#| (func (param i64) (result f64) (f64.reinterpret_i64 (local.get 0))))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse trunc sat" {
let source =
#|(module
#| (func (param f32) (result i32) (i32.trunc_sat_f32_s (local.get 0)))
#| (func (param f32) (result i32) (i32.trunc_sat_f32_u (local.get 0)))
#| (func (param f64) (result i32) (i32.trunc_sat_f64_s (local.get 0)))
#| (func (param f64) (result i32) (i32.trunc_sat_f64_u (local.get 0))))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse sign extension" {
let source =
#|(module (func (param i32 i64)
#| (i32.extend8_s (local.get 0))
#| (i32.extend16_s (local.get 0))
#| (i64.extend8_s (local.get 1))
#| (i64.extend16_s (local.get 1))
#| (i64.extend32_s (local.get 1))
#| drop drop drop drop drop))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
// ----------------------------------------------------------------------------
// Variable Instruction Tests
// ----------------------------------------------------------------------------
///|
test "parse local get set tee" {
let source =
#|(module (func (param i32) (local i32)
#| (local.get 0)
#| (local.set 1)
#| (local.tee 1 (i32.const 42))
#| drop))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse global get set" {
let source =
#|(module
#| (global $g (mut i32) (i32.const 0))
#| (func
#| (global.get $g)
#| (global.set $g)
#| (global.get 0)
#| (global.set 0)))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
// ----------------------------------------------------------------------------
// Memory Instruction Tests
// ----------------------------------------------------------------------------
///|
test "parse memory load store" {
let source =
#|(module
#| (memory 1)
#| (func (param i32)
#| (i32.load (local.get 0))
#| (i32.store (local.get 0))
#| (i64.load (local.get 0))
#| (i64.store (local.get 0))
#| drop drop))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse memory load with offset and align" {
let source =
#|(module
#| (memory 1)
#| (func (param i32) (result i32)
#| (i32.load offset=4 align=4 (local.get 0))))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse memory load partial" {
let source =
#|(module
#| (memory 1)
#| (func (param i32)
#| (i32.load8_s (local.get 0))
#| (i32.load8_u (local.get 0))
#| (i32.load16_s (local.get 0))
#| (i32.load16_u (local.get 0))
#| (i64.load8_s (local.get 0))
#| (i64.load8_u (local.get 0))
#| (i64.load16_s (local.get 0))
#| (i64.load16_u (local.get 0))
#| (i64.load32_s (local.get 0))
#| (i64.load32_u (local.get 0))
#| drop drop drop drop drop drop drop drop drop drop))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse memory store partial" {
let source =
#|(module
#| (memory 1)
#| (func (param i32 i64)
#| (i32.store8 (local.get 0) (local.get 0))
#| (i32.store16 (local.get 0) (local.get 0))
#| (i64.store8 (local.get 0) (local.get 1))
#| (i64.store16 (local.get 0) (local.get 1))
#| (i64.store32 (local.get 0) (local.get 1))))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse memory size and grow" {
let source =
#|(module
#| (memory 1)
#| (func (result i32)
#| memory.size
#| (memory.grow (i32.const 1))
#| drop))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse memory fill copy" {
let source =
#|(module
#| (memory 1)
#| (func
#| (memory.fill (i32.const 0) (i32.const 0) (i32.const 100))
#| (memory.copy (i32.const 0) (i32.const 100) (i32.const 50))))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse memory init and data drop" {
let source =
#|(module
#| (memory 1)
#| (data $d (i32.const 0) "hello")
#| (func
#| (memory.init $d (i32.const 0) (i32.const 0) (i32.const 5))
#| (data.drop $d)))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
// ----------------------------------------------------------------------------
// Table Instruction Tests
// ----------------------------------------------------------------------------
///|
test "parse table get set" {
let source =
#|(module
#| (table 1 funcref)
#| (func (param i32)
#| (table.get (local.get 0))
#| (table.set (local.get 0))
#| drop))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse table size grow fill" {
let source =
#|(module
#| (table 1 funcref)
#| (func
#| (table.size)
#| (table.grow (ref.null func) (i32.const 1))
#| drop drop))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse table copy init" {
let source =
#|(module
#| (table $t 10 funcref)
#| (elem $e (i32.const 0) $f)
#| (func $f)
#| (func
#| (table.copy (i32.const 0) (i32.const 5) (i32.const 5))
#| (table.init $e (i32.const 0) (i32.const 0) (i32.const 1))
#| (elem.drop $e)))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
// ----------------------------------------------------------------------------
// Reference Instruction Tests
// ----------------------------------------------------------------------------
///|
test "parse ref null" {
let source = "(module (func (result funcref) (ref.null func)))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse ref is null" {
let source = "(module (func (param funcref) (result i32) (ref.is_null (local.get 0))))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse ref func" {
let source =
#|(module
#| (func $f)
#| (func (result funcref) (ref.func $f)))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse ref get desc" {
let source = "(module (func (param funcref) (result i32) (ref.get_desc 0 (local.get 0))))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse ref test desc and cast desc eq" {
let source =
#|(module
#| (func (param funcref) (result i32)
#| (drop (ref.test_desc 0 (local.get 0)))
#| (drop (ref.test_desc_null 0 (local.get 0)))
#| (drop (ref.cast_desc_eq 0 (local.get 0)))
#| (drop (ref.cast_desc_eq_null 0 (local.get 0)))
#| (i32.const 0)))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse struct allocation instruction families" {
let source =
#|(module
#| (func
#| (drop (struct.new 0))
#| (drop (struct.new_default 0))
#| (drop (struct.new_desc 0 (ref.null none)))
#| (drop (struct.new_default_desc 0 (ref.null none)))))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let mod = parser.parse_module()
assert_false(parser.has_errors())
match mod.fields {
[FuncField(func)] =>
@debug.assert_eq(func.body, [
StructNew(Num(0U)),
Drop,
StructNewDefault(Num(0U)),
Drop,
RefNull(TypedFuncRef(HeapTypeRef::None_)),
StructNewDesc(Num(0U)),
Drop,
RefNull(TypedFuncRef(HeapTypeRef::None_)),
StructNewDefaultDesc(Num(0U)),
Drop,
])
_ =>
fail("expected single function field in struct allocation parse fixture")
}
}
///|
test "parse tags with throw and throw_ref" {
let source =
#|(module
#| (type $void (func))
#| (tag $e0 (type $void))
#| (func
#| (throw $e0)
#| throw_ref))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse try_table catch forms" {
let source =
#|(module
#| (type $void (func))
#| (tag $e0 (type $void))
#| (func
#| (block $h
#| (try_table (catch $e0 $h)
#| (throw $e0))
#| (try_table (catch_ref $e0 $h)
#| throw_ref)
#| (try_table (catch_all $h)
#| unreachable)
#| (try_table (catch_all_ref $h)
#| unreachable))))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse legacy try do catch forms" {
let source =
#|(module
#| (tag $e0)
#| (func
#| (try
#| (do (throw $e0))
#| (catch $e0)))
#| (func
#| (try (result i32)
#| (do (throw $e0) (i32.const 0))
#| (catch_all (i32.const 1)))))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse legacy delegate and rethrow forms" {
let source =
#|(module
#| (tag $e0)
#| (func
#| (try $outer
#| (do
#| (try
#| (do (throw $e0))
#| (delegate $outer)))
#| (catch $e0)))
#| (func
#| (try
#| (do (throw $e0))
#| (catch $e0 (rethrow 0)))))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse function annotations on defined functions" {
let source =
#|(module
#| (@binaryen.idempotent)
#| (@metadata.code.inline "\00")
#| (func $f))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let mod = parser.parse_module()
assert_false(parser.has_errors())
match mod.fields {
[FuncField(func)] =>
@debug.assert_eq(func.annotations, [
{ name: "binaryen.idempotent", args: [], },
{ name: "metadata.code.inline", args: ["\"\\00\""], },
])
_ => fail("expected single annotated function field")
}
}
///|
test "parse function annotations on func imports" {
let source =
#|(module
#| (@binaryen.js.called)
#| (import "env" "f" (func $f)))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let mod = parser.parse_module()
assert_false(parser.has_errors())
match mod.fields {
[ImportField(im)] => {
@debug.assert_eq(im.annotations, [
{ name: "binaryen.js.called", args: [], },
])
match im.desc {
Func(_) => ()
_ => fail("expected function import descriptor")
}
}
_ => fail("expected single annotated import field")
}
}
///|
test "parse typed ref forms in signatures" {
let source =
#|(module
#| (type $t (func))
#| (func $dummy)
#| (elem declare func $dummy)
#|
#| (tag $e (param (ref $t)))
#| (func $throw (throw $e (ref.func $dummy)))
#|
#| (func (export "catch") (result (ref null $t))
#| (block $l (result (ref null $t))
#| (try_table (catch $e $l) (call $throw))
#| unreachable))
#| (func (export "catch_ref") (result (ref null $t))
#| (block $l (result (ref null $t) (ref exn))
#| (try_table (catch_ref $e $l) (call $throw))
#| unreachable)
#| drop))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse typed-heap value types preserve concrete type references" {
let source =
#|(module
#| (type $t (func))
#| (func (param (ref $t)) (result (ref null $t))
#| (ref.null $t)))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let mod = parser.parse_module()
assert_false(parser.has_errors())
match mod.fields {
[TypeField(_), FuncField(func)] => {
@debug.assert_eq(func.type_use.func_type.params, [
NonNullTypedFuncRef(HeapTypeRef::TypeId("$t")),
])
@debug.assert_eq(func.type_use.func_type.results, [
TypedFuncRef(HeapTypeRef::TypeId("$t")),
])
match func.body {
[RefNull(vt)] =>
@debug.assert_eq(vt, TypedFuncRef(HeapTypeRef::TypeId("$t")))
_ => fail("expected single ref.null instruction in typed-heap fixture")
}
}
_ => fail("expected type + function fields in typed-heap parse fixture")
}
}
///|
test "parse exact typed-heap value types preserve exact concrete references" {
let source =
#|(module
#| (type $t (func))
#| (func (param (ref (exact $t))) (result (ref (exact $t)))
#| (local.get 0)))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let mod = parser.parse_module()
assert_false(parser.has_errors())
match mod.fields {
[TypeField(_), FuncField(func)] => {
@debug.assert_eq(func.type_use.func_type.params, [
NonNullTypedFuncRef(HeapTypeRef::ExactTypeId("$t")),
])
@debug.assert_eq(func.type_use.func_type.results, [
NonNullTypedFuncRef(HeapTypeRef::ExactTypeId("$t")),
])
match func.body {
[LocalGet(Num(0U))] => ()
_ =>
fail(
"expected single local.get instruction in exact typed-heap fixture",
)
}
}
_ =>
fail("expected type + function fields in exact typed-heap parse fixture")
}
}
///|
test "parse abstract heap ref forms preserve non-func heap kinds" {
let source =
#|(module
#| (func
#| (param (ref any) (ref eq) (ref i31) (ref none) (ref nofunc) (ref noextern) (ref noexn))
#| (result (ref null any) (ref null eq) (ref null i31) (ref null none) (ref null nofunc) (ref null noextern) (ref null noexn))
#| (ref.null any)
#| (ref.null eq)
#| (ref.null i31)
#| (ref.null none)
#| (ref.null nofunc)
#| (ref.null noextern)
#| (ref.null noexn)))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let mod = parser.parse_module()
assert_false(parser.has_errors())
match mod.fields {
[FuncField(func)] => {
@debug.assert_eq(func.type_use.func_type.params, [
NonNullTypedFuncRef(HeapTypeRef::Any),
NonNullTypedFuncRef(HeapTypeRef::Eq),
NonNullTypedFuncRef(HeapTypeRef::I31),
NonNullTypedFuncRef(HeapTypeRef::None_),
NonNullTypedFuncRef(HeapTypeRef::NoFunc),
NonNullTypedFuncRef(HeapTypeRef::NoExtern),
NonNullTypedFuncRef(HeapTypeRef::NoExn),
])
@debug.assert_eq(func.type_use.func_type.results, [
TypedFuncRef(HeapTypeRef::Any),
TypedFuncRef(HeapTypeRef::Eq),
TypedFuncRef(HeapTypeRef::I31),
TypedFuncRef(HeapTypeRef::None_),
TypedFuncRef(HeapTypeRef::NoFunc),
TypedFuncRef(HeapTypeRef::NoExtern),
TypedFuncRef(HeapTypeRef::NoExn),
])
@debug.assert_eq(func.body, [
RefNull(TypedFuncRef(HeapTypeRef::Any)),
RefNull(TypedFuncRef(HeapTypeRef::Eq)),
RefNull(TypedFuncRef(HeapTypeRef::I31)),
RefNull(TypedFuncRef(HeapTypeRef::None_)),
RefNull(TypedFuncRef(HeapTypeRef::NoFunc)),
RefNull(TypedFuncRef(HeapTypeRef::NoExtern)),
RefNull(TypedFuncRef(HeapTypeRef::NoExn)),
])
}
_ =>
fail("expected single function field in abstract heap-ref parse fixture")
}
}
///|
test "parse legacy gc reference aliases and abstract struct array refs" {
let source =
#|(module
#| (table 1 anyref)
#| (func
#| (param anyref eqref i31ref structref arrayref nullref)
#| (result anyref structref arrayref nullref)
#| (ref.null struct)
#| (ref.null array)))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let mod = parser.parse_module()
assert_false(parser.has_errors())
match mod.fields {
[TableField(table), FuncField(func)] => {
@debug.assert_eq(
table.table_type.elem_type,
TypedFuncRef(HeapTypeRef::Any),
)
@debug.assert_eq(func.type_use.func_type.params, [
TypedFuncRef(HeapTypeRef::Any),
TypedFuncRef(HeapTypeRef::Eq),
TypedFuncRef(HeapTypeRef::I31),
TypedFuncRef(HeapTypeRef::Struct),
TypedFuncRef(HeapTypeRef::Array),
TypedFuncRef(HeapTypeRef::None_),
])
@debug.assert_eq(func.type_use.func_type.results, [
TypedFuncRef(HeapTypeRef::Any),
TypedFuncRef(HeapTypeRef::Struct),
TypedFuncRef(HeapTypeRef::Array),
TypedFuncRef(HeapTypeRef::None_),
])
@debug.assert_eq(func.body, [
RefNull(TypedFuncRef(HeapTypeRef::Struct)),
RefNull(TypedFuncRef(HeapTypeRef::Array)),
])
}
_ => fail("expected table + function fields in legacy alias parse fixture")
}
}
// ----------------------------------------------------------------------------
// Parametric Instruction Tests
// ----------------------------------------------------------------------------
///|
test "parse drop" {
let source = "(module (func (i32.const 42) drop))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse select" {
let source = "(module (func (param i32) (result i32) (select (i32.const 1) (i32.const 2) (local.get 0))))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse typed select" {
let source = "(module (func (param i32) (result i32) (select (result i32) (i32.const 1) (i32.const 2) (local.get 0))))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
// ----------------------------------------------------------------------------
// Memory Definition Tests
// ----------------------------------------------------------------------------
///|
test "parse memory min only" {
let source = "(module (memory 1))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse memory min max" {
let source = "(module (memory 1 10))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse named memory" {
let source = "(module (memory $mem 1))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse memory with inline export" {
let source =
#|(module (memory $mem (export "memory") 1))|
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
// ----------------------------------------------------------------------------
// Table Definition Tests
// ----------------------------------------------------------------------------
///|
test "parse table min only" {
let source = "(module (table 1 funcref))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse table min max" {
let source = "(module (table 1 10 funcref))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse named table" {
let source = "(module (table $tbl 1 funcref))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse table with inline export" {
let source =
#|(module (table $tbl (export "table") 1 funcref))|
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse table elem-abbreviation with function ids" {
let source =
#|(module
#| (func $f)
#| (table funcref (elem $f)))|
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse table elem-abbreviation with numeric function indices" {
let source =
#|(module
#| (func)
#| (func)
#| (table funcref (elem 0 1)))|
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse table elem-abbreviation with explicit limits" {
let source =
#|(module
#| (func $a)
#| (func $b)
#| (table 3 funcref (elem $a $b)))|
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse table elem-abbreviation with func keyword and non-zero offset" {
let source =
#|(module
#| (func $f)
#| (table 4 funcref (elem (i32.const 2) func $f)))|
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse table elem-abbreviation with typed ref.func items" {
let source =
#|(module
#| (func $f)
#| (table 2 funcref (elem funcref (ref.func $f) (item (ref.func $f)))))|
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse table elem-abbreviation with typed ref.null items" {
let source =
#|(module
#| (func $f)
#| (table 2 funcref (elem funcref (item (ref.null func)) (item (ref.func $f)))))|
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
// ----------------------------------------------------------------------------
// Global Definition Tests
// ----------------------------------------------------------------------------
///|
test "parse immutable global" {
let source = "(module (global i32 (i32.const 42)))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse mutable global" {
let source = "(module (global (mut i32) (i32.const 42)))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse named global" {
let source = "(module (global $g (mut i32) (i32.const 0)))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse global with inline export" {
let source =
#|(module (global $g (export "global") (mut i32) (i32.const 0)))|
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse global all types" {
let source =
#|(module
#| (global i32 (i32.const 0))
#| (global i64 (i64.const 0))
#| (global f32 (f32.const 0))
#| (global f64 (f64.const 0)))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
// ----------------------------------------------------------------------------
// Import Tests
// ----------------------------------------------------------------------------
///|
test "parse import func" {
let source =
#|(module (import "env" "log" (func $log (param i32))))|
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse import func with type" {
let source =
#|(module
#| (type $sig (func (param i32)))
#| (import "env" "log" (func $log (type $sig))))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse import memory" {
let source =
#|(module (import "env" "memory" (memory 1)))|
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse import table" {
let source =
#|(module (import "env" "table" (table 1 funcref)))|
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse import global immutable" {
let source =
#|(module (import "env" "g" (global i32)))|
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse import global mutable" {
let source =
#|(module (import "env" "g" (global (mut i32))))|
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
// ----------------------------------------------------------------------------
// Export Tests
// ----------------------------------------------------------------------------
///|
test "parse export func" {
let source =
#|(module (func $f) (export "f" (func $f)))|
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse export func by index" {
let source =
#|(module (func) (export "f" (func 0)))|
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse export memory" {
let source =
#|(module (memory 1) (export "mem" (memory 0)))|
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse export table" {
let source =
#|(module (table 1 funcref) (export "tbl" (table 0)))|
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse export global" {
let source =
#|(module (global i32 (i32.const 0)) (export "g" (global 0)))|
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
// ----------------------------------------------------------------------------
// Start Function Tests
// ----------------------------------------------------------------------------
///|
test "parse start by name" {
let source = "(module (func $main) (start $main))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse start by index" {
let source = "(module (func) (start 0))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
// ----------------------------------------------------------------------------
// Data Segment Tests
// ----------------------------------------------------------------------------
///|
test "parse data segment" {
let source =
#|(module (memory 1) (data (i32.const 0) "hello"))|
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse data segment with offset keyword" {
let source =
#|(module (memory 1) (data (offset (i32.const 0)) "hello"))|
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse named data segment" {
let source =
#|(module (memory 1) (data $d (i32.const 0) "hello"))|
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse data segment multiple strings" {
let source =
#|(module (memory 1) (data (i32.const 0) "hello" " " "world"))|
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse data segment with escapes" {
let source =
#|(module (memory 1) (data (i32.const 0) "hello\nworld\00"))|
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
// ----------------------------------------------------------------------------
// Element Segment Tests
// ----------------------------------------------------------------------------
///|
test "parse elem segment" {
let source = "(module (table 1 funcref) (func $f) (elem (i32.const 0) $f))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse elem segment with offset keyword" {
let source = "(module (table 1 funcref) (func $f) (elem (offset (i32.const 0)) $f))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse named elem segment" {
let source = "(module (table 1 funcref) (func $f) (elem $e (i32.const 0) $f))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse elem segment multiple funcs" {
let source = "(module (table 3 funcref) (func $a) (func $b) (func $c) (elem (i32.const 0) $a $b $c))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse elem segment by index" {
let source = "(module (table 1 funcref) (func) (elem (i32.const 0) 0))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse elem segment with typed ref.null item expressions" {
let source =
#|(module
#| (table 1 funcref)
#| (func $f)
#| (elem (i32.const 0) funcref (item (ref.null func)) (item (ref.func $f))))|
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
// ----------------------------------------------------------------------------
// Complex Expression Tests
// ----------------------------------------------------------------------------
///|
test "parse deeply nested folded expressions" {
let source =
#|(module (func (result i32)
#| (i32.add
#| (i32.mul
#| (i32.const 2)
#| (i32.const 3))
#| (i32.sub
#| (i32.const 10)
#| (i32.const 4)))))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse mixed flat and folded" {
let source =
#|(module (func (param i32) (result i32)
#| local.get 0
#| (i32.add (i32.const 1))
#| i32.const 2
#| i32.mul))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse fibonacci recursive" {
let source =
#|(module
#| (func $fib (param $n i32) (result i32)
#| (if (result i32) (i32.lt_s (local.get $n) (i32.const 2))
#| (then (local.get $n))
#| (else
#| (i32.add
#| (call $fib (i32.sub (local.get $n) (i32.const 1)))
#| (call $fib (i32.sub (local.get $n) (i32.const 2))))))))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse factorial iterative" {
let source =
#|(module
#| (func $fact (param $n i32) (result i32)
#| (local $result i32)
#| (local.set $result (i32.const 1))
#| (block $exit
#| (loop $loop
#| (br_if $exit (i32.le_s (local.get $n) (i32.const 1)))
#| (local.set $result (i32.mul (local.get $result) (local.get $n)))
#| (local.set $n (i32.sub (local.get $n) (i32.const 1)))
#| (br $loop)))
#| (local.get $result)))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
// ----------------------------------------------------------------------------
// Complete Module Tests
// ----------------------------------------------------------------------------
///|
test "parse complete module with all sections" {
let source =
#|(module $complete
#| ;; Types
#| (type $unary (func (param i32) (result i32)))
#| (type $binary (func (param i32 i32) (result i32)))
#|
#| ;; Imports
#| (import "env" "log" (func $log (param i32)))
#| (import "env" "memory" (memory $mem 1))
#|
#| ;; Table
#| (table $tbl 2 funcref)
#|
#| ;; Globals
#| (global $counter (mut i32) (i32.const 0))
#| (global $max i32 (i32.const 100))
#|
#| ;; Functions
#| (func $inc (type $unary)
#| (i32.add (local.get 0) (i32.const 1)))
#|
#| (func $add (type $binary)
#| (i32.add (local.get 0) (local.get 1)))
#|
#| (func $main
#| (call $log (call $inc (i32.const 41))))
#|
#| ;; Element segment
#| (elem (i32.const 0) $inc $add)
#|
#| ;; Data segment
#| (data (i32.const 0) "Hello, World!")
#|
#| ;; Exports
#| (export "inc" (func $inc))
#| (export "add" (func $add))
#| (export "table" (table $tbl))
#| (export "counter" (global $counter))
#|
#| ;; Start
#| (start $main))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let mod = parser.parse_module()
assert_false(parser.has_errors())
@debug.assert_eq(mod.id, Some("$complete"))
}
// ----------------------------------------------------------------------------
// Edge Case Tests
// ----------------------------------------------------------------------------
///|
test "parse hex integers" {
let source = "(module (func (result i32) i32.const 0xDEADBEEF))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse underscore in numbers" {
let source = "(module (func (result i32) i32.const 1_000_000))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse empty block in if" {
let source = "(module (func (param i32) (if (local.get 0) (then) (else))))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse unreachable and nop" {
let source = "(module (func unreachable) (func nop))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse many fields in module" {
let source =
#|(module
#| (func) (func) (func) (func) (func)
#| (func) (func) (func) (func) (func))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let mod = parser.parse_module()
assert_false(parser.has_errors())
@debug.assert_eq(mod.fields.length(), 10)
}
///|
test "parse function with many params" {
let source = "(module (func (param i32 i32 i32 i32 i32 i32 i32 i32)))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse f32 special values" {
let source =
#|(module
#| (func (result f32) f32.const inf)
#| (func (result f32) f32.const -inf)
#| (func (result f32) f32.const nan))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse f64 special values" {
let source =
#|(module
#| (func (result f64) f64.const inf)
#| (func (result f64) f64.const -inf)
#| (func (result f64) f64.const nan))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
// ----------------------------------------------------------------------------
// If Statement Variations
// ----------------------------------------------------------------------------
///|
test "parse if flat form simple" {
let source =
#|(module (func (param i32)
#| local.get 0
#| if
#| nop
#| end))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse if flat form with else" {
let source =
#|(module (func (param i32)
#| local.get 0
#| if
#| nop
#| else
#| nop
#| end))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse if flat form with result" {
let source =
#|(module (func (param i32) (result i32)
#| local.get 0
#| if (result i32)
#| i32.const 1
#| else
#| i32.const 0
#| end))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse if folded with condition" {
let source = "(module (func (param i32) (if (local.get 0) (then nop))))"
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse if folded complex condition" {
let source =
#|(module (func (param i32 i32)
#| (if (i32.and (local.get 0) (local.get 1))
#| (then nop)
#| (else nop))))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse if inside block" {
let source =
#|(module (func (param i32)
#| (block
#| (if (local.get 0)
#| (then (br 1))))))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
test "parse if inside loop" {
let source =
#|(module (func (param i32)
#| (loop $continue
#| (if (local.get 0)
#| (then (br $continue))))))
let lexer = make_lexer(source)
let parser = WastParser::new(lexer)
let _ = parser.parse_module()
assert_false(parser.has_errors())
}
///|
pub impl Show for FuncType with fn output(self, logger) {
logger.write_string(@debug.to_string(self))
}
///|
pub impl Show for TypeUse with fn output(self, logger) {
logger.write_string(@debug.to_string(self))
}
///|
pub impl Show for Annotation with fn output(self, logger) {
logger.write_string(@debug.to_string(self))
}
///|
pub impl Show for InlineExport with fn output(self, logger) {
logger.write_string(@debug.to_string(self))
}
///|
pub impl Show for ImportDesc with fn output(self, logger) {
logger.write_string(@debug.to_string(self))
}
///|
pub impl Show for Import with fn output(self, logger) {
logger.write_string(@debug.to_string(self))
}
///|
pub impl Show for ExportDesc with fn output(self, logger) {
logger.write_string(@debug.to_string(self))
}
///|
pub impl Show for Export with fn output(self, logger) {
logger.write_string(@debug.to_string(self))
}
///|
pub impl Show for TableType with fn output(self, logger) {
logger.write_string(@debug.to_string(self))
}
///|
pub impl Show for MemoryType with fn output(self, logger) {
logger.write_string(@debug.to_string(self))
}
///|
pub impl Show for GlobalType with fn output(self, logger) {
logger.write_string(@debug.to_string(self))
}
///|
pub impl Show for Local with fn output(self, logger) {
logger.write_string(@debug.to_string(self))
}
///|
pub impl Show for Func with fn output(self, logger) {
logger.write_string(@debug.to_string(self))
}
///|
pub impl Show for Global with fn output(self, logger) {
logger.write_string(@debug.to_string(self))
}
///|
pub impl Show for Table with fn output(self, logger) {
logger.write_string(@debug.to_string(self))
}
///|
pub impl Show for Memory with fn output(self, logger) {
logger.write_string(@debug.to_string(self))
}
///|
pub impl Show for Tag with fn output(self, logger) {
logger.write_string(@debug.to_string(self))
}
///|
pub impl Show for FieldStorage with fn output(self, logger) {
logger.write_string(@debug.to_string(self))
}
///|
pub impl Show for StructFieldDef with fn output(self, logger) {
logger.write_string(@debug.to_string(self))
}
///|
pub impl Show for TypeDefBody with fn output(self, logger) {
logger.write_string(@debug.to_string(self))
}
///|
pub impl Show for TypeDefMetadata with fn output(self, logger) {
logger.write_string(@debug.to_string(self))
}
///|
pub impl Show for TypeDef with fn output(self, logger) {
logger.write_string(@debug.to_string(self))
}
///|
pub impl Show for DataSegment with fn output(self, logger) {
logger.write_string(@debug.to_string(self))
}
///|
pub impl Show for ElemSegment with fn output(self, logger) {
logger.write_string(@debug.to_string(self))
}
///|
pub impl Show for ElemInitExpr with fn output(self, logger) {
logger.write_string(@debug.to_string(self))
}
///|
pub impl Show for Start with fn output(self, logger) {
logger.write_string(@debug.to_string(self))
}
///|
pub impl Show for ModuleField with fn output(self, logger) {
logger.write_string(@debug.to_string(self))
}
///|
pub impl Show for MemArg with fn output(self, logger) {
logger.write_string(@debug.to_string(self))
}
///|
pub impl Show for BlockType with fn output(self, logger) {
logger.write_string(@debug.to_string(self))
}
///|
pub impl Show for V128Const with fn output(self, logger) {
logger.write_string(@debug.to_string(self))
}
///|
pub impl Show for LegacyCatchClause with fn output(self, logger) {
logger.write_string(@debug.to_string(self))
}
///|
pub impl Show for Instruction with fn output(self, logger) {
logger.write_string(@debug.to_string(self))
}
///|
pub impl Show for WastValue with fn output(self, logger) {
logger.write_string(@debug.to_string(self))
}
///|
pub impl Show for WastResult with fn output(self, logger) {
logger.write_string(@debug.to_string(self))
}
///|
pub impl Show for WastAction with fn output(self, logger) {
logger.write_string(@debug.to_string(self))
}
///|
pub impl Show for WastActionType with fn output(self, logger) {
logger.write_string(@debug.to_string(self))
}
///|
pub impl Show for WastCommand with fn output(self, logger) {
logger.write_string(@debug.to_string(self))
}
///|
pub impl Show for WastModuleDef with fn output(self, logger) {
logger.write_string(@debug.to_string(self))
}
///|
pub impl Show for ParseError with fn output(self, logger) {
logger.write_string(@debug.to_string(self))
}
///|
fn WastParser::parse_memory_access(
self : WastParser,
op : Opcode,
align : UInt64,
store : Bool,
) -> Instruction raise ParserError {
let mut array_type : Index? = None
if self.check(Lpar) {
let saved_state = self.save_lexer_state()
let saved_token = self.current
self.expect_lpar()
if self.check(Type) {
ignore(self.advance())
array_type = Some(self.parse_index())
self.expect_rpar()
} else {
self.restore_lexer_state(saved_state)
self.current = saved_token
}
}
let ma = self.parse_mem_arg(align)
match array_type {
Some(ti) =>
if store {
ArrayStore(op, ti, ma)
} else {
ArrayLoad(op, ti, ma)
}
None =>
match op {
V128Load => V128Load(ma)
V128Store => V128Store(ma)
_ => if store { Store(op, ma) } else { Load(op, ma) }
}
}
}
///|
fn WastParser::parse_atomic_order_pair(
self : WastParser,
) -> AtomicOrder raise ParserError {
let first = self.try_parse_atomic_order().unwrap_or(SeqCst)
let second = self.try_parse_atomic_order().unwrap_or(SeqCst)
if first != second {
raise UnexpectedToken(
self.current,
"aggregate atomic memory orders must be identical",
)
}
first
}
///|
fn WastParser::parse_memory_type(
self : WastParser,
) -> MemoryType raise ParserError {
let memory64 = self.current.token_type == ValueType(I64)
if memory64 || self.current.token_type == ValueType(I32) {
let _ = self.advance()
}
if !self.check(Nat) {
raise UnexpectedToken(self.current, "expected minimum limit")
}
let min = self.parse_memory_limit(memory64)
let max : UInt64? = if self.check(Nat) {
Some(self.parse_memory_limit(memory64))
} else {
None
}
let shared = self.is_reserved_word("shared")
if shared {
let _ = self.advance()
}
MemoryType::new(min, max~, shared~, memory64~)
}
///|
fn WastParser::parse_memory_limit(
self : WastParser,
memory64 : Bool,
) -> UInt64 raise ParserError {
let token = self.expect(Nat, "expected memory limit")
let text = self.get_literal_text(token)
let hex = text.has_prefix("0x") || text.has_prefix("0X")
let radix = if hex { 16UL } else { 10UL }
let limit = if memory64 { 18446744073709551615UL } else { 4294967295UL }
let mut value = 0UL
for i = (if hex { 2 } else { 0 }); i < text.length(); i = i + 1 {
let c = text[i]
if c == '_' {
continue
}
let digit = if c >= '0' && c <= '9' {
(c.to_int() - 48).to_uint64()
} else if c >= 'a' && c <= 'f' {
(c.to_int() - 87).to_uint64()
} else if c >= 'A' && c <= 'F' {
(c.to_int() - 55).to_uint64()
} else {
raise UnexpectedToken(token, "invalid memory limit")
}
if digit >= radix || value > (limit - digit) / radix {
raise UnexpectedToken(token, "memory limit exceeds address width")
}
value = value * radix + digit
}
value
}