// Expressions: precedence climbing over a group's terms.
///|
/// An expression that must consume every term it was given.
fn Reader::expr_all(
self : Reader,
ts : Array[@sh.Node],
) -> @ast.Instr[@basic.Location] raise ReadError {
let c = Cursor::new(ts, self)
let e = self.expr_from_bp(c, 0)
if !c.done() {
fail_at(
"this is not part of the expression before it",
node_span(c.ts[c.i]),
source=self.src,
)
}
e
}
///|
/// An expression starting where the cursor is.
fn Reader::expr_from(
self : Reader,
c : Cursor,
) -> @ast.Instr[@basic.Location] raise ReadError {
self.expr_from_bp(c, 0)
}
///|
/// An assignment, or an expression.
fn Reader::assign_or_expr(
self : Reader,
ts : Array[@sh.Node],
span : Span,
) -> @ast.Instr[@basic.Location] raise ReadError {
let mut at = -1
let mut op = ""
for i, t in ts {
match as_op(t) {
Some(s) =>
if s == "=" || s == ":=" || assign_binop(s) is Some(_) {
at = i
op = s
break
}
None => ()
}
}
if at < 0 {
return self.expr_all(ts)
}
let lhs = ts[0:at].to_owned()
let rhs = ts[at + 1:].to_owned()
if rhs.length() == 0 {
fail_at(
"expected an expression on the right of the assignment",
span,
source=self.src,
)
}
let value = self.expr_all(rhs)
// `x := e` yields the value; every other form does not.
if op == ":=" {
let name = match (lhs.length() == 1, as_id(lhs[0])) {
(true, Some(n)) => self.ident(n, node_span(lhs[0]))
_ =>
fail_at("`:=` assigns to a local", terms_span(lhs[:]), source=self.src)
}
return self.instr(Tee(name, value), span)
}
let folded : @basic.Annotated[@ast.BinOp, @basic.Location]? = match
assign_binop(op) {
Some(b) => Some({ desc: b, info: self.loc(span), })
None => None
}
// A bare name is a local; anything else is a field or an element.
if lhs.length() == 1 && as_id(lhs[0]) is Some(n) {
return self.instr(
Set(self.ident(n, node_span(lhs[0])), folded, value),
span,
)
}
let target = self.expr_all(lhs)
match target.desc {
StructGet(recv, field) =>
self.instr(
StructSet(recv, field, self.fold(folded, target, value, span)),
span,
)
ArrayGet(arr, index) =>
self.instr(
ArraySet(arr, index, self.fold(folded, target, value, span)),
span,
)
_ =>
fail_at(
"this is not something that can be assigned to",
span,
source=self.src,
)
}
}
///|
/// `p.x += e` is `p.x = p.x + e`; Wax has no compound form for a field, so the
/// receiver is evaluated twice, exactly as it is written.
fn Reader::fold(
self : Reader,
op : @basic.Annotated[@ast.BinOp, @basic.Location]?,
old : @ast.Instr[@basic.Location],
value : @ast.Instr[@basic.Location],
span : Span,
) -> @ast.Instr[@basic.Location] {
match op {
None => value
Some(o) => self.instr(BinOpI(o, old, value), span)
}
}
///|
fn Reader::expr_from_bp(
self : Reader,
c : Cursor,
min_bp : Int,
) -> @ast.Instr[@basic.Location] raise ReadError {
let mut lhs = self.unary(c)
let mut compared = false
while c.peek() is Some(t) {
let name = match infix_name(t) {
Some(s) => s
None => break
}
let (lbp, rbp) = match infix_bp(name) {
Some(bp) => bp
None => break
}
if lbp < min_bp {
break
}
c.i += 1
let span = node_span(t)
match name {
"as" =>
// `as ?descriptor(d)` is a different instruction, not a different
// cast type, so it is peeled off before the type is read.
match self.desc_cast(c) {
Some((nullable, d)) =>
lhs = self.instr(CastDesc(lhs, nullable, d), span)
None => lhs = self.instr(Cast(lhs, self.cast_type(c)), span)
}
"is" => lhs = self.instr(Test(lhs, c.reftype()), span)
"on" => {
let clauses = match c.next() {
Some(n) =>
match n.it {
Brackets(gs) => self.on_clauses(gs)
_ =>
fail_at(
"`on` takes a bracketed handler list",
node_span(n),
source=self.src,
)
}
None =>
fail_at(
"`on` takes a bracketed handler list",
span,
source=self.src,
)
}
lhs = self.instr(On(lhs, clauses), span)
}
_ => {
// Wax rejects `a < b < c`; the level is non-associative there and the
// rejection is reproduced here rather than silently re-associated.
if lbp == 3 {
if compared {
fail_at(
"comparisons do not chain",
span,
source=self.src,
help="parenthesise one of them",
)
}
compared = true
}
let rhs = self.expr_from_bp(c, rbp)
match binop_of(name) {
Some(op) =>
lhs = self.instr(
BinOpI({ desc: op, info: self.loc(span), }, lhs, rhs),
span,
)
None =>
fail_at("`" + name + "` is not an operator", span, source=self.src)
}
}
}
}
lhs
}
///|
/// The `on [tag -> ~l, switch]` clauses of a resume.
fn Reader::on_clauses(
self : Reader,
gs : Array[@sh.Node],
) -> Array[@ast.OnClause] 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 {
if ts.length() == 1 && as_id(ts[0]) is Some(n) {
out.push(@ast.OnClause::OnSwitch(self.ident(n, node_span(ts[0]))))
continue
}
fail_at("expected `tag -> ~label`", terms_span(ts[:]), source=self.src)
}
let tag = match as_id(ts[0]) {
Some(n) => self.ident(n, node_span(ts[0]))
None => fail_at("expected a tag name", 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,
)
}
out.push(@ast.OnClause::OnLabel(tag, label))
}
out
}
///|
/// What `as` casts to.
fn Reader::cast_type(
self : Reader,
c : Cursor,
) -> @ast.CastType raise ReadError {
let here = c.here()
match c.peek() {
None => fail_at("expected a type after `as`", here, source=self.src)
Some(n) => {
// `as i32_u` -- widening, where the signedness is not in the type.
match as_id(n) {
Some(name) =>
match signed_cast(name) {
Some((typ, signage)) => {
c.i += 1
return Signed(typ~, signage~, strict=false)
}
None => ()
}
None => ()
}
Value(c.valtype())
}
}
}
///|
/// `as ?descriptor(d)` / `as descriptor(d)`, if that is what follows.
fn Reader::desc_cast(
self : Reader,
c : Cursor,
) -> (Bool, @ast.Instr[@basic.Location])? raise ReadError {
let save = c.i
let nullable = c.eat_op("?")
if !(c.peek() is Some(d) && is_id(d, "descriptor")) {
c.i = save
return None
}
c.i += 1
let here = c.here()
let arg = match c.next() {
Some(a) =>
match a.it {
Parens(gs) =>
if gs.length() == 1 {
self.group(gs[0])
} else {
fail_at(
"`descriptor` takes one expression",
node_span(a),
source=self.src,
)
}
_ =>
fail_at(
"`descriptor` takes one expression",
node_span(a),
source=self.src,
)
}
None => fail_at("`descriptor` takes one expression", here, source=self.src)
}
Some((nullable, arg))
}
///|
/// The Wax `as i32_u` family.
fn signed_cast(name : String) -> (@ast.NumType, @wasm_types.Signage)? {
match name {
"i32_s" => Some((I32, Signed))
"i32_u" => Some((I32, Unsigned))
"i64_s" => Some((I64, Signed))
"i64_u" => Some((I64, Unsigned))
_ => None
}
}
///|
fn Reader::unary(
self : Reader,
c : Cursor,
) -> @ast.Instr[@basic.Location] raise ReadError {
match c.peek() {
Some(t) =>
if is_op(t, "-") {
c.i += 1
let e = self.unary(c)
return self.instr(
UnOpI({ desc: Neg, info: self.loc(node_span(t)), }, e),
node_span(t),
)
} else if is_op(t, "+") {
c.i += 1
let e = self.unary(c)
return self.instr(
UnOpI({ desc: Pos, info: self.loc(node_span(t)), }, e),
node_span(t),
)
} else if is_op(t, "!") {
c.i += 1
let e = self.unary(c)
return self.instr(
UnOpI({ desc: Not, info: self.loc(node_span(t)), }, e),
node_span(t),
)
}
None => ()
}
self.postfix(c)
}
///|
fn Reader::postfix(
self : Reader,
c : Cursor,
) -> @ast.Instr[@basic.Location] raise ReadError {
let mut e = self.primary(c)
while c.peek() is Some(t) {
let sp = node_span(t)
match t.it {
Parens(gs) => {
c.i += 1
let args = []
for g in gs {
args.push(self.arg(g))
}
e = self.instr(Call(e, args), sp)
}
Brackets(gs) => {
c.i += 1
e = self.bracketed(e, gs, sp)
}
Braces(gs) => {
c.i += 1
e = self.braced(Some(e), gs, sp)
}
Op(".") => {
c.i += 1
match c.next() {
Some(f) =>
match as_id(f) {
Some("descriptor") => e = self.instr(GetDescriptor(e), sp)
Some(name) =>
e = self.instr(StructGet(e, self.ident(name, node_span(f))), sp)
None =>
fail_at(
"expected a field name after `.`",
node_span(f),
source=self.src,
)
}
None =>
fail_at("expected a field name after `.`", sp, source=self.src)
}
}
Op("!") => {
c.i += 1
e = self.instr(NonNull(e), sp)
}
Op("!.") => {
c.i += 1
let nn = self.instr(NonNull(e), sp)
match c.next() {
Some(f) =>
match as_id(f) {
Some(name) =>
e = self.instr(
StructGet(nn, self.ident(name, node_span(f))),
sp,
)
None =>
fail_at(
"expected a field name after `!.`",
node_span(f),
source=self.src,
)
}
None =>
fail_at("expected a field name after `!.`", sp, source=self.src)
}
}
_ => break
}
}
e
}
///|
/// One argument, which may be labelled: `m.load32(p, align: 2)`.
fn Reader::arg(
self : Reader,
g : @sh.Node,
) -> @ast.Instr[@basic.Location] raise ReadError {
let p = split(g)
if p.head.length() == 1 &&
as_id(p.head[0]) is Some(name) &&
p.block is Some(bs) {
if bs.length() != 1 {
fail_at("a labelled argument takes one value", p.span, source=self.src)
}
return self.instr(
Labelled(self.ident(name, node_span(p.head[0])), self.group(bs[0])),
p.span,
)
}
self.group(g)
}