// Assignment and control flow.
//
// This is where wap earns most of its keep: the labels a Wax loop needs are
// generated here, and `break` and `continue` name the loop rather than the
// branch target. Nothing is emitted that was not used -- a loop no one breaks
// out of gets no enclosing block.
///|
/// `x = e`, `x += e`, and `(x, y) = (a, b)`.
fn Lowering::assign(
self : Lowering,
targets : Array[@wap.Node],
op : @wap.AssignOp,
value : @wap.Node,
sp : @wap.Span,
) -> Array[@ast.Instr[@basic.Location]] {
let at = self.loc(sp)
if targets.length() == 1 {
return [self.assign_one(targets[0], op, value, sp)]
}
// Multiple assignment reads every right-hand side before writing any left,
// which on wasm means one temporary per target. They are named in the
// output; they are the whole cost of `(x, y) = (y, x)`.
let values = match value.it {
TupleLit(items) => items
_ => {
self.error(
"assigning to several names needs as many values",
sp,
help="write `(x, y) = (a, b)`, or bind the call's results with `let`",
)
return [@ast.build(Unreachable, at)]
}
}
if values.length() != targets.length() {
self.error(
"there are " +
targets.length().to_string() +
" names and " +
values.length().to_string() +
" values",
sp,
)
return [@ast.build(Unreachable, at)]
}
let out = []
let temps = []
for i, v in values {
let name = self.gensym("t")
temps.push(name)
let vt = self.type_of(v)
match vt {
Some(t) => self.bind(name, t)
None => ()
}
out.push(
@ast.build(
Let(
[(Some({ name, loc: self.fresh_loc(), }), None)],
Some(self.expr(v, None)),
),
at,
),
)
ignore(i)
}
for i, t in targets {
let tmp : @wap.Node = { it: Var(temps[i]), span: sp, }
out.push(self.assign_one(t, Set, tmp, sp))
}
out
}
///|
fn Lowering::assign_one(
self : Lowering,
target : @wap.Node,
op : @wap.AssignOp,
value : @wap.Node,
sp : @wap.Span,
) -> @ast.Instr[@basic.Location] {
let at = self.loc(sp)
let ttyp = self.type_of(target)
match target.it {
Var(n) => {
let bop : @basic.Annotated[@ast.BinOp, @basic.Location]? = match op {
Set => None
OpSet(o) => Some({ desc: self.wax_binop(o, ttyp, sp), info: at, })
}
@ast.build(
Set(self.ident(n, target.span), bop, self.expr(value, ttyp)),
at,
)
}
Field(recv, name) => {
let base = self.expr(recv, None)
let rhs = match op {
Set => self.expr(value, ttyp)
OpSet(o) => {
let old = self.widen(
@ast.build(StructGet(base, self.ident(name, sp)), at),
ttyp,
sp,
)
@ast.build(
BinOpI(
{ desc: self.wax_binop(o, ttyp, sp), info: at, },
old,
self.expr(value, ttyp),
),
at,
)
}
}
@ast.build(
StructSet(self.expr(recv, None), self.ident(name, sp), rhs),
at,
)
}
Index(a, i) => {
let rhs = match op {
Set => self.expr(value, ttyp)
OpSet(o) => {
let old = self.widen(
@ast.build(ArrayGet(self.expr(a, None), self.expr(i, None)), at),
ttyp,
sp,
)
@ast.build(
BinOpI(
{ desc: self.wax_binop(o, ttyp, sp), info: at, },
old,
self.expr(value, ttyp),
),
at,
)
}
}
@ast.build(ArraySet(self.expr(a, None), self.expr(i, None), rhs), at)
}
_ => {
self.error("this is not something that can be assigned to", target.span)
@ast.build(Unreachable, at)
}
}
}
///|
/// A compound assignment's operator, with the target's signedness.
fn Lowering::wax_binop(
self : Lowering,
o : @wap.BinOp,
t : @wap.Type?,
sp : @wap.Span,
) -> @ast.BinOp {
let sign = self.signage(t)
match o {
Add => Add
Sub => Sub
Mul => Mul
Div => Div(sign)
Rem =>
match sign {
Some(s) => Rem(s)
None => {
self.error("`%=` is an integer operator", sp)
Rem(Signed)
}
}
Shl => Shl
Shr =>
match sign {
Some(s) => Shr(s)
None => {
self.error("`>>=` is an integer operator", sp)
Shr(Signed)
}
}
BitAnd => And
BitOr => Or
BitXor => Xor
_ => {
self.error("this operator has no compound form", sp)
Add
}
}
}
// ---------------------------------------------------------------- if / match
///|
/// `if`, in all three of its shapes, as nested Wax conditionals.
fn Lowering::conditional(
self : Lowering,
arms : Array[(@wap.Node?, Array[@wap.Node])],
typ : @wap.Type?,
sp : @wap.Span,
) -> @ast.Instr[@basic.Location] {
let at = self.loc(sp)
let ftyp = match typ {
Some(t) => ({ params: [], results: [self.valtype(t, sp)], } : @ast.FuncType)
None => empty_type()
}
seq_of(self.conditional_from(arms, 0, ftyp, sp, at), at)
}
///|
/// An arm and everything after it, as the instructions it contributes.
///
/// The `else` arm's body is spliced into the else block rather than wrapped:
/// a `Sequence` is a value, and an arm whose body is an assignment does not
/// produce one.
fn Lowering::conditional_from(
self : Lowering,
arms : Array[(@wap.Node?, Array[@wap.Node])],
i : Int,
ftyp : @ast.FuncType,
sp : @wap.Span,
at : @basic.Location,
) -> Array[@ast.Instr[@basic.Location]] {
if i >= arms.length() {
return []
}
let (cond, body) = arms[i]
match cond {
None => self.body(body)
Some(c) => {
let rest = self.conditional_from(arms, i + 1, ftyp, sp, at)
let else_block : @basic.Annotated[
Array[@ast.Instr[@basic.Location]],
@basic.Location,
]? = if rest.length() > 0 {
Some({ desc: rest, info: at, })
} else {
None
}
[
@ast.build(
If(
label=None,
typ=ftyp,
cond=self.expr(c, None),
if_block={ desc: self.body(body), info: at, },
else_block~,
),
at,
),
]
}
}
}
///|
/// `match`, over types or over values.
///
/// Type patterns become Wax's `match`, which is a type switch. Value patterns
/// become a comparison chain: correct for every label set, and the printed
/// expansion says so.
fn Lowering::match_expr(
self : Lowering,
scrutinee : @wap.Node,
arms : Array[@wap.Arm],
typ : @wap.Type?,
sp : @wap.Span,
) -> Array[@ast.Instr[@basic.Location]] {
let mut type_match = false
for a in arms {
if a.pat is PType(..) || a.pat is PNull {
type_match = true
}
}
if type_match {
[self.type_match(scrutinee, arms, typ, sp)]
} else {
self.value_match(scrutinee, arms, typ, sp)
}
}
///|
fn Lowering::type_match(
self : Lowering,
scrutinee : @wap.Node,
arms : Array[@wap.Arm],
typ : @wap.Type?,
sp : @wap.Span,
) -> @ast.Instr[@basic.Location] {
let at = self.loc(sp)
// Wax's `match` is a statement: every arm branches or returns, and the
// construct itself leaves nothing behind. A wap `match` that stands for a
// value therefore needs the block to branch out of, which is what the
// corpus writes by hand as `do t { match ... }`.
let mut yields = arms.length() > 0
for a in arms {
if a.body.length() == 0 || is_terminator(a.body[a.body.length() - 1]) {
yields = false
}
}
let result = if yields {
match typ {
Some(t) => Some(t)
None => {
let mut found = None
for a in arms {
if found is None {
found = self.type_of_body(a.body)
}
}
found
}
}
} else {
None
}
if yields && result is None {
self.error(
"cannot tell what type this `match` produces",
sp,
help="annotate it with `-> t`, or end every arm with `return`",
)
}
let label = if result is Some(_) { Some(self.gensym("match")) } else { None }
let out : Array[
(
@ast.MatchPattern,
@basic.Annotated[Array[@ast.Instr[@basic.Location]], @basic.Location],
),
] = []
let mut default : @basic.Annotated[
Array[@ast.Instr[@basic.Location]],
@basic.Location,
] = { desc: [@ast.build(Unreachable, at)], info: at, }
for a in arms {
match a.pat {
PWild => {
self.push_scope()
default = {
desc: self.arm_body(a.body, label, a.span),
info: self.loc(a.span),
}
self.pop_scope()
}
PNull => {
self.push_scope()
out.push(
(
MatchNull,
{
desc: self.arm_body(a.body, label, a.span),
info: self.loc(a.span),
},
),
)
self.pop_scope()
}
PType(name~, typ~) => {
let v = self.valtype(typ, a.span)
let rt = match v {
Ref(r) => r
_ => {
self.error("a `match` arm tests a reference type", a.span)
({ nullable: true, typ: Any, } : @wasm_types.RefType[@ast.Ident])
}
}
self.push_scope()
match name {
Some(n) => self.bind(n, typ)
None => ()
}
let body : @basic.Annotated[
Array[@ast.Instr[@basic.Location]],
@basic.Location,
] = {
desc: self.arm_body(a.body, label, a.span),
info: self.loc(a.span),
}
self.pop_scope()
out.push(
(
MatchCast(
match name {
Some(n) => Some(self.ident(n, a.span))
None => None
},
rt,
),
body,
),
)
}
_ => self.error("a type `match` cannot also match on values", a.span)
}
}
let m = @ast.build(
Match(scrutinee=self.expr(scrutinee, None), arms=out, default~),
at,
)
match (label, result) {
(Some(l), Some(t)) =>
@ast.build(
Block(
label=Some(self.ident(l, sp)),
typ={ params: [], results: [self.valtype(t, sp)], },
block={ desc: [m], info: at, },
),
at,
)
_ => m
}
}
///|
/// An arm's body, ending in the branch that carries its value out.
fn Lowering::arm_body(
self : Lowering,
body : Array[@wap.Node],
label : String?,
sp : @wap.Span,
) -> Array[@ast.Instr[@basic.Location]] {
match label {
None => self.body(body)
Some(l) => {
let out = []
for i, e in body {
if i == body.length() - 1 {
out.push(
@ast.build(
Br(self.ident(l, sp), Some(self.expr(e, None))),
self.loc(sp),
),
)
} else {
for x in self.stmts(e) {
out.push(x)
}
}
}
out
}
}
}
///|
/// True when an expression leaves the block it is in, so nothing after it --
/// and nothing around it -- receives a value.
fn is_terminator(e : @wap.Node) -> Bool {
match e.it {
Return(_) | Break(_) | Continue(_) | Unreachable => true
_ => false
}
}
///|
fn Lowering::value_match(
self : Lowering,
scrutinee : @wap.Node,
arms : Array[@wap.Arm],
typ : @wap.Type?,
sp : @wap.Span,
) -> Array[@ast.Instr[@basic.Location]] {
let at = self.loc(sp)
let stype = self.type_of(scrutinee)
// The scrutinee is tested once per arm, so it is bound once.
let tmp = self.gensym("m")
match stype {
Some(t) => self.bind(tmp, t)
None => self.bind(tmp, I32)
}
let bind_instr = @ast.build(
Let(
[(Some({ name: tmp, loc: self.fresh_loc(), }), None)],
Some(self.expr(scrutinee, None)),
),
at,
)
let subject : @wap.Node = { it: Var(tmp), span: sp, }
let chain : Array[(@wap.Node?, Array[@wap.Node])] = []
for a in arms {
match a.pat {
PWild => chain.push((None, a.body))
_ =>
match self.pattern_test(subject, a.pat, stype, a.span) {
Some(c) => chain.push((Some(c), a.body))
None => ()
}
}
}
ignore(at)
[bind_instr, self.conditional(chain, typ, sp)]
}
///|
/// The condition an arm's pattern stands for.
fn Lowering::pattern_test(
self : Lowering,
subject : @wap.Node,
pat : @wap.Pattern,
stype : @wap.Type?,
sp : @wap.Span,
) -> @wap.Node? {
ignore(stype)
match pat {
PInt(v) =>
Some({ it: Bin(Eq, subject, { it: Int(v), span: sp, }), span: sp, })
PRange(low~, high~) => {
let lo : @wap.Node = {
it: Bin(Ge, subject, { it: Int(low), span: sp, }),
span: sp,
}
let hi : @wap.Node = {
it: Bin(Le, subject, { it: Int(high), span: sp, }),
span: sp,
}
Some({ it: AndAlso(lo, hi), span: sp, })
}
PSet(items) => {
let mut acc : @wap.Node? = None
for p in items {
match self.pattern_test(subject, p, stype, sp) {
Some(c) =>
acc = Some(
match acc {
Some(a) => ({ it: OrElse(a, c), span: sp, } : @wap.Node)
None => c
},
)
None => ()
}
}
acc
}
PWild => None
_ => {
self.error("this pattern cannot appear in a value `match`", sp)
None
}
}
}