// The individual module fields.
///|
/// `type NAME = ...` and `type NAME :: PARENT = ...`
fn Reader::type_entry(
self : Reader,
head : Array[@sh.Node],
p : Parts,
) -> @basic.Annotated[(@ast.Ident, @ast.SubType), @basic.Location] raise ReadError {
let at = self.loc(p.span)
let ts = head[1:].to_owned()
let name = match
(ts.length() > 0, if ts.length() > 0 { as_id(ts[0]) } else { None }) {
(true, Some(n)) => self.ident(n, node_span(ts[0]))
_ => fail_at("expected a name after `type`", p.span, source=self.src)
}
let mut i = 1
let mut supertype = None
if i < ts.length() && is_op(ts[i], "::") {
match
(
i + 1 < ts.length(),
if i + 1 < ts.length() {
as_id(ts[i + 1])
} else {
None
},
) {
(true, Some(sn)) => {
supertype = Some(self.ident(sn, node_span(ts[i + 1])))
i += 2
}
_ =>
fail_at(
"expected the supertype's name after `::`",
p.span,
source=self.src,
)
}
}
if !(i < ts.length() && is_op(ts[i], "=")) {
fail_at("expected `=` in a type declaration", p.span, source=self.src)
}
let rhs = ts[i + 1:].to_owned()
// The body may be on the next line, as a block.
let rhs = match (rhs.length() == 0, p.block) {
(true, Some(gs)) =>
if gs.length() == 1 {
children(gs[0])
} else {
fail_at("a type's body is one group", p.span, source=self.src)
}
_ => rhs
}
let c = Cursor::new(rhs, self)
let final_ = !c.eat_id("open")
let mut descriptor = None
let mut describes = None
if c.eat_id("descriptor") {
descriptor = Some(self.take_name(c, "descriptor"))
} else if c.eat_id("describes") {
describes = Some(self.take_name(c, "describes"))
}
let typ = self.comptype(c, p)
{
desc: (name, { typ, supertype, final_, descriptor, describes, }),
info: at,
}
}
///|
fn Reader::take_name(
self : Reader,
c : Cursor,
what : String,
) -> @ast.Ident raise ReadError {
let here = c.here()
match c.next() {
Some(n) =>
match as_id(n) {
Some(s) => self.ident(s, node_span(n))
None =>
fail_at(
"expected a name after `" + what + "`",
node_span(n),
source=self.src,
)
}
None =>
fail_at("expected a name after `" + what + "`", here, source=self.src)
}
}
///|
/// A struct, an array, a function type or a continuation type.
fn Reader::comptype(
self : Reader,
c : Cursor,
p : Parts,
) -> @ast.CompType raise ReadError {
let here = c.here()
match c.next() {
None => fail_at("expected a type body", here, source=self.src)
Some(n) =>
match n.it {
Braces(gs) => Struct(self.fields_of(gs))
Brackets(gs) => {
if gs.length() != 1 {
fail_at(
"an array type takes one element type",
node_span(n),
source=self.src,
)
}
Array(Cursor::new(children(gs[0]), self).muttype())
}
Id("fn") => {
let params = match c.next() {
Some(ps) => ps
None =>
fail_at(
"expected a parameter list after `fn`",
node_span(n),
source=self.src,
)
}
Func(self.signature(params, c))
}
Id("cont") => Cont(self.take_name(c, "cont"))
_ => {
ignore(p)
fail_at(
"expected `{ ... }`, `[ ... ]`, `fn(...)` or `cont t`",
node_span(n),
source=self.src,
)
}
}
}
}
///|
/// A struct's fields, including the `..` that repeats a supertype's.
fn Reader::fields_of(
self : Reader,
gs : Array[@sh.Node],
) -> Array[
@basic.Annotated[
(@ast.Ident, @wasm_types.MutType[@wasm_types.StorageType[@ast.Ident]]),
@basic.Location,
],
] raise ReadError {
let out = []
for g in gs {
let ts = children(g)
let at = self.loc(terms_span(ts[:]))
if ts.length() == 1 && is_op(ts[0], "..") {
out.push(@ast.splice_field(at))
continue
}
let name = match
(ts.length() > 0, if ts.length() > 0 { as_id(ts[0]) } else { None }) {
(true, Some(n)) => self.ident(n, node_span(ts[0]))
_ => fail_at("expected a field name", terms_span(ts[:]), source=self.src)
}
if !(ts.length() > 2 && is_op(ts[1], "::")) {
fail_at(
"expected `::` and a type after the field's name",
terms_span(ts[:]),
source=self.src,
)
}
let typ = Cursor::new(ts[2:].to_owned(), self).muttype()
out.push({ desc: (name, typ), info: at, })
}
out
}
///|
/// `fn NAME(params) -> results:` and the bodiless declaration form.
fn Reader::fn_field(
self : Reader,
head : Array[@sh.Node],
p : Parts,
mods : Mods,
) -> @ast.ModuleField[@basic.Location] raise ReadError {
let ts = head[1:].to_owned()
let name = match
(ts.length() > 0, if ts.length() > 0 { as_id(ts[0]) } else { None }) {
(true, Some(n)) => self.ident(n, node_span(ts[0]))
_ => fail_at("expected a name after `fn`", p.span, source=self.src)
}
if ts.length() < 2 {
fail_at("expected a parameter list", p.span, source=self.src)
}
let c = Cursor::new(ts[2:].to_owned(), self)
let sign = self.signature(ts[1], c)
let (label, body) = match p.block {
Some(gs) => (None, self.body(gs))
None => (None, [])
}
Func(
name~,
typ=None,
sign=Some(sign),
body=(label, body),
attributes=mods.attrs,
)
}
///|
/// `const NAME :: t = e` (immutable) and `let NAME :: t = e` (mutable).
fn Reader::global_field(
self : Reader,
head : Array[@sh.Node],
p : Parts,
mods : Mods,
mut_ : Bool,
) -> @ast.ModuleField[@basic.Location] raise ReadError {
let ts = head[1:].to_owned()
let name = match
(ts.length() > 0, if ts.length() > 0 { as_id(ts[0]) } else { None }) {
(true, Some(n)) => self.ident(n, node_span(ts[0]))
_ => fail_at("expected a name", p.span, source=self.src)
}
let mut i = 1
let mut typ = None
if i < ts.length() && is_op(ts[i], "::") {
let c = Cursor::new(ts[i + 1:].to_owned(), self)
typ = Some(c.valtype())
i = i + 1 + c.i
}
let def = if i < ts.length() && is_op(ts[i], "=") {
self.expr_all(ts[i + 1:].to_owned())
} else {
// A global with no initialiser is an import; Wax still wants a node there.
self.instr(Nop, p.span)
}
Global(name~, mut_~, typ~, def~, attributes=mods.attrs)
}
///|
/// `tag NAME(types) -> results`
fn Reader::tag_field(
self : Reader,
head : Array[@sh.Node],
p : Parts,
mods : Mods,
) -> @ast.ModuleField[@basic.Location] raise ReadError {
let ts = head[1:].to_owned()
let name = match
(ts.length() > 0, if ts.length() > 0 { as_id(ts[0]) } else { None }) {
(true, Some(n)) => self.ident(n, node_span(ts[0]))
_ => fail_at("expected a name after `tag`", p.span, source=self.src)
}
if ts.length() < 2 {
fail_at(
"expected a parameter list after the tag's name",
p.span,
source=self.src,
)
}
let c = Cursor::new(ts[2:].to_owned(), self)
let sign = self.signature(ts[1], c)
Tag(name~, typ=None, sign=Some(sign), attributes=mods.attrs)
}
///|
/// `memory NAME :: i32 [min, max] pagesize N shared`
fn Reader::memory_field(
self : Reader,
head : Array[@sh.Node],
p : Parts,
mods : Mods,
) -> @ast.ModuleField[@basic.Location] raise ReadError {
let ts = head[1:].to_owned()
let name = match
(ts.length() > 0, if ts.length() > 0 { as_id(ts[0]) } else { None }) {
(true, Some(n)) => self.ident(n, node_span(ts[0]))
_ => fail_at("expected a name after `memory`", p.span, source=self.src)
}
let c = Cursor::new(ts[1:].to_owned(), self)
let address_type : @wasm_types.AddressType = if c.eat_op("::") {
if c.eat_id("i64") {
I64
} else {
let _ = c.eat_id("i32")
I32
}
} else {
I32
}
let limits = self.limits(c)
let mut page_size_log2 = None
if c.eat_id("pagesize") {
let here = c.here()
match c.next() {
Some(n) =>
match int_of(raw_text(n)) {
Some(v) => page_size_log2 = Some(log2_of(v))
None =>
fail_at(
"`pagesize` takes an integer",
node_span(n),
source=self.src,
)
}
None => fail_at("`pagesize` takes an integer", here, source=self.src)
}
}
let shared = c.eat_id("shared")
Memory(
name~,
address_type~,
limits~,
page_size_log2~,
shared~,
data=[],
attributes=mods.attrs,
)
}
///|
/// `[min]` or `[min, max]`.
fn Reader::limits(
self : Reader,
c : Cursor,
) -> (UInt64, UInt64?)? raise ReadError {
match c.peek() {
Some(n) =>
match n.it {
Brackets(gs) => {
c.i += 1
if gs.length() == 0 || gs.length() > 2 {
fail_at(
"limits are `[min]` or `[min, max]`",
node_span(n),
source=self.src,
)
}
let mi = self.u64_of(gs[0])
let ma = if gs.length() == 2 {
Some(self.u64_of(gs[1]))
} else {
None
}
Some((mi, ma))
}
_ => None
}
None => None
}
}
///|
fn Reader::u64_of(self : Reader, g : @sh.Node) -> UInt64 raise ReadError {
let ts = children(g)
if ts.length() != 1 {
fail_at("expected an integer", terms_span(ts[:]), source=self.src)
}
match int_of(raw_text(ts[0])) {
Some(v) => v.to_uint64()
None => fail_at("expected an integer", node_span(ts[0]), source=self.src)
}
}
///|
/// `table NAME :: &?func [min, max]`
fn Reader::table_field(
self : Reader,
head : Array[@sh.Node],
p : Parts,
mods : Mods,
) -> @ast.ModuleField[@basic.Location] raise ReadError {
let ts = head[1:].to_owned()
let name = match
(ts.length() > 0, if ts.length() > 0 { as_id(ts[0]) } else { None }) {
(true, Some(n)) => self.ident(n, node_span(ts[0]))
_ => fail_at("expected a name after `table`", p.span, source=self.src)
}
let c = Cursor::new(ts[1:].to_owned(), self)
if !c.eat_op("::") {
fail_at("expected `::` and an element type", p.span, source=self.src)
}
let reftype = c.reftype()
let limits = self.limits(c)
let init = if c.eat_op("=") { Some(self.expr_from(c)) } else { None }
Table(
name~,
address_type=I32,
reftype~,
limits~,
init~,
attributes=mods.attrs,
)
}
///|
/// `elem NAME :: &?func = [f, g]`
fn Reader::elem_field(
self : Reader,
head : Array[@sh.Node],
p : Parts,
mods : Mods,
) -> @ast.ModuleField[@basic.Location] raise ReadError {
let ts = head[1:].to_owned()
let name = match
(ts.length() > 0, if ts.length() > 0 { as_id(ts[0]) } else { None }) {
(true, Some(n)) => self.ident(n, node_span(ts[0]))
_ => fail_at("expected a name after `elem`", p.span, source=self.src)
}
let c = Cursor::new(ts[1:].to_owned(), self)
// `elem e at t[0] :: &?func = [...]` is the active form.
let mut mode : @ast.ElemMode[@basic.Location] = EPassive
if c.eat_id("at") {
let table = self.take_name(c, "at")
let offset = match c.next() {
Some(n) =>
match n.it {
Brackets(gs) =>
if gs.length() == 1 {
self.group(gs[0])
} else {
fail_at("expected one offset", node_span(n), source=self.src)
}
_ => fail_at("expected `[offset]`", node_span(n), source=self.src)
}
None => fail_at("expected `[offset]`", p.span, source=self.src)
}
mode = EActive(table, offset)
}
if !c.eat_op("::") {
fail_at("expected `::` and an element type", p.span, source=self.src)
}
let reftype = c.reftype()
let init = if c.eat_op("=") {
match c.next() {
Some(n) =>
match n.it {
Brackets(gs) => {
let out = []
for g in gs {
out.push(self.group(g))
}
out
}
_ =>
fail_at("expected a bracketed list", node_span(n), source=self.src)
}
None => fail_at("expected a bracketed list", p.span, source=self.src)
}
} else {
[]
}
Elem(name~, reftype~, mode~, init~, attributes=mods.attrs)
}
///|
/// `data NAME at MEM[off] = "bytes" ++ f32[1.0]`
fn Reader::data_field(
self : Reader,
head : Array[@sh.Node],
p : Parts,
mods : Mods,
) -> @ast.ModuleField[@basic.Location] raise ReadError {
let ts = head[1:].to_owned()
let mut i = 0
let mut name = None
if i < ts.length() && as_id(ts[i]) is Some(n) && !is_id(ts[i], "at") {
name = Some(self.ident(n, node_span(ts[i])))
i += 1
}
let mut mode : @ast.DataMode[@basic.Location] = Passive
if i < ts.length() && is_id(ts[i], "at") {
i += 1
let mem = match
(i < ts.length(), if i < ts.length() { as_id(ts[i]) } else { None }) {
(true, Some(m)) => self.ident(m, node_span(ts[i]))
_ => fail_at("expected a memory name after `at`", p.span, source=self.src)
}
i += 1
let offset = match
(i < ts.length(), if i < ts.length() { Some(ts[i]) } else { None }) {
(true, Some(n)) =>
match n.it {
Brackets(gs) =>
if gs.length() == 1 {
self.group(gs[0])
} else {
fail_at("expected one offset", node_span(n), source=self.src)
}
_ => fail_at("expected `[offset]`", node_span(n), source=self.src)
}
_ => fail_at("expected `[offset]`", p.span, source=self.src)
}
i += 1
mode = Active(mem, offset)
}
if !(i < ts.length() && is_op(ts[i], "=")) {
fail_at("expected `=` and the segment's contents", p.span, source=self.src)
}
let init = self.data_elems(ts[i + 1:].to_owned())
Data(name~, mode~, init~, attributes=mods.attrs)
}
///|
/// `"bytes" ++ f32[1.0, 0.5] ++ i16[640, 480]`
fn Reader::data_elems(
self : Reader,
ts : Array[@sh.Node],
) -> Array[@ast.DataElem] raise ReadError {
let parts : Array[Array[@sh.Node]] = []
let current = []
for t in ts {
if is_op(t, "++") {
parts.push(current.copy())
current.clear()
} else {
current.push(t)
}
}
parts.push(current)
let out = []
for part in parts {
if part.length() == 1 && as_str(part[0]) is Some(s) {
out.push(@ast.DataElem::Str(@utf8.encode(s)))
} else if part.length() == 2 && part[1].it is Brackets(gs) {
let st = Cursor::new([part[0]], self).storagetype()
let vals = []
for g in gs {
let vs = children(g)
vals.push(
(
{ desc: literal_text(vs, self), info: self.loc(terms_span(vs[:])), } :
@basic.Annotated[String, @basic.Location]),
)
}
out.push(@ast.DataElem::Run(st, vals))
} else {
fail_at(
"a data segment is a string or `t[a, b]`, joined with `++`",
terms_span(part[:]),
source=self.src,
)
}
}
out
}
///|
/// A numeric literal, with an optional sign.
fn literal_text(ts : Array[@sh.Node], r : Reader) -> String raise ReadError {
if ts.length() == 1 {
raw_text(ts[0])
} else if ts.length() == 2 && (is_op(ts[0], "-") || is_op(ts[0], "+")) {
as_op(ts[0]).unwrap() + raw_text(ts[1])
} else {
fail_at("expected a literal", terms_span(ts[:]), source=r.src)
}
}
///|
/// `import "env": `
fn Reader::import_field(
self : Reader,
head : Array[@sh.Node],
p : Parts,
) -> @ast.ModuleField[@basic.Location] raise ReadError {
if head.length() < 2 || as_str(head[1]) is None {
fail_at(
"`import` takes the module name as a string",
p.span,
source=self.src,
)
}
let module_name = as_str(head[1]).unwrap()
let at = self.loc(node_span(head[1]))
let decls : Array[@basic.Annotated[@ast.ImportDecl, @basic.Location]] = []
match p.block {
None =>
fail_at(
"expected `:` and a list of declarations",
p.span,
source=self.src,
)
Some(gs) =>
for g in gs {
decls.push(self.import_decl(g))
}
}
ImportGroup(module_={ desc: @utf8.encode(module_name), info: at, }, decls~)
}
///|
fn Reader::import_decl(
self : Reader,
g : @sh.Node,
) -> @basic.Annotated[@ast.ImportDecl, @basic.Location] raise ReadError {
let p = split(g)
let (mods, head) = self.mods(p.head)
let at = self.loc(p.span)
if head.length() == 0 {
fail_at("expected a declaration", p.span, source=self.src)
}
let ts = head[1:].to_owned()
let name = match
(ts.length() > 0, if ts.length() > 0 { as_id(ts[0]) } else { None }) {
(true, Some(n)) => self.ident(n, node_span(ts[0]))
_ => fail_at("expected a name", p.span, source=self.src)
}
let kind : @ast.ImportKind = match as_id(head[0]) {
Some("fn") => {
if ts.length() < 2 {
fail_at("expected a parameter list", p.span, source=self.src)
}
let c = Cursor::new(ts[2:].to_owned(), self)
Func(typ=None, sign=Some(self.signature(ts[1], c)), exact=false)
}
Some("const") | Some("let") => {
let mut_ = is_id(head[0], "let")
let c = Cursor::new(ts[1:].to_owned(), self)
if !c.eat_op("::") {
fail_at("expected `::` and a type", p.span, source=self.src)
}
Global(mut_~, typ=c.valtype())
}
Some("tag") => {
if ts.length() < 2 {
fail_at("expected a parameter list", p.span, source=self.src)
}
let c = Cursor::new(ts[2:].to_owned(), self)
Tag(typ=None, sign=Some(self.signature(ts[1], c)))
}
Some("memory") => {
let c = Cursor::new(ts[1:].to_owned(), self)
let address_type : @wasm_types.AddressType = if c.eat_op("::") {
if c.eat_id("i64") {
I64
} else {
let _ = c.eat_id("i32")
I32
}
} else {
I32
}
Memory(
address_type~,
limits=self.limits(c),
page_size_log2=None,
shared=false,
)
}
Some("table") => {
let c = Cursor::new(ts[1:].to_owned(), self)
if !c.eat_op("::") {
fail_at("expected `::` and an element type", p.span, source=self.src)
}
let reftype = c.reftype()
Table(address_type=I32, reftype~, limits=self.limits(c))
}
_ =>
fail_at(
"an import declares a `fn`, `const`, `let`, `tag`, `memory` or `table`",
p.span,
source=self.src,
)
}
{ desc: { id: name, kind, attributes: mods.attrs, }, info: at, }
}
///|
/// `cfg(cond): ... | ...`
fn Reader::cfg_field(
self : Reader,
head : Array[@sh.Node],
p : Parts,
) -> @ast.ModuleField[@basic.Location] raise ReadError {
if head.length() < 2 {
fail_at("`cfg` takes a condition in parentheses", p.span, source=self.src)
}
let cond = match head[1].it {
Parens(gs) =>
if gs.length() == 1 {
self.cond(children(gs[0]))
} else {
fail_at(
"`cfg` takes one condition",
node_span(head[1]),
source=self.src,
)
}
_ =>
fail_at(
"`cfg` takes a condition in parentheses",
node_span(head[1]),
source=self.src,
)
}
let at = self.loc(p.span)
let then_fields = match p.block {
Some(gs) => self.field_list(gs)
None =>
match p.alts {
Some(alts) if alts.length() >= 1 => self.field_list(alts[0])
_ =>
fail_at(
"expected `:` or `|` after the condition",
p.span,
source=self.src,
)
}
}
let else_fields = match (p.block, p.alts) {
(Some(_), Some(alts)) if alts.length() >= 1 =>
Some(self.field_list(alts[0]))
(None, Some(alts)) if alts.length() >= 2 => Some(self.field_list(alts[1]))
_ => None
}
Conditional(
cond~,
then_fields={ desc: then_fields, info: at, },
else_fields=match else_fields {
Some(f) => Some({ desc: f, info: at, })
None => None
},
)
}
///|
fn Reader::field_list(
self : Reader,
gs : Array[@sh.Node],
) -> @ast.LocModule raise ReadError {
let out : @ast.LocModule = []
for g in gs {
match self.module_field(g) {
Some(f) => out.push(f)
None => ()
}
}
out
}
///|
/// A conditional-compilation condition.
fn Reader::cond(
self : Reader,
ts : Array[@sh.Node],
) -> @wasm_types.Cond raise ReadError {
// `a || b`, then `a && b`, then `!a`, then an atom.
for i, t in ts {
if is_op(t, "||") {
return Or([
self.cond(ts[0:i].to_owned()),
self.cond(ts[i + 1:].to_owned()),
])
}
}
for i, t in ts {
if is_op(t, "&&") {
return And([
self.cond(ts[0:i].to_owned()),
self.cond(ts[i + 1:].to_owned()),
])
}
}
if ts.length() >= 2 && is_op(ts[0], "!") {
return Not(self.cond(ts[1:].to_owned()))
}
if ts.length() == 1 {
match ts[0].it {
Id(n) => return Var({ desc: n, info: self.loc(node_span(ts[0])), })
Lit(Str(s)) =>
return Str({ desc: @utf8.encode(s), info: self.loc(node_span(ts[0])), })
Parens(gs) => if gs.length() == 1 { return self.cond(children(gs[0])) }
_ => ()
}
}
fail_at("expected a condition", terms_span(ts[:]), source=self.src)
}
///|
/// An integer literal in any radix.
fn int_of(text : String) -> Int? {
Some(@string.parse_int(text)) catch {
_ => None
}
}
///|
/// The base-2 logarithm of a page size, which is how Wax stores it.
fn log2_of(n : Int) -> Int {
let mut v = n
let mut k = 0
while v > 1 {
v = v / 2
k += 1
}
k
}