use anyhow::{Result, anyhow, bail};
use syn::spanned::Spanned;
use syn::{BinOp, Block, Expr, Lit, Pat, Stmt, UnOp};
use std::sync::Arc;
use crate::interpreter::bytecode::{BinKind, Const, DISCARD, Op, Reg, UnKind};
use crate::interpreter::numeric::{IntWidth, truncate};
use super::*;
fn flatten_and(cond: &Expr) -> Vec<&Expr> {
fn walk<'a>(e: &'a Expr, out: &mut Vec<&'a Expr>) {
if let Expr::Binary(b) = e
&& matches!(b.op, BinOp::And(_))
{
walk(&b.left, out);
walk(&b.right, out);
} else {
out.push(e);
}
}
let mut out = Vec::new();
walk(cond, &mut out);
out
}
fn from_str_root(e: &Expr) -> Option<&syn::ExprCall> {
from_str_chain(e, false)
}
fn maps_only_the_error(method: &syn::Ident) -> bool {
method == "map_err" || method == "context" || method == "with_context"
}
fn from_str_chain(e: &Expr, unwrapped: bool) -> Option<&syn::ExprCall> {
match e {
Expr::Call(c) => {
let Expr::Path(p) = &*c.func else { return None };
let seg = p.path.segments.last()?;
if seg.ident != "from_str" || first_generic_type(seg).is_some() {
return None;
}
Some(c)
}
Expr::Try(t) => from_str_chain(&t.expr, true),
Expr::Paren(p) => from_str_chain(&p.expr, unwrapped),
Expr::Group(g) => from_str_chain(&g.expr, unwrapped),
Expr::MethodCall(m) if m.method == "unwrap" || m.method == "expect" => {
from_str_chain(&m.receiver, true)
}
Expr::MethodCall(m) if unwrapped && maps_only_the_error(&m.method) => {
from_str_chain(&m.receiver, unwrapped)
}
_ => None,
}
}
fn collect_root(e: &Expr) -> Option<&syn::ExprMethodCall> {
match e {
Expr::MethodCall(m) if m.method == "collect" && m.turbofish.is_none() => Some(m),
Expr::Paren(p) => collect_root(&p.expr),
Expr::Group(g) => collect_root(&g.expr),
_ => None,
}
}
fn is_string_type(ty: &syn::Type) -> bool {
matches!(ty, syn::Type::Path(p)
if p.path.segments.last().is_some_and(|s| s.ident == "String"))
}
pub(super) fn returns_string(output: &syn::ReturnType) -> bool {
matches!(output, syn::ReturnType::Type(_, ty) if is_string_type(ty))
}
pub(super) fn returned_json_type(output: &syn::ReturnType) -> Option<&syn::Type> {
let syn::ReturnType::Type(_, ty) = output else {
return None;
};
Some(result_ok_type(ty).unwrap_or(ty))
}
fn result_ok_type(ty: &syn::Type) -> Option<&syn::Type> {
let syn::Type::Path(p) = ty else { return None };
let seg = p.path.segments.last()?;
if seg.ident != "Result" {
return None;
}
first_generic_type(seg)
}
fn tail_exprs<'a>(e: &'a Expr, out: &mut Vec<&'a Expr>) {
match e {
Expr::Paren(p) => tail_exprs(&p.expr, out),
Expr::Group(g) => tail_exprs(&g.expr, out),
Expr::Block(b) => tail_block_exprs(&b.block, out),
Expr::If(i) => {
tail_block_exprs(&i.then_branch, out);
if let Some((_, alt)) = &i.else_branch {
tail_exprs(alt, out);
}
}
Expr::Match(m) => {
for arm in &m.arms {
tail_exprs(&arm.body, out);
}
}
other => out.push(other),
}
}
fn tail_block_exprs<'a>(block: &'a Block, out: &mut Vec<&'a Expr>) {
if let Some(Stmt::Expr(e, None)) = block.stmts.last() {
tail_exprs(e, out);
}
}
fn returned_exprs(block: &Block) -> Vec<&Expr> {
let mut found = Vec::new();
tail_block_exprs(block, &mut found);
walk_returns(block, &mut found);
found
}
pub(super) fn returned_collects(block: &Block) -> Vec<*const syn::ExprMethodCall> {
returned_exprs(block)
.into_iter()
.filter_map(|e| match e {
Expr::MethodCall(m) if m.method == "collect" && m.turbofish.is_none() => {
Some(std::ptr::from_ref(m))
}
_ => None,
})
.collect()
}
pub(super) fn returned_from_strs(block: &Block) -> Vec<*const syn::ExprCall> {
returned_exprs(block)
.into_iter()
.filter_map(|e| from_str_chain(e, true).map(std::ptr::from_ref))
.collect()
}
fn walk_returns<'a>(block: &'a Block, out: &mut Vec<&'a Expr>) {
for stmt in &block.stmts {
match stmt {
Stmt::Expr(e, _) => walk_returns_expr(e, out),
Stmt::Local(local) => {
if let Some(init) = &local.init
&& let Some(diverge) = &init.diverge
{
walk_returns_expr(&diverge.1, out);
}
}
_ => {}
}
}
}
fn walk_returns_expr<'a>(e: &'a Expr, out: &mut Vec<&'a Expr>) {
match e {
Expr::Return(r) => {
if let Some(value) = &r.expr {
tail_exprs(value, out);
}
}
Expr::Block(b) => walk_returns(&b.block, out),
Expr::Unsafe(u) => walk_returns(&u.block, out),
Expr::If(i) => {
walk_returns(&i.then_branch, out);
if let Some((_, alt)) = &i.else_branch {
walk_returns_expr(alt, out);
}
}
Expr::Match(m) => {
for arm in &m.arms {
walk_returns_expr(&arm.body, out);
}
}
Expr::ForLoop(f) => walk_returns(&f.body, out),
Expr::While(w) => walk_returns(&w.body, out),
Expr::Loop(l) => walk_returns(&l.body, out),
_ => {}
}
}
impl Compiler<'_> {
pub(super) fn compile_block(&mut self, block: &Block, dst: Reg) -> Result<()> {
self.push_scope();
let res = self.compile_block_inner(block, dst);
self.pop_scope();
res
}
pub(super) fn compile_block_inner(&mut self, block: &Block, dst: Reg) -> Result<()> {
if block.stmts.is_empty() {
self.emit(Op::LoadUnit { dst });
return Ok(());
}
for stmt in &block.stmts {
let Stmt::Item(item) = stmt else { continue };
match item {
syn::Item::Const(c) => {
self.set_line(c.span());
let val = self.alloc();
self.compile_into(val, &c.expr)?;
self.define(&c.ident.to_string(), val);
}
syn::Item::Static(s) => {
if matches!(s.mutability, syn::StaticMutability::Mut(_)) {
bail!("unsupported feature: `static mut`");
}
self.set_line(s.span());
let val = self.alloc();
self.compile_into(val, &s.expr)?;
self.define(&s.ident.to_string(), val);
}
_ => {}
}
}
let last = block.stmts.len() - 1;
for (i, stmt) in block.stmts.iter().enumerate() {
let is_last = i == last;
self.set_line(stmt.span());
match stmt {
Stmt::Local(local)
if local
.init
.as_ref()
.and_then(|i| i.diverge.as_ref())
.is_some() =>
{
let init = local.init.as_ref().unwrap();
let else_expr = &init.diverge.as_ref().unwrap().1;
let val = self.alloc();
self.compile_into(val, &init.expr)?;
let matched = self.alloc();
let pidx = self.pattern_info(&local.pat)?;
self.emit(Op::TestBind {
val,
pat: pidx,
dst: matched,
});
let jmp_ok = self.here();
self.emit(Op::JumpIfTrue {
cond: matched,
to: 0,
});
let else_dst = self.alloc();
self.compile_into(else_dst, else_expr)?;
let ok_at = self.here() as u32;
self.patch_jump(jmp_ok, ok_at);
if is_last {
self.emit(Op::LoadUnit { dst });
}
}
Stmt::Local(local) => {
let val = self.alloc();
let mut offered = false;
let outer_string_let = self.string_let.take();
if let Pat::Type(t) = &local.pat
&& let Some(init) = &local.init
{
if let Some(call) = from_str_root(&init.expr) {
self.json_let = Some((call as *const _, self.lower_ir(&t.ty)));
offered = true;
} else if is_string_type(&t.ty)
&& let Some(mc) = collect_root(&init.expr)
{
self.string_let = Some(mc as *const _);
}
if let Expr::MethodCall(mc) = &*init.expr
&& mc.method == "unwrap_or_default"
&& let Some(ty) = ScalarTy::lower(&t.ty)
{
self.default_let = Some((std::ptr::from_ref(mc), ty));
}
}
if let Pat::Type(t) = &local.pat
&& let Pat::Ident(ident) = &*t.pat
&& let Some(declared) = annotation_scalar(&t.ty)
{
self.typed_locals.insert(ident.ident.to_string(), declared);
}
let mut typed_literal = false;
if let Pat::Type(t) = &local.pat
&& let Some(init) = &local.init
&& let Some(target) = numeric_annotation(&t.ty)
{
typed_literal = self.compile_numeric_annotated(val, &init.expr, target)?;
}
if !typed_literal {
match &local.init {
Some(init) => self.compile_into(val, &init.expr)?,
None => self.emit(Op::LoadUnit { dst: val }),
}
}
let consumed = offered && self.json_let.is_none();
self.json_let = None;
self.string_let = outer_string_let;
if let Pat::Type(t) = &local.pat {
if !consumed && !typed_literal {
self.emit_annotation(val, &t.ty);
}
self.bind_pattern_irrefutable(&t.pat, val)?;
} else {
self.bind_pattern_irrefutable(&local.pat, val)?;
}
if is_last {
self.emit(Op::LoadUnit { dst });
}
}
Stmt::Expr(expr, semi) => {
if is_last && semi.is_none() {
self.compile_into(dst, expr)?;
} else {
if let Expr::MethodCall(m) = expr {
self.compile_method(DISCARD, m)?;
} else {
let tmp = self.alloc();
self.compile_into(tmp, expr)?;
}
if is_last {
self.emit(Op::LoadUnit { dst });
}
}
}
Stmt::Item(item) => {
if let syn::Item::Fn(_) = item {
bail!("unsupported feature: nested functions");
}
if is_last {
self.emit(Op::LoadUnit { dst });
}
}
Stmt::Macro(m) => {
let target = if is_last { dst } else { self.alloc() };
self.compile_macro(&m.mac, target)?;
if is_last && !macro_yields_value(&m.mac) {
self.emit(Op::LoadUnit { dst });
}
}
}
}
Ok(())
}
fn emit_annotation(&mut self, reg: Reg, ty: &syn::Type) {
if numeric_annotation(ty).is_some() {
let idx = self.add_cast(ty);
self.emit(Op::Cast {
dst: reg,
src: reg,
ty: idx,
});
return;
}
self.emit_coerce(reg, ty);
}
pub(super) fn emit_coerce(&mut self, reg: Reg, ty: &syn::Type) {
let ir = self.lower_ir(ty);
if !ir.is_active() {
return;
}
let idx = self.add_coerce(ir);
self.emit(Op::Coerce {
dst: reg,
src: reg,
ty: idx,
});
}
pub(super) fn compile_expr(&mut self, expr: &Expr) -> Result<Reg> {
if let Expr::Path(p) = expr
&& p.path.segments.len() == 1
&& p.qself.is_none()
{
let name = p.path.segments[0].ident.to_string();
if let NameLoc::Local(reg) = self.resolve(&name) {
return Ok(reg);
}
}
let dst = self.alloc();
self.compile_into(dst, expr)?;
Ok(dst)
}
pub(super) fn compile_into(&mut self, dst: Reg, expr: &Expr) -> Result<()> {
self.set_line(expr.span());
match expr {
Expr::Lit(lit) => self.compile_lit(dst, &lit.lit)?,
Expr::Paren(p) => self.compile_into(dst, &p.expr)?,
Expr::Group(g) => self.compile_into(dst, &g.expr)?,
Expr::Reference(r) => self.compile_into(dst, &r.expr)?,
Expr::Unsafe(u) => self.compile_block(&u.block, dst)?,
Expr::Block(b) => self.compile_block(&b.block, dst)?,
Expr::Path(p) => self.compile_path(dst, &p.path)?,
Expr::Unary(u) => self.compile_unary(dst, u)?,
Expr::Binary(b) => self.compile_binary(dst, b)?,
Expr::Assign(a) => {
self.compile_assign(&a.left, &a.right)?;
self.emit(Op::LoadUnit { dst });
}
Expr::If(if_expr) => self.compile_if(dst, if_expr)?,
Expr::While(w) => self.compile_while(dst, w)?,
Expr::ForLoop(f) => self.compile_for(dst, f)?,
Expr::Loop(l) => self.compile_loop(dst, l)?,
Expr::Match(m) => self.compile_match(dst, m)?,
Expr::Return(r) => {
let src = match &r.expr {
Some(e) => self.compile_expr(e)?,
None => {
let u = self.alloc();
self.emit(Op::LoadUnit { dst: u });
u
}
};
self.emit(Op::Ret { src });
}
Expr::Break(b) => self.compile_break(b)?,
Expr::Continue(_) => self.compile_continue()?,
Expr::Call(c) => self.compile_call(dst, c)?,
Expr::MethodCall(m) => self.compile_method(dst, m)?,
Expr::Macro(m) => self.compile_macro(&m.mac, dst)?,
Expr::Tuple(t) => {
let base = self.compile_args(t.elems.iter())?;
self.emit(Op::MakeTuple {
dst,
base,
count: t.elems.len() as u16,
});
}
Expr::Array(a) => {
let base = self.compile_args(a.elems.iter())?;
self.emit(Op::MakeVec {
dst,
base,
count: a.elems.len() as u16,
});
}
Expr::Repeat(r) => {
let val = self.compile_expr(&r.expr)?;
let count = self.compile_expr(&r.len)?;
self.emit(Op::MakeArrayRepeat { dst, val, count });
}
Expr::Index(idx) => {
let base = self.compile_expr(&idx.expr)?;
let key = self.compile_expr(&idx.index)?;
self.emit(Op::Index { dst, base, key });
}
Expr::Field(f) => {
let base = self.compile_expr(&f.base)?;
let member = self.member_of(&f.member);
self.emit(Op::GetField { dst, base, member });
}
Expr::Struct(s) => self.compile_struct_literal(dst, s)?,
Expr::Range(r) => self.compile_range(dst, r)?,
Expr::Try(t) => {
let src = self.compile_expr(&t.expr)?;
self.emit(Op::Try { dst, src });
}
Expr::Cast(c) => {
let src = self.compile_expr(&c.expr)?;
let ty = self.add_cast(&c.ty);
self.emit(Op::Cast { dst, src, ty });
}
Expr::Closure(c) => self.compile_closure(dst, c)?,
Expr::Await(a) => {
if !self.ctx.async_mode {
bail!("`.await` is only available under #[tokio::main]");
}
let src = self.compile_expr(&a.base)?;
self.emit(Op::Await { dst, src });
}
Expr::Async(_) => {
bail!("an async block is only supported directly inside tokio::spawn")
}
other => bail!("unsupported expression: {}", expr_kind(other)),
}
Ok(())
}
pub(super) fn compile_lit(&mut self, dst: Reg, lit: &Lit) -> Result<()> {
match lit {
Lit::Int(i) => self.compile_int_lit(dst, i, false, None)?,
Lit::Bool(b) => self.emit(Op::LoadBool { dst, v: b.value }),
Lit::Float(f) => self.compile_float_lit(dst, f, false, None)?,
Lit::Str(s) => {
let k = self.add_const(Const::Str(Arc::from(s.value().as_str())));
self.emit(Op::LoadConst { dst, k });
}
Lit::Char(c) => {
let k = self.add_const(Const::Char(c.value()));
self.emit(Op::LoadConst { dst, k });
}
Lit::Byte(b) => self.emit(Op::LoadInt {
dst,
v: b.value() as i64,
}),
Lit::ByteStr(bs) => {
let k = self.add_const(Const::Bytes(Arc::from(bs.value().as_slice())));
self.emit(Op::LoadConst { dst, k });
}
other => bail!("unsupported literal: {other:?}"),
}
Ok(())
}
fn compile_int_lit(
&mut self,
dst: Reg,
lit: &syn::LitInt,
negated: bool,
annotation: Option<IntWidth>,
) -> Result<()> {
let raw: u128 = lit.base10_parse()?;
if raw > u128::from(u64::MAX) {
bail!("integer literal does not fit any supported width");
}
let mut value = raw as i128;
if negated {
value = -value;
}
let width = match lit.suffix() {
"" | "u128" | "i128" => annotation,
suffix => Some(
IntWidth::parse(suffix)
.ok_or_else(|| anyhow!("unsupported literal suffix `{suffix}`"))?,
),
};
let width = width.unwrap_or({
if value > i128::from(i64::MAX) {
IntWidth::U64
} else {
IntWidth::I64
}
});
match width {
IntWidth::I64 => self.emit(Op::LoadInt {
dst,
v: value as i64,
}),
w => self.emit(Op::LoadIntW {
dst,
v: w.encode(truncate(value, w)),
w,
}),
}
Ok(())
}
fn compile_float_lit(
&mut self,
dst: Reg,
lit: &syn::LitFloat,
negated: bool,
annotation: Option<FloatTy>,
) -> Result<()> {
let is_f32 = match lit.suffix() {
"f32" => true,
"f64" => false,
_ => annotation == Some(FloatTy::F32),
};
let k = if is_f32 {
let mut v: f32 = lit.base10_parse()?;
if negated {
v = -v;
}
self.add_const(Const::F32(v))
} else {
let mut v: f64 = lit.base10_parse()?;
if negated {
v = -v;
}
self.add_const(Const::Float(v))
};
self.emit(Op::LoadConst { dst, k });
Ok(())
}
fn compile_numeric_annotated(
&mut self,
dst: Reg,
expr: &Expr,
target: NumericTy,
) -> Result<bool> {
match expr {
Expr::Paren(p) => self.compile_numeric_annotated(dst, &p.expr, target),
Expr::Group(g) => self.compile_numeric_annotated(dst, &g.expr, target),
Expr::Unary(u) if matches!(u.op, UnOp::Neg(_)) => {
self.compile_numeric_lit(dst, &u.expr, true, target)
}
other => self.compile_numeric_lit(dst, other, false, target),
}
}
fn compile_numeric_lit(
&mut self,
dst: Reg,
expr: &Expr,
negated: bool,
target: NumericTy,
) -> Result<bool> {
let Expr::Lit(l) = expr else {
return Ok(false);
};
match (&l.lit, target) {
(Lit::Int(i), NumericTy::Int(width)) => {
self.compile_int_lit(dst, i, negated, Some(width))?;
Ok(true)
}
(Lit::Float(f), NumericTy::Float(ty)) => {
self.compile_float_lit(dst, f, negated, Some(ty))?;
Ok(true)
}
_ => Ok(false),
}
}
pub(super) fn compile_path(&mut self, dst: Reg, path: &syn::Path) -> Result<()> {
if path.segments.len() == 1 {
let name = path.segments[0].ident.to_string();
return self.load_name(&name, dst);
}
let segs: Vec<String> = path.segments.iter().map(|s| s.ident.to_string()).collect();
self.compile_resolved_value(dst, &segs)
}
pub(super) fn compile_unary(&mut self, dst: Reg, u: &syn::ExprUnary) -> Result<()> {
if matches!(u.op, UnOp::Deref(_)) {
let src = self.compile_expr(&u.expr)?;
self.emit(Op::Deref { dst, src });
return Ok(());
}
if matches!(u.op, UnOp::Neg(_))
&& let Expr::Lit(l) = &*u.expr
{
match &l.lit {
Lit::Int(i) => return self.compile_int_lit(dst, i, true, None),
Lit::Float(f) => return self.compile_float_lit(dst, f, true, None),
_ => {}
}
}
let a = self.compile_expr(&u.expr)?;
let op = match u.op {
UnOp::Neg(_) => UnKind::Neg,
UnOp::Not(_) => UnKind::Not,
_ => bail!("unsupported unary operator"),
};
self.emit(Op::Un { dst, a, op });
Ok(())
}
pub(super) fn compile_binary(&mut self, dst: Reg, b: &syn::ExprBinary) -> Result<()> {
if is_assign_op(&b.op) {
let op = bin_kind(&b.op).ok_or_else(|| anyhow!("unsupported operator {:?}", b.op))?;
self.compile_compound_assign(&b.left, op, &b.right)?;
self.emit(Op::LoadUnit { dst });
return Ok(());
}
match b.op {
BinOp::And(_) => {
self.compile_into(dst, &b.left)?;
let jmp = self.here();
self.emit(Op::JumpIfFalse { cond: dst, to: 0 });
self.compile_into(dst, &b.right)?;
let end = self.here() as u32;
self.patch_jump(jmp, end);
return Ok(());
}
BinOp::Or(_) => {
self.compile_into(dst, &b.left)?;
let jmp = self.here();
self.emit(Op::JumpIfTrue { cond: dst, to: 0 });
self.compile_into(dst, &b.right)?;
let end = self.here() as u32;
self.patch_jump(jmp, end);
return Ok(());
}
_ => {}
}
let op = bin_kind(&b.op).ok_or_else(|| anyhow!("unsupported operator {:?}", b.op))?;
let a = self.compile_expr(&b.left)?;
if let Some(imm) = int_literal(&b.right) {
self.emit(Op::BinImm { dst, a, imm, op });
return Ok(());
}
let c = self.compile_expr(&b.right)?;
self.emit(Op::Bin { dst, a, b: c, op });
Ok(())
}
pub(super) fn emit_cond_jump(&mut self, cond: &Expr) -> Result<usize> {
if let Expr::Binary(b) = cond
&& let Some(op) = bin_kind(&b.op)
&& !is_assign_op(&b.op)
&& matches!(
op,
BinKind::Eq | BinKind::Ne | BinKind::Lt | BinKind::Le | BinKind::Gt | BinKind::Ge
)
{
let a = self.compile_expr(&b.left)?;
if let Some(imm) = int_literal(&b.right) {
let at = self.here();
self.emit(Op::CmpJumpImm { a, imm, op, to: 0 });
return Ok(at);
}
let c = self.compile_expr(&b.right)?;
let at = self.here();
self.emit(Op::CmpJump { a, b: c, op, to: 0 });
return Ok(at);
}
let c = self.compile_expr(cond)?;
let at = self.here();
self.emit(Op::JumpIfFalse { cond: c, to: 0 });
Ok(at)
}
pub(super) fn compile_range(&mut self, dst: Reg, r: &syn::ExprRange) -> Result<()> {
let start = match &r.start {
Some(e) => self.compile_expr(e)?,
None => {
let z = self.alloc();
self.emit(Op::LoadInt { dst: z, v: 0 });
z
}
};
let end = match &r.end {
Some(e) => self.compile_expr(e)?,
None => {
let z = self.alloc();
self.emit(Op::LoadInt {
dst: z,
v: i64::MAX,
});
z
}
};
let inclusive = matches!(r.limits, syn::RangeLimits::Closed(_));
self.emit(Op::MakeRange {
dst,
start,
end,
inclusive,
});
Ok(())
}
pub(super) fn compile_if(&mut self, dst: Reg, if_expr: &syn::ExprIf) -> Result<()> {
let terms = flatten_and(&if_expr.cond);
if terms.iter().any(|t| matches!(t, Expr::Let(_))) {
self.push_scope();
let mut else_jumps = Vec::new();
for term in &terms {
if let Expr::Let(let_expr) = term {
let scrut = self.compile_expr(&let_expr.expr)?;
let matched = self.alloc();
let pat = self.pattern_info(&let_expr.pat)?;
self.emit(Op::TestBind {
val: scrut,
pat,
dst: matched,
});
else_jumps.push(self.here());
self.emit(Op::JumpIfFalse {
cond: matched,
to: 0,
});
} else {
let cond = self.compile_expr(term)?;
else_jumps.push(self.here());
self.emit(Op::JumpIfFalse { cond, to: 0 });
}
}
self.compile_block_inner(&if_expr.then_branch, dst)?;
self.pop_scope();
let jmp_end = self.here();
self.emit(Op::Jump { to: 0 });
let else_at = self.here() as u32;
for j in else_jumps {
self.patch_jump(j, else_at);
}
match &if_expr.else_branch {
Some((_, e)) => self.compile_into(dst, e)?,
None => self.emit(Op::LoadUnit { dst }),
}
let end = self.here() as u32;
self.patch_jump(jmp_end, end);
return Ok(());
}
let jmp_else = self.emit_cond_jump(&if_expr.cond)?;
self.compile_block(&if_expr.then_branch, dst)?;
let jmp_end = self.here();
self.emit(Op::Jump { to: 0 });
let else_at = self.here() as u32;
self.patch_jump(jmp_else, else_at);
match &if_expr.else_branch {
Some((_, e)) => self.compile_into(dst, e)?,
None => self.emit(Op::LoadUnit { dst }),
}
let end = self.here() as u32;
self.patch_jump(jmp_end, end);
Ok(())
}
pub(super) fn compile_while(&mut self, dst: Reg, w: &syn::ExprWhile) -> Result<()> {
let head = self.here();
if let Expr::Let(let_expr) = &*w.cond {
let scrut = self.compile_expr(&let_expr.expr)?;
self.push_scope();
let matched = self.alloc();
let pat = self.pattern_info(&let_expr.pat)?;
self.emit(Op::TestBind {
val: scrut,
pat,
dst: matched,
});
let exit = self.here();
self.emit(Op::JumpIfFalse {
cond: matched,
to: 0,
});
self.loops.push(LoopCtx {
breaks: vec![exit],
continue_to: head,
result: dst,
});
let body = self.alloc();
self.compile_block_inner(&w.body, body)?;
self.pop_scope();
self.emit(Op::Jump { to: head as u32 });
let end = self.here() as u32;
let lc = self.loops.pop().unwrap();
for b in lc.breaks {
self.patch_jump(b, end);
}
self.emit(Op::LoadUnit { dst });
return Ok(());
}
let exit = self.emit_cond_jump(&w.cond)?;
self.loops.push(LoopCtx {
breaks: vec![exit],
continue_to: head,
result: dst,
});
let body = self.alloc();
self.compile_block(&w.body, body)?;
self.emit(Op::Jump { to: head as u32 });
let end = self.here() as u32;
let lc = self.loops.pop().unwrap();
for b in lc.breaks {
self.patch_jump(b, end);
}
self.emit(Op::LoadUnit { dst });
Ok(())
}
pub(super) fn compile_loop(&mut self, dst: Reg, l: &syn::ExprLoop) -> Result<()> {
self.emit(Op::LoadUnit { dst });
let head = self.here();
self.loops.push(LoopCtx {
breaks: Vec::new(),
continue_to: head,
result: dst,
});
let body = self.alloc();
self.compile_block(&l.body, body)?;
self.emit(Op::Jump { to: head as u32 });
let end = self.here() as u32;
let lc = self.loops.pop().unwrap();
for b in lc.breaks {
self.patch_jump(b, end);
}
Ok(())
}
pub(super) fn compile_for(&mut self, dst: Reg, f: &syn::ExprForLoop) -> Result<()> {
let src = self.compile_expr(&f.expr)?;
let iter = self.alloc();
self.emit(Op::IterInit { dst: iter, src });
let idx = self.alloc();
self.emit(Op::LoadInt { dst: idx, v: 0 });
let val = self.alloc();
let head = self.here();
let next = self.here();
self.emit(Op::ForNext {
iter,
idx,
val,
to: 0,
});
self.push_scope();
self.bind_pattern_irrefutable(&f.pat, val)?;
self.loops.push(LoopCtx {
breaks: vec![next],
continue_to: head,
result: dst,
});
let body = self.alloc();
self.compile_block_inner(&f.body, body)?;
self.pop_scope();
self.emit(Op::Jump { to: head as u32 });
let end = self.here() as u32;
let lc = self.loops.pop().unwrap();
for b in lc.breaks {
self.patch_jump(b, end);
}
self.emit(Op::LoadUnit { dst });
Ok(())
}
pub(super) fn compile_break(&mut self, b: &syn::ExprBreak) -> Result<()> {
let result = self.loops.last().map(|l| l.result);
if let Some(result) = result {
if let Some(e) = &b.expr {
self.compile_into(result, e)?;
}
} else {
bail!("break outside a loop");
}
let jmp = self.here();
self.emit(Op::Jump { to: 0 });
self.loops.last_mut().unwrap().breaks.push(jmp);
Ok(())
}
pub(super) fn compile_continue(&mut self) -> Result<()> {
let to = self
.loops
.last()
.map(|l| l.continue_to)
.ok_or_else(|| anyhow!("continue outside a loop"))?;
self.emit(Op::Jump { to: to as u32 });
Ok(())
}
pub(super) fn compile_match(&mut self, dst: Reg, m: &syn::ExprMatch) -> Result<()> {
let scrut = self.compile_expr(&m.expr)?;
let mut end_jumps = Vec::new();
for arm in &m.arms {
self.push_scope();
let matched = self.alloc();
let pat = self.pattern_info(&arm.pat)?;
self.emit(Op::TestBind {
val: scrut,
pat,
dst: matched,
});
let skip = self.here();
self.emit(Op::JumpIfFalse {
cond: matched,
to: 0,
});
let mut guard_skip = None;
if let Some((_, guard)) = &arm.guard {
let g = self.compile_expr(guard)?;
let gs = self.here();
self.emit(Op::JumpIfFalse { cond: g, to: 0 });
guard_skip = Some(gs);
}
self.compile_into(dst, &arm.body)?;
let je = self.here();
self.emit(Op::Jump { to: 0 });
end_jumps.push(je);
self.pop_scope();
let next = self.here() as u32;
self.patch_jump(skip, next);
if let Some(gs) = guard_skip {
self.patch_jump(gs, next);
}
}
let p = self.add_path(vec!["::unreachable_match".to_string()], None);
self.emit(Op::CallPath {
dst,
path: p,
base: dst,
argc: 0,
});
let end = self.here() as u32;
for j in end_jumps {
self.patch_jump(j, end);
}
Ok(())
}
}
pub(super) fn annotation_scalar(ty: &syn::Type) -> Option<ScalarTy> {
let syn::Type::Path(path) = ty else {
return None;
};
let segment = path.path.segments.last()?;
let container = segment.ident.to_string();
if !matches!(container.as_str(), "Option" | "Result" | "Vec" | "VecDeque") {
return None;
}
let syn::PathArguments::AngleBracketed(args) = &segment.arguments else {
return None;
};
let inner = args.args.iter().find_map(|arg| match arg {
syn::GenericArgument::Type(inner) => ScalarTy::lower(inner),
_ => None,
})?;
Some(match container.as_str() {
"Option" | "Result" => ScalarTy::Opt(Box::new(inner)),
_ => ScalarTy::List(Box::new(inner)),
})
}