// Expressions, lowered to Wax instructions.
//
// Every case here is a translation rather than a compilation: what comes out is
// something a person could have written in was, which is the property that
// makes `wap -f was` a specification rather than a debugging aid.
///|
/// Lower a sequence of expressions.
fn Lowering::body(
self : Lowering,
body : Array[@wap.Node],
) -> Array[@ast.Instr[@basic.Location]] {
let out = []
for e in body {
for i in self.stmts(e) {
out.push(i)
}
}
out
}
///|
/// One wap expression as the instructions it becomes in statement position.
///
/// Several wap forms are more than one Wax instruction -- a `for` is a binding
/// and a `while`, a multiple assignment is a temporary per target -- and Wax's
/// `let` scopes over the rest of the block it is in. Splicing them into the
/// enclosing block is therefore not a tidiness: wrapping them in a `Sequence`
/// would put a binding somewhere a value is expected.
fn Lowering::stmts(
self : Lowering,
e : @wap.Node,
) -> Array[@ast.Instr[@basic.Location]] {
match e.it {
Block(items) => self.body(items)
Assign(targets~, op~, value~) => self.assign(targets, op, value, e.span)
Match(scrutinee~, arms~, typ~) =>
self.match_expr(scrutinee, arms, typ, e.span)
ForRange(label~, var_~, from~, to~, inclusive~, by~, body~) =>
self.for_range(label, var_, from, to, inclusive, by, body, e.span)
ForIn(label~, var_~, seq~, body~) =>
self.for_in(label, var_, seq, body, e.span)
_ => [self.expr(e, None)]
}
}
///|
/// A block of instructions with a location, which is what Wax's block-shaped
/// constructors take.
fn Lowering::blk(
self : Lowering,
body : Array[@wap.Node],
span : @wap.Span,
) -> @basic.Annotated[Array[@ast.Instr[@basic.Location]], @basic.Location] {
{ desc: self.body(body), info: self.loc(span), }
}
///|
/// True when `&&` or `||` should short-circuit rather than combine bits.
///
/// An unannotated operand -- a bare integer literal, a call wap cannot see the
/// signature of -- is treated as a condition, because that is what an untyped
/// operand of a logical operator nearly always is.
fn Lowering::is_boolean(self : Lowering, a : @wap.Node, b : @wap.Node) -> Bool {
let known = match self.type_of(a) {
Some(t) => Some(t)
None => self.type_of(b)
}
match known {
Some(t) => self.underlying(t) is Bool
None => true
}
}
///|
/// Bitwise or, which has no infix spelling of its own.
fn Lowering::bit_or(
self : Lowering,
a : @wap.Node,
b : @wap.Node,
sp : @wap.Span,
) -> @ast.Instr[@basic.Location] {
let at = self.loc(sp)
let t = match self.type_of(a) {
Some(t) => Some(t)
None => self.type_of(b)
}
@ast.build(
BinOpI({ desc: Or, info: at, }, self.expr(a, t), self.expr(b, t)),
at,
)
}
///|
/// One instruction, or a sequence of them.
fn seq_of(
xs : Array[@ast.Instr[@basic.Location]],
at : @basic.Location,
) -> @ast.Instr[@basic.Location] {
if xs.length() == 1 {
xs[0]
} else {
@ast.build(Sequence(xs), at)
}
}
///|
/// An empty function type: Wax reads it as "no annotation", and infers.
fn empty_type() -> @ast.FuncType {
{ params: [], results: [], }
}
///|
/// A function type with one result, for the conditionals that stand in for
/// `&&` and `||`.
fn Lowering::i32_type(self : Lowering) -> @ast.FuncType {
ignore(self)
{ params: [], results: [I32], }
}
///|
/// Lower one expression.
fn Lowering::expr(
self : Lowering,
e : @wap.Node,
expected : @wap.Type?,
) -> @ast.Instr[@basic.Location] {
let sp = e.span
let at = self.loc(sp)
match e.it {
Int(s) => @ast.build(Int(s), at)
Float(s) => @ast.build(Float(s), at)
BoolLit(b) => @ast.build(Int(if b { "1" } else { "0" }), at)
CharLit(c) => @ast.build(Char(c), at)
Null => @ast.build(Null, at)
Nop => @ast.build(Nop, at)
Unreachable => @ast.build(Unreachable, at)
StrLit(s) => {
let name = self.array_type(U8, sp)
@ast.build(Str(Some(self.ident(name, sp)), @utf8.encode(s)), at)
}
Var(n) => self.var_ref(n, sp)
Drop(inner) =>
@ast.build(Let([(None, None)], Some(self.expr(inner, None))), at)
Field(recv, name) =>
// `hashing.limit` is a name in another module, not a field of a local.
match self.module_ref(recv, name) {
Some(q) => {
self.check_visible(q, sp)
self.var_ref(q, sp)
}
None => {
let base = @ast.build(
StructGet(self.expr(recv, None), self.ident(name, sp)),
at,
)
self.widen(base, self.field_of(recv, name), sp)
}
}
Index(a, i) =>
// `ints[0]` is an array literal when `ints` is a type, and an index when
// it is a value. Only the declarations know which.
match a.it {
Var(n) if self.nominal(n) =>
@ast.build(
ArrayFixed(Some(self.ident(self.mangle(n), sp)), [
self.expr(i, self.elem_of_named(n)),
]),
at,
)
_ => {
let base = @ast.build(
ArrayGet(self.expr(a, None), self.expr(i, None)),
at,
)
let elem = match self.type_of(a) {
Some(t) => self.elem_type(t)
None => None
}
self.widen(base, elem, sp)
}
}
TupleLit(items) => {
// A tuple is its elements, in order: wasm multi-value, not a value.
let out = []
for it in items {
out.push(self.expr(it, None))
}
@ast.build(Sequence(out), at)
}
SetLit(items) => self.set_literal(items, expected, sp)
RecordLit(typ~, fields~) => {
let name = match typ {
Some(n) => Some(self.ident(self.mangle(n), sp))
None => self.expected_record(expected, sp)
}
let fs = []
for f in fields {
let (fname, value) = f
let ftyp = match typ {
Some(t) => self.field_type(t, fname)
None => None
}
fs.push(
(
self.ident(fname, sp),
match value {
Some(v) => Some(self.expr(v, ftyp))
None => None
},
),
)
}
@ast.build(Struct(name, fs), at)
}
RecordDefault(typ~) => {
let name = match typ {
Some(n) => Some(self.ident(self.mangle(n), sp))
None => self.expected_record(expected, sp)
}
@ast.build(StructDefault(name), at)
}
ArrayLit(typ~, items~) => {
let name = match typ {
Some(n) => Some(self.ident(self.mangle(n), sp))
None => self.expected_array(expected, sp)
}
let elem = self.elem_hint(typ, expected)
let out = []
for it in items {
out.push(self.expr(it, elem))
}
@ast.build(ArrayFixed(name, out), at)
}
ArrayRepeat(typ~, value~, count~) => {
let name = match typ {
Some(n) => Some(self.ident(self.mangle(n), sp))
None => self.expected_array(expected, sp)
}
let elem = self.elem_hint(typ, expected)
@ast.build(
Array(name, self.expr(value, elem), self.expr(count, None)),
at,
)
}
Call(callee, args) => {
let f = match callee.it {
Var(n) => self.var_ref(n, callee.span)
_ => self.expr(callee, None)
}
let sig = match callee.it {
Var(n) => self.funcs.get(self.qualify(n))
_ => None
}
let out = []
for i, a in args {
let hint = match sig {
Some(s) =>
if i < s.params.length() {
Some(s.params[i])
} else {
None
}
None => None
}
out.push(self.expr(a, hint))
}
@ast.build(Call(f, out), at)
}
MethodCall(recv, name, args) => self.method_call(recv, name, args, sp)
Bin(op, a, b) => self.binary(op, a, b, sp)
Un(Neg, a) =>
@ast.build(UnOpI({ desc: Neg, info: at, }, self.expr(a, expected)), at)
Un(Not, a) =>
@ast.build(UnOpI({ desc: Not, info: at, }, self.expr(a, None)), at)
// `&&` and `||` are logical on `bool` and bitwise on integers, decided by
// the operands' types -- the same rule that makes `<` a signed or an
// unsigned comparison. Shrubbery's `|` is the alternatives marker, so
// there is no separate bitwise spelling to give them.
AndAlso(a, b) =>
if self.is_boolean(a, b) {
@ast.build(
If(
label=None,
typ=self.i32_type(),
cond=self.expr(a, None),
if_block={ desc: [self.expr(b, None)], info: at, },
else_block=Some({ desc: [@ast.build(Int("0"), at)], info: at, }),
),
at,
)
} else {
self.binary(BitAnd, a, b, sp)
}
OrElse(a, b) =>
if self.is_boolean(a, b) {
@ast.build(
If(
label=None,
typ=self.i32_type(),
cond=self.expr(a, None),
if_block={ desc: [@ast.build(Int("1"), at)], info: at, },
else_block=Some({ desc: [self.expr(b, None)], info: at, }),
),
at,
)
} else {
self.bit_or(a, b, sp)
}
InSet(x, s) => self.in_set(x, s, sp)
Cast(inner, t) => self.cast(inner, t, sp)
Test(inner, t) => {
let v = self.valtype(t, sp)
match v {
Ref(r) => @ast.build(Test(self.expr(inner, None), r), at)
_ => {
self.error("`is` tests a reference type", sp)
@ast.build(Int("0"), at)
}
}
}
NonNull(inner) => @ast.build(NonNull(self.expr(inner, None)), at)
Block(items) => @ast.build(Sequence(self.body(items)), at)
Bind(binders~, value~, ..) => {
let bs = []
for b in binders {
let t = match b.typ {
Some(t) => Some(t)
None =>
match value {
Some(v) =>
if binders.length() == 1 {
self.type_of(v)
} else {
None
}
None => None
}
}
match t {
Some(t) => self.bind(b.name, t)
None => ()
}
bs.push(
(
Some(self.ident(b.name, b.span)),
match b.typ {
Some(t) => Some(self.valtype(t, b.span))
None => None
},
),
)
}
let v = match value {
Some(v) =>
Some(
self.expr(
v,
if binders.length() == 1 {
binders[0].typ
} else {
None
},
),
)
None => None
}
@ast.build(Let(bs, v), at)
}
Assign(targets~, op~, value~) =>
seq_of(self.assign(targets, op, value, sp), at)
If(arms~, typ~) => self.conditional(arms, typ, sp)
While(label~, cond~, step~, body~) =>
self.while_loop(label, cond, step, body, sp)
Loop(label~, body~) => self.plain_loop(label, body, sp)
ForRange(label~, var_~, from~, to~, inclusive~, by~, body~) =>
seq_of(self.for_range(label, var_, from, to, inclusive, by, body, sp), at)
ForIn(label~, var_~, seq~, body~) => {
let s = seq
seq_of(self.for_in(label, var_, s, body, sp), at)
}
Break(label) => self.jump(label, true, sp)
Continue(label) => self.jump(label, false, sp)
Return(v) =>
@ast.build(
Return(
match v {
Some(v) => Some(self.expr(v, None))
None => None
},
),
at,
)
Match(scrutinee~, arms~, typ~) =>
seq_of(self.match_expr(scrutinee, arms, typ, sp), at)
}
}
// ------------------------------------------------------------------ names
///|
/// A name in expression position: an enumerator, a local, a global or a
/// function.
fn Lowering::var_ref(
self : Lowering,
n : String,
sp : @wap.Span,
) -> @ast.Instr[@basic.Location] {
let at = self.loc(sp)
// An enumerator is a constant, and never reaches the emitted module.
if self.local_type(n) is None {
match self.member_owner.get(self.qualify(n)) {
Some(owner) =>
match self.enum_members.get(owner) {
Some(ms) =>
match ms.get(n) {
Some(v) => return @ast.build(Int(v.to_string()), at)
None => ()
}
None => ()
}
None => ()
}
}
if self.local_type(n) is Some(_) {
return @ast.build(Get(self.ident(n, sp)), at)
}
match self.funcs.get(self.qualify(n)) {
Some(sig) => @ast.build(Get(self.ident(sig.emitted, sp)), at)
None =>
if self.globals.contains(self.qualify(n)) {
@ast.build(Get(self.ident(self.mangle(n), sp)), at)
} else {
@ast.build(Get(self.ident(n, sp)), at)
}
}
}
///|
/// The declared type of `recv.name`, if it is knowable.
fn Lowering::field_of(
self : Lowering,
recv : @wap.Node,
name : String,
) -> @wap.Type? {
match self.type_of(recv) {
Some(t) =>
match self.record_name(t) {
Some(r) => self.field_type(r, name)
None => None
}
None => None
}
}
///|
/// The element type of a named array type.
fn Lowering::elem_of_named(self : Lowering, n : String) -> @wap.Type? {
self.elem_type(Named(n))
}
///|
/// An element-type hint for the items of an array literal.
fn Lowering::elem_hint(
self : Lowering,
typ : String?,
expected : @wap.Type?,
) -> @wap.Type? {
match typ {
Some(n) => self.elem_type(Named(n))
None =>
match expected {
Some(t) => self.elem_type(t)
None => None
}
}
}
///|
/// The record an untyped `{...}` should build, from the expected type.
fn Lowering::expected_record(
self : Lowering,
expected : @wap.Type?,
sp : @wap.Span,
) -> @ast.Ident? {
match expected {
Some(t) =>
match self.record_name(t) {
Some(r) => Some(self.ident(self.mangle(r), sp))
None => None
}
None => None
}
}
///|
/// The array type an untyped `[...]` should build.
fn Lowering::expected_array(
self : Lowering,
expected : @wap.Type?,
sp : @wap.Span,
) -> @ast.Ident? {
match expected {
Some(Named(n)) if self.nominal(n) => Some(self.ident(self.mangle(n), sp))
Some(ArrayOf(e)) => Some(self.ident(self.array_type(e, sp), sp))
_ => None
}
}
///|
/// Widen a packed field or element on the way out.
///
/// Wax makes this explicit -- `arr[i] as i32_u` -- because the signedness is
/// not recoverable from the storage type. In wap it is: `u8` says unsigned and
/// `i8` says signed, so the cast is inserted rather than written.
fn Lowering::widen(
self : Lowering,
base : @ast.Instr[@basic.Location],
typ : @wap.Type?,
sp : @wap.Span,
) -> @ast.Instr[@basic.Location] {
match typ {
Some(t) =>
if self.is_packed(t) {
let signage = match self.signage(Some(t)) {
Some(s) => s
None => @wasm_types.Signage::Unsigned
}
@ast.build(
Cast(base, Signed(typ=I32, signage~, strict=false)),
self.loc(sp),
)
} else {
base
}
None => base
}
}