// Lookup tables and small helpers used by the grammar's semantic actions.
//
// Wax spells types as ordinary identifiers rather than keywords -- `i32` is an
// IDENT, not a token -- so the grammar accepts any identifier in a type
// position and classifies it here. That is why "not a value type" is a semantic
// error raised from an action rather than a parse error: the token stream is
// fine, the name is not.
///|
/// Absolute heap types: the ones spelled as a bare keyword-like identifier.
/// Anything else in a heap-type position names a defined type.
let absheaptype_tbl : Map[String, @wasm_types.HeapType[Ident]] = {
"func": Func,
"nofunc": NoFunc,
"exn": Exn,
"noexn": NoExn,
"nocont": NoCont,
"extern": Extern,
"noextern": NoExtern,
"any": Any,
"eq": Eq,
"i31": I31,
"struct": Struct,
"array": Array,
"none": None_,
}
///|
let valtype_tbl : Map[String, @wasm_types.ValType[Ident]] = {
"i32": I32,
"i64": I64,
"f32": F32,
"f64": F64,
"v128": V128,
}
///|
/// Cast types that state a signedness, e.g. `i32_s`, `f64_u`, `i64_s_strict`.
///
/// Built from the same `format_signed_type` the printer uses, so the accepted
/// spellings and the emitted ones cannot drift apart. `strict` (the trapping
/// form) exists only for the integer targets: a float target never traps.
let casttype_tbl : Map[String, @ast.CastType] = build_casttype_tbl()
///|
fn build_casttype_tbl() -> Map[String, @ast.CastType] {
let m : Map[String, @ast.CastType] = Map([])
fn add(t : @ast.NumType, s : @wasm_types.Signage, strict : Bool) -> Unit {
m[@ast.format_signed_type(t, s, strict)] = Signed(typ=t, signage=s, strict~)
}
add(I32, Signed, false)
add(I32, Signed, true)
add(I32, Unsigned, false)
add(I32, Unsigned, true)
add(I64, Signed, false)
add(I64, Signed, true)
add(I64, Unsigned, false)
add(I64, Unsigned, true)
add(F32, Signed, false)
add(F32, Unsigned, false)
add(F64, Signed, false)
add(F64, Unsigned, false)
m
}
///|
let storagetype_tbl : Map[String, @wasm_types.StorageType[Ident]] = {
"i8": Packed(I8),
"i16": Packed(I16),
"i32": Value(I32),
"i64": Value(I64),
"f32": Value(F32),
"f64": Value(F64),
"v128": Value(V128),
}
///|
fn heap_type_of(t : Ident) -> @wasm_types.HeapType[Ident] {
match absheaptype_tbl.get(t.name) {
Some(h) => h
None => Type(t)
}
}
///|
fn value_type_of(
p : (Position, Position),
t : String,
) -> @wasm_types.ValType[Ident] {
match valtype_tbl.get(t) {
Some(v) => v
None =>
fail_at(loc_of(p), "Identifier '\{t}' is not a value type.", default=I32)
}
}
///|
fn cast_type_of(p : (Position, Position), t : String) -> @ast.CastType {
match valtype_tbl.get(t) {
Some(v) => Value(v)
None =>
match casttype_tbl.get(t) {
Some(c) => c
None =>
fail_at(
loc_of(p),
"Identifier '\{t}' is not a cast type.",
default=Value(I32),
)
}
}
}
///|
fn storage_type_of(
p : (Position, Position),
t : String,
) -> @wasm_types.StorageType[Ident] {
match storagetype_tbl.get(t) {
Some(s) => s
None =>
fail_at(
loc_of(p),
"Identifier '\{t}' is not a storage type.",
default=Value(I32),
)
}
}
///|
/// Apply the `!` exactness marker to a heap type.
///
/// Only a concrete named type can be exact: `&!any` is meaningless, since the
/// abstract types are not describable.
fn make_exact(
p : (Position, Position),
typ : @wasm_types.HeapType[Ident],
) -> @wasm_types.HeapType[Ident] {
match typ {
Type(t) => Exact(t)
_ => fail_at(loc_of(p), "Only a concrete type can be exact.", default=typ)
}
}
///|
/// The scalar storage type named by a data-segment numeric run, `[f32: ...]`.
fn scalar_storagetype(
p : (Position, Position),
t : Ident,
) -> @wasm_types.StorageType[Ident] {
match t.name {
"i8" => Packed(I8)
"i16" => Packed(I16)
"i32" => Value(I32)
"i64" => Value(I64)
"f32" => Value(F32)
"f64" => Value(F64)
_ =>
fail_at(
loc_of(p),
"A data numeric run needs a scalar element type ('i8', 'i16', 'i32', 'i64', 'f32', or 'f64').",
default=Value(I32),
)
}
}
///|
/// The vector shape named by a v128 run element, `i32x4(...)`.
fn vec_shape(p : (Position, Position), s : Ident) -> @wasm_types.V128Shape {
match s.name {
"i8x16" => I8x16
"i16x8" => I16x8
"i32x4" => I32x4
"i64x2" => I64x2
"f32x4" => F32x4
"f64x2" => F64x2
_ =>
fail_at(
loc_of(p),
"A v128 run element is a lane group like i32x4(1, 2, 3, 4).",
default=I32x4,
)
}
}
///|
/// A function or tag is declared with either a type reference (`: name`) or a
/// parenthesized signature; one is required.
///
/// A bare `tag stop;` or `fn f { ... }` is rejected -- write `()` for an empty
/// signature. The message names the exact repair, so a quick fix is derived
/// from it: a zero-width insertion of `()` right after the name or type
/// reference, which is where an empty parameter list belongs.
fn decl_sign(
p : (Position, Position),
t : Ident?,
sign : FuncType?,
) -> FuncType? {
if t is None && sign is None {
let caret = p.1
record_error(
loc_of(p),
"A parameter list is required.",
fix=Some({ loc: { start: caret, end: caret }, new_text: "()" }),
)
}
sign
}
///|
/// Parse an integer literal (decimal or hex, with `_` separators) as a u64.
///
/// Used for memory and table limits. A table64 bound may exceed Int64.max, so
/// the full unsigned range is parsed. Range is checked FIRST so an out-of-range
/// literal surfaces as a recoverable syntax error rather than a crash.
fn u64_of_int_literal(p : (Position, Position), n : String) -> UInt64 {
let cleaned = n.replace_all(old="_", new="")
// `parse_uint64` understands the 0x prefix itself when base is 0.
@string.parse_uint64(cleaned, base=0) catch {
_ =>
fail_at(
loc_of(p),
"The integer literal \{n} is out of range.",
default=0UL,
hint=Some(
"This integer must fit in an unsigned 64-bit value (0 to 18446744073709551615).",
),
)
}
}
///|
/// A custom page size is written `pagesize 65536` but STORED as its base-2
/// logarithm, because that is what the binary format encodes.
///
/// Requires a power of two; the further restriction to 1 or 65536 is a
/// type-checking concern, not a syntactic one.
///
/// The logarithm is computed on the 64-bit value directly rather than narrowing
/// to Int first: a literal above Int.max would overflow the narrowing. A power
/// of two has a single set bit, and its log2 is that bit's position, which
/// always fits an Int.
fn page_size_log2(p : (Position, Position), n : String) -> Int {
let v = u64_of_int_literal(p, n)
if v != 0UL && (v & (v - 1UL)) == 0UL {
let mut x = v
let mut e = 0
while x != 1UL {
x = x >> 1
e += 1
}
e
} else {
fail_at(loc_of(p), "The page size must be a power of two.", default=0)
}
}
///|
/// The address type of a memory or table, `i32` or `i64`.
fn address_type_of(
p : (Position, Position),
t : String,
) -> @wasm_types.AddressType {
match t {
"i32" => I32
"i64" => I64
_ =>
fail_at(
loc_of(p),
"Expected a memory address type 'i32' or 'i64'.",
default=I32,
)
}
}
///|
/// A version component of a conditional-compilation predicate,
/// `#[if(version = (1, 2, 3))]`.
///
/// Guarded so an over-long component surfaces as a recoverable syntax error
/// rather than a crash.
fn int_of_version_component(p : (Position, Position), s : String) -> Int {
@string.parse_int(s) catch {
_ =>
fail_at(
loc_of(p),
"The version component \{s} is out of range.",
default=0,
)
}
}
///|
/// Build a data numeric run, `[t: ...]`.
///
/// Elements are tagged by their shape as they are parsed; the wrong kind for
/// the run's element type is a syntax error rather than a silent coercion.
fn data_run(
p : (Position, Position),
t : Ident,
items : Array[DataRunItem],
) -> @ast.DataElem {
if t.name == "v128" {
let out : Array[Annotated[@wasm_types.V128, Location]] = []
for it in items {
match it {
Vec(v) => out.push(v)
Num(n) => {
record_error(
n.info,
"Expected a v128 lane group like i32x4(1, 2, 3, 4).",
)
out.push({ desc: { shape: I32x4, components: [] }, info: n.info })
}
}
}
V128Run(out)
} else {
let out : Array[Annotated[String, Location]] = []
for it in items {
match it {
Num(n) => out.push(n)
Vec(v) => {
record_error(
v.info,
"Expected a scalar literal, not a v128 lane group.",
)
out.push({ desc: "0", info: v.info })
}
}
}
Run(scalar_storagetype(p, t), out)
}
}
///|
/// One element of a data run, before it has been checked against the run's
/// element type.
priv enum DataRunItem {
Num(Annotated[String, Location])
Vec(Annotated[@wasm_types.V128, Location])
}