// The forms whose head is a keyword.
///|
/// One of Wax's keyword forms, or nothing.
fn Reader::form(
self : Reader,
p : Parts,
) -> @ast.Instr[@basic.Location]? raise ReadError {
let head = p.head
if head.length() == 0 {
return None
}
let kw = match as_id(head[0]) {
Some(k) => k
None => return None
}
let rest = head[1:].to_owned()
let at = p.span
match kw {
"unreachable" => Some(self.instr(Unreachable, at))
"nop" => Some(self.instr(Nop, at))
"let" => Some(self.let_form(rest, p))
"if" => Some(self.if_form(rest, p))
"do" => Some(self.block_form(rest, p, false))
"loop" => Some(self.block_form(rest, p, true))
"while" => Some(self.while_form(rest, p))
"match" => Some(self.match_form(rest, p))
"dispatch" => Some(self.dispatch_form(rest, p))
"try" => Some(self.try_form(rest, p))
"return" =>
Some(
self.instr(
Return(
if rest.length() == 0 {
None
} else {
Some(self.expr_all(rest))
},
),
at,
),
)
"become" => {
let call = self.expr_all(rest)
match call.desc {
Call(f, args) => Some(self.instr(TailCall(f, args), at))
_ => fail_at("`become` takes a call", at, source=self.src)
}
}
"throw" => {
let (name, args) = self.tag_call(rest, "throw")
Some(self.instr(Throw(name, args), at))
}
"suspend" => {
let (name, args) = self.tag_call(rest, "suspend")
Some(self.instr(Suspend(name, args), at))
}
"throw_ref" => Some(self.instr(ThrowRef(self.expr_all(rest)), at))
// Wax spells this `c ? a : b`, whose `:` would open a block here.
"select" => {
if rest.length() != 1 {
fail_at(
"`select` takes three parenthesised expressions",
at,
source=self.src,
)
}
match rest[0].it {
Parens(gs) =>
if gs.length() == 3 {
Some(
self.instr(
Select(self.group(gs[0]), self.group(gs[1]), self.group(gs[2])),
at,
),
)
} else {
fail_at(
"`select` takes three expressions",
node_span(rest[0]),
source=self.src,
)
}
_ =>
fail_at(
"`select` takes three parenthesised expressions",
at,
source=self.src,
)
}
}
"br" => {
let (l, r) = self.label_and_rest(rest, "br")
Some(
self.instr(
Br(l, if r.length() == 0 { None } else { Some(self.expr_all(r)) }),
at,
),
)
}
"br_if" => {
let (l, r) = self.label_and_rest(rest, "br_if")
Some(self.instr(BrIf(l, self.expr_all(r)), at))
}
"br_on_null" => {
let (l, r) = self.label_and_rest(rest, "br_on_null")
Some(self.instr(BrOnNull(l, self.expr_all(r)), at))
}
"br_on_non_null" => {
let (l, r) = self.label_and_rest(rest, "br_on_non_null")
Some(self.instr(BrOnNonNull(l, self.expr_all(r)), at))
}
"br_on_cast" | "br_on_cast_fail" => {
let (l, r) = self.label_and_rest(rest, kw)
let c = Cursor::new(r, self)
let rt = c.reftype()
let value = self.expr_from(c)
Some(
self.instr(
if kw == "br_on_cast" {
BrOnCast(l, rt, value)
} else {
BrOnCastFail(l, rt, value)
},
at,
),
)
}
"br_table" => {
if rest.length() == 0 {
fail_at("`br_table` takes a label list", at, source=self.src)
}
let labels = match rest[0].it {
Brackets(gs) => self.label_list(gs)
_ =>
fail_at(
"expected a label list in brackets",
node_span(rest[0]),
source=self.src,
)
}
Some(self.instr(BrTable(labels, self.expr_all(rest[1:].to_owned())), at))
}
_ => None
}
}
///|
/// `let x :: t = e`, `let (q, r) = e`, `let x :: t`.
fn Reader::let_form(
self : Reader,
ts : Array[@sh.Node],
p : Parts,
) -> @ast.Instr[@basic.Location] raise ReadError {
let mut eq = -1
for i, t in ts {
if is_op(t, "=") {
eq = i
break
}
}
let lhs = if eq < 0 { ts } else { ts[0:eq].to_owned() }
let binders = self.binders(lhs)
let value = if eq < 0 {
None
} else {
Some(self.expr_all(ts[eq + 1:].to_owned()))
}
self.instr(Let(binders, value), p.span)
}
///|
/// The names a `let` binds, with their optional annotations.
fn Reader::binders(
self : Reader,
ts : Array[@sh.Node],
) -> Array[(@ast.Ident?, @wasm_types.ValType[@ast.Ident]?)] raise ReadError {
if ts.length() == 1 && ts[0].it is Parens(gs) {
let out = []
for g in gs {
for b in self.binders(children(g)) {
out.push(b)
}
}
return out
}
if ts.length() == 0 {
fail_at("expected a name to bind", nowhere, source=self.src)
}
if is_id(ts[0], "_") && ts.length() == 1 {
return [(None, None)]
}
let name = match as_id(ts[0]) {
Some(s) => s
None => fail_at("expected a name", node_span(ts[0]), source=self.src)
}
if ts.length() == 1 {
return [(Some(self.ident(name, node_span(ts[0]))), None)]
}
if !is_op(ts[1], "::") {
fail_at("expected `::` and a type", node_span(ts[1]), source=self.src)
}
let typ = Cursor::new(ts[2:].to_owned(), self).valtype()
[(Some(self.ident(name, node_span(ts[0]))), Some(typ))]
}
///|
/// `if ~l c -> t: ... | else`, and `if c | then | else`.
fn Reader::if_form(
self : Reader,
ts : Array[@sh.Node],
p : Parts,
) -> @ast.Instr[@basic.Location] raise ReadError {
let (label, ts) = label_of(ts)
let c = Cursor::new(ts, self)
let cond = self.expr_from_bp(c, 0)
let typ = self.block_type(c)
let (if_block, else_block) = match (p.block, p.alts) {
(Some(gs), None) => (self.body(gs), None)
(Some(gs), Some(alts)) => {
if alts.length() != 1 {
fail_at(
"an `if` with a `:` body takes one alternative, the else",
p.span,
source=self.src,
)
}
(self.body(gs), Some(self.body(alts[0])))
}
(None, Some(alts)) => {
if alts.length() < 1 || alts.length() > 2 {
fail_at(
"`if c | then | else` takes one or two alternatives",
p.span,
source=self.src,
)
}
(
self.body(alts[0]),
if alts.length() == 2 {
Some(self.body(alts[1]))
} else {
None
},
)
}
(None, None) =>
fail_at(
"expected `:` or `|` after the condition",
p.span,
source=self.src,
)
}
self.instr(
If(
label=self.opt_ident(label, p.span),
typ~,
cond~,
if_block=self.blk(if_block, p.span),
else_block=match else_block {
Some(b) => Some(self.blk(b, p.span))
None => None
},
),
p.span,
)
}
///|
/// `do ~l -> t:` and `loop ~l -> t:`.
fn Reader::block_form(
self : Reader,
ts : Array[@sh.Node],
p : Parts,
looping : Bool,
) -> @ast.Instr[@basic.Location] raise ReadError {
let (label, ts) = label_of(ts)
let c = Cursor::new(ts, self)
let typ = self.block_type(c)
if !c.done() {
fail_at("expected `:` after the block's type", c.here(), source=self.src)
}
let body = self.need_block(p, if looping { "loop" } else { "do" })
let lbl = self.opt_ident(label, p.span)
let block = self.blk(body, p.span)
self.instr(
if looping {
Loop(label=lbl, typ~, block~)
} else {
Block(label=lbl, typ~, block~)
},
p.span,
)
}
///|
/// `while ~l c ~step(s):`
fn Reader::while_form(
self : Reader,
ts : Array[@sh.Node],
p : Parts,
) -> @ast.Instr[@basic.Location] raise ReadError {
let (label, ts) = label_of(ts)
let mut end = ts.length()
let mut step = None
for j, t in ts {
if as_kw(t) is Some("step") {
end = j
if j + 1 < ts.length() && ts[j + 1].it is Parens(gs) && gs.length() == 1 {
step = Some(self.group(gs[0]))
} else {
fail_at(
"`~step` takes one parenthesised expression",
node_span(t),
source=self.src,
)
}
break
}
}
let cond = self.expr_all(ts[0:end].to_owned())
let body = self.need_block(p, "while")
self.instr(
While(
label=self.opt_ident(label, p.span),
cond~,
step~,
block=self.blk(body, p.span),
),
p.span,
)
}
///|
/// `match e | p :: &t: ... | null: ... | _: ...`
fn Reader::match_form(
self : Reader,
ts : Array[@sh.Node],
p : Parts,
) -> @ast.Instr[@basic.Location] raise ReadError {
let scrutinee = self.expr_all(ts)
let arms : Array[
(
@ast.MatchPattern,
@basic.Annotated[Array[@ast.Instr[@basic.Location]], @basic.Location],
),
] = []
let mut default = self.blk([self.instr(Unreachable, p.span)], p.span)
match p.alts {
None => fail_at("`match` needs `|` arms", p.span, source=self.src)
Some(alts) =>
for alt in alts {
if alt.length() != 1 {
fail_at(
"each `match` arm is one `pattern: body`",
terms_span(alt[:]),
source=self.src,
)
}
let ap = split(alt[0])
let body = self.blk(self.need_block(ap, "this pattern"), ap.span)
let head = ap.head
if head.length() == 1 && is_id(head[0], "_") {
default = body
} else if head.length() == 1 && is_id(head[0], "null") {
arms.push((MatchNull, body))
} else {
let mut name = None
let mut rest = head
if head.length() >= 3 &&
as_id(head[0]) is Some(n) &&
is_op(head[1], "::") {
name = Some(self.ident(n, node_span(head[0])))
rest = head[2:].to_owned()
}
let rt = Cursor::new(rest, self).reftype()
arms.push((MatchCast(name, rt), body))
}
}
}
self.instr(Match(scrutinee~, arms~, default~), p.span)
}
///|
/// `dispatch e [~a, ~b, else ~d] | ~a: ... | ~b: ...`
fn Reader::dispatch_form(
self : Reader,
ts : Array[@sh.Node],
p : Parts,
) -> @ast.Instr[@basic.Location] raise ReadError {
if ts.length() == 0 {
fail_at(
"`dispatch` takes an index and a label list",
p.span,
source=self.src,
)
}
// The label list is the last bracketed term of the head.
let mut table = -1
for i, t in ts {
if t.it is Brackets(_) {
table = i
}
}
if table < 0 {
fail_at("expected a label list in brackets", p.span, source=self.src)
}
let index = self.expr_all(ts[0:table].to_owned())
let (cases, default) = match ts[table].it {
Brackets(gs) => self.dispatch_labels(gs, p.span)
_ => fail_at("expected a label list", node_span(ts[table]), source=self.src)
}
let arms : Array[
(
@ast.Ident,
@basic.Annotated[Array[@ast.Instr[@basic.Location]], @basic.Location],
),
] = []
match p.alts {
None => fail_at("`dispatch` needs `|` arms", p.span, source=self.src)
Some(alts) =>
for alt in alts {
if alt.length() != 1 {
fail_at(
"each `dispatch` arm is one `~label: body`",
terms_span(alt[:]),
source=self.src,
)
}
let ap = split(alt[0])
let body = self.blk(self.need_block(ap, "this label"), ap.span)
if ap.head.length() != 1 || as_kw(ap.head[0]) is None {
fail_at("expected a `~label`", ap.span, source=self.src)
}
arms.push((self.ident(as_kw(ap.head[0]).unwrap(), ap.span), body))
}
}
self.instr(Dispatch(index~, cases~, default~, arms~), p.span)
}
///|
fn Reader::dispatch_labels(
self : Reader,
gs : Array[@sh.Node],
at : Span,
) -> (Array[@ast.Ident], @ast.Ident) raise ReadError {
let cases = []
let mut default = None
for g in gs {
let ts = children(g)
if ts.length() == 2 && is_id(ts[0], "else") && as_kw(ts[1]) is Some(k) {
default = Some(self.ident(k, node_span(ts[1])))
} else if ts.length() == 1 && as_kw(ts[0]) is Some(k) {
cases.push(self.ident(k, node_span(ts[0])))
} else {
fail_at(
"expected `~label` or `else ~label`",
terms_span(ts[:]),
source=self.src,
)
}
}
match default {
Some(d) => (cases, d)
None => fail_at("a label list needs an `else ~label`", at, source=self.src)
}
}
///|
/// `try ~l -> t: ... | tag(binders): ...` and `try on [tag -> ~h] -> t: ...`
fn Reader::try_form(
self : Reader,
ts : Array[@sh.Node],
p : Parts,
) -> @ast.Instr[@basic.Location] raise ReadError {
let (label, ts) = label_of(ts)
let lbl = self.opt_ident(label, p.span)
// The branch-to-label form: `try on [tag -> ~h]:`
if ts.length() >= 2 && is_id(ts[0], "on") && ts[1].it is Brackets(gs) {
let catches = self.catch_list(gs)
let c = Cursor::new(ts[2:].to_owned(), self)
let typ = self.block_type(c)
let body = self.need_block(p, "try")
return self.instr(
TryTable(label=lbl, typ~, catches~, block=self.blk(body, p.span)),
p.span,
)
}
let c = Cursor::new(ts, self)
let typ = self.block_type(c)
let body = self.blk(self.need_block(p, "try"), p.span)
let arms : Array[@ast.TryCatchArm[@basic.Location]] = []
match p.alts {
None => fail_at("`try` needs `|` catch arms", p.span, source=self.src)
Some(alts) =>
for alt in alts {
if alt.length() != 1 {
fail_at(
"each catch arm is one `tag: body`",
terms_span(alt[:]),
source=self.src,
)
}
let ap = split(alt[0])
let arm_body = self.blk(self.need_block(ap, "this catch"), ap.span)
let head = ap.head
if head.length() == 1 && is_id(head[0], "_") {
arms.push({ arm_tag: None, arm_ref: false, arm_types: [], arm_body, })
} else {
let name = match as_id(head[0]) {
Some(n) => n
None => fail_at("expected a tag name", ap.span, source=self.src)
}
let arm_types = if head.length() > 1 && head[1].it is Parens(gs) {
let out = []
for g in gs {
out.push(Cursor::new(children(g), self).valtype())
}
out
} else {
[]
}
arms.push({
arm_tag: Some(self.ident(name, node_span(head[0]))),
arm_ref: false,
arm_types,
arm_body,
})
}
}
}
self.instr(TryCatch(label=lbl, typ~, block=body, arms~), p.span)
}
///|
/// `[tag -> ~h, _ -> ~a]`, the catches of a `try_table`.
fn Reader::catch_list(
self : Reader,
gs : Array[@sh.Node],
) -> Array[@ast.Catch] raise ReadError {
let out = []
for g in gs {
let ts = children(g)
let mut arrow = -1
for i, t in ts {
if is_op(t, "->") {
arrow = i
break
}
}
if arrow < 0 || arrow + 1 >= ts.length() {
fail_at("expected `tag -> ~label`", terms_span(ts[:]), source=self.src)
}
let label = match as_kw(ts[arrow + 1]) {
Some(k) => self.ident(k, node_span(ts[arrow + 1]))
None =>
fail_at(
"expected a `~label`",
node_span(ts[arrow + 1]),
source=self.src,
)
}
let lhs = ts[0:arrow].to_owned()
let by_ref = lhs.length() > 0 && is_op(lhs[lhs.length() - 1], "!")
let names = if by_ref { lhs[0:lhs.length() - 1].to_owned() } else { lhs }
if names.length() == 1 && is_id(names[0], "_") {
out.push(
if by_ref {
@ast.Catch::CatchAllRef(label)
} else {
CatchAll(label)
},
)
} else if names.length() == 1 && as_id(names[0]) is Some(n) {
let tag = self.ident(n, node_span(names[0]))
out.push(
if by_ref {
@ast.Catch::CatchRef(tag, label)
} else {
Catch(tag, label)
},
)
} else {
fail_at(
"expected a tag name or `_`",
terms_span(names[:]),
source=self.src,
)
}
}
out
}
///|
/// `[~a, ~b]`, a plain list of labels.
fn Reader::label_list(
self : Reader,
gs : Array[@sh.Node],
) -> Array[@ast.Ident] raise ReadError {
let out = []
for g in gs {
let ts = children(g)
if ts.length() == 1 && as_kw(ts[0]) is Some(k) {
out.push(self.ident(k, node_span(ts[0])))
} else {
fail_at("expected a `~label`", terms_span(ts[:]), source=self.src)
}
}
out
}
///|
/// `tag(a, b)` after `throw` or `suspend`.
fn Reader::tag_call(
self : Reader,
ts : Array[@sh.Node],
what : String,
) -> (@ast.Ident, Array[@ast.Instr[@basic.Location]]) raise ReadError {
if ts.length() == 0 || as_id(ts[0]) is None {
fail_at(
"`" + what + "` takes a tag name",
terms_span(ts[:]),
source=self.src,
)
}
let name = self.ident(as_id(ts[0]).unwrap(), node_span(ts[0]))
let args = if ts.length() > 1 && ts[1].it is Parens(gs) {
let out = []
for g in gs {
out.push(self.group(g))
}
out
} else {
[]
}
(name, args)
}
///|
/// A leading `~label` that a branch requires.
fn Reader::label_and_rest(
self : Reader,
ts : Array[@sh.Node],
what : String,
) -> (@ast.Ident, Array[@sh.Node]) raise ReadError {
if ts.length() == 0 || as_kw(ts[0]) is None {
fail_at(
"`" + what + "` takes a `~label`",
terms_span(ts[:]),
source=self.src,
)
}
(self.ident(as_kw(ts[0]).unwrap(), node_span(ts[0])), ts[1:].to_owned())
}
///|
fn Reader::opt_ident(self : Reader, name : String?, span : Span) -> @ast.Ident? {
match name {
Some(n) => Some(self.ident(n, span))
None => None
}
}
///|
/// An optional `-> t` on a block-shaped form. An absent one is the empty
/// function type, which Wax reads as "no annotation, infer".
fn Reader::block_type(
self : Reader,
c : Cursor,
) -> @ast.FuncType raise ReadError {
ignore(self)
{ params: [], results: c.results(), }
}