use crate::ast::*;
use crate::host::{binop as bop, ops, unop, FuncDef, ParamSlot, TryDef};
use fusevm::{Chunk, ChunkBuilder, Op, Value};
#[derive(Default)]
pub struct Program {
pub main: Chunk,
pub functions: Vec<(String, FuncDef)>,
pub tries: Vec<TryDef>,
}
pub fn rebase_program(prog: &mut Program, func_off: usize, try_off: usize) {
if func_off == 0 && try_off == 0 {
return;
}
rebase_chunk(&mut prog.main, func_off, try_off);
for (_, f) in &mut prog.functions {
rebase_chunk(&mut f.chunk, func_off, try_off);
}
for t in &mut prog.tries {
rebase_chunk(&mut t.block, func_off, try_off);
if let Some((_, hb)) = &mut t.handler {
rebase_chunk(hb, func_off, try_off);
}
if let Some(f) = &mut t.finalizer {
rebase_chunk(f, func_off, try_off);
}
}
}
fn rebase_chunk(chunk: &mut Chunk, func_off: usize, try_off: usize) {
for i in 1..chunk.ops.len() {
let off = match chunk.ops[i] {
Op::CallBuiltin(id, _) if id == ops::MKFUNC => func_off,
Op::CallBuiltin(id, 1) if id == ops::TRY => try_off,
_ => continue,
};
if off == 0 {
continue;
}
if let Op::LoadInt(v) = &mut chunk.ops[i - 1] {
*v += off as i64;
}
}
for sub in &mut chunk.sub_chunks {
rebase_chunk(sub, func_off, try_off);
}
}
struct LoopCtx {
breaks: Vec<usize>,
continues: Vec<usize>,
catches_continue: bool,
}
#[derive(Default)]
pub struct Compiler {
functions: Vec<(String, FuncDef)>,
tries: Vec<TryDef>,
loops: Vec<LoopCtx>,
tmp: usize,
}
pub fn compile(stmts: &[Stmt]) -> Result<Program, String> {
let mut c = Compiler::default();
let mut b = ChunkBuilder::new();
c.hoist_funcs(&mut b, stmts)?;
c.compile_stmts(&mut b, stmts)?;
Ok(Program {
main: b.build(),
functions: c.functions,
tries: c.tries,
})
}
fn argc(n: usize) -> Result<u8, String> {
u8::try_from(n).map_err(|_| "too many arguments (>255) for one call".to_string())
}
impl Compiler {
fn name_const(&self, b: &mut ChunkBuilder, s: &str) {
let k = b.add_constant(Value::str(s));
b.emit(Op::LoadConst(k), 0);
}
fn strlit(&self, b: &mut ChunkBuilder, s: &str) {
let k = b.add_constant(Value::str(s));
b.emit(Op::LoadConst(k), 0);
b.emit(Op::CallBuiltin(ops::MKSTR, 1), 0);
}
fn tmp_name(&mut self, tag: &str) -> String {
let n = format!(".{tag}{}", self.tmp);
self.tmp += 1;
n
}
fn emit_mkfunc(&self, b: &mut ChunkBuilder, def_id: usize) {
b.emit(Op::LoadInt(def_id as i64), 0);
b.emit(Op::CallBuiltin(ops::MKFUNC, 1), 0);
}
fn hoist_funcs(&mut self, b: &mut ChunkBuilder, stmts: &[Stmt]) -> Result<(), String> {
for s in stmts {
if let StmtKind::FuncDecl { name, params, body } = &s.kind {
let def_id = self.build_function(name, params, body)?;
self.emit_mkfunc(b, def_id);
self.declare(b, &Expr::Ident(name.clone()));
}
}
Ok(())
}
fn compile_stmts(&mut self, b: &mut ChunkBuilder, stmts: &[Stmt]) -> Result<(), String> {
for s in stmts {
self.compile_stmt(b, s)?;
}
Ok(())
}
fn compile_stmt(&mut self, b: &mut ChunkBuilder, s: &Stmt) -> Result<(), String> {
let line = s.line;
match &s.kind {
StmtKind::Expr(e) => {
self.compile_expr(b, e)?;
b.emit(Op::Pop, line);
}
StmtKind::Empty => {}
StmtKind::FuncDecl { .. } => {} StmtKind::Decl { decls, .. } => {
for d in decls {
match &d.init {
Some(v) => self.compile_expr(b, v)?,
None => {
b.emit(Op::LoadUndef, line);
}
}
self.compile_bind(b, &d.target, true)?;
}
}
StmtKind::Block(body) => {
self.hoist_funcs(b, body)?;
self.compile_stmts(b, body)?;
}
StmtKind::If { test, cons, alt } => self.compile_if(b, test, cons, alt)?,
StmtKind::While { test, body } => self.compile_while(b, test, body)?,
StmtKind::DoWhile { body, test } => self.compile_do_while(b, body, test)?,
StmtKind::For {
init,
test,
update,
body,
} => self.compile_for(b, init, test, update, body)?,
StmtKind::ForOf {
decl_kind,
target,
iter,
body,
} => self.compile_for_of(b, decl_kind.is_some(), target, iter, body)?,
StmtKind::ForIn {
decl_kind,
target,
object,
body,
} => self.compile_for_in(b, decl_kind.is_some(), target, object, body)?,
StmtKind::Switch { disc, cases } => self.compile_switch(b, disc, cases)?,
StmtKind::Return(e) => {
match e {
Some(e) => self.compile_expr(b, e)?,
None => {
b.emit(Op::LoadUndef, line);
}
}
b.emit(Op::CallBuiltin(ops::SIG_RETURN, 1), line);
}
StmtKind::Break(_) => {
let j = b.emit(Op::Jump(0), line);
self.loops
.last_mut()
.ok_or("SyntaxError: 'break' outside loop")?
.breaks
.push(j);
}
StmtKind::Continue(_) => {
let j = b.emit(Op::Jump(0), line);
self.loops
.iter_mut()
.rev()
.find(|c| c.catches_continue)
.ok_or("SyntaxError: 'continue' outside loop")?
.continues
.push(j);
}
StmtKind::Throw(e) => {
self.compile_expr(b, e)?;
b.emit(Op::CallBuiltin(ops::THROW, 1), line);
}
StmtKind::Try {
block,
handler,
finalizer,
} => self.compile_try(b, block, handler, finalizer)?,
}
Ok(())
}
fn compile_bind(&mut self, b: &mut ChunkBuilder, target: &Expr, declare: bool) -> Result<(), String> {
match target {
Expr::Ident(_) => {
if declare {
self.declare(b, target);
} else {
self.store_simple(b, target)?;
}
}
Expr::Member { .. } | Expr::Index { .. } => {
self.store_simple(b, target)?;
}
Expr::Array(items) => self.destructure_array(b, items, declare)?,
Expr::Object(props) => self.destructure_object(b, props, declare)?,
Expr::Assign { target, value } => {
b.emit(Op::Dup, 0);
b.emit(Op::LoadUndef, 0);
b.emit(Op::CallBuiltin(ops::STRICT_EQ, 2), 0);
let jf = b.emit(Op::JumpIfFalse(0), 0);
b.emit(Op::Pop, 0); self.compile_expr(b, value)?;
let end = b.current_pos();
b.patch_jump(jf, end);
self.compile_bind(b, target, declare)?;
}
_ => return Err("SyntaxError: invalid assignment target".into()),
}
Ok(())
}
fn declare(&self, b: &mut ChunkBuilder, target: &Expr) {
if let Expr::Ident(n) = target {
self.name_const(b, n);
b.emit(Op::Swap, 0);
b.emit(Op::CallBuiltin(ops::DECLARE, 2), 0);
b.emit(Op::Pop, 0);
}
}
fn store_simple(&mut self, b: &mut ChunkBuilder, target: &Expr) -> Result<(), String> {
match target {
Expr::Ident(n) => {
self.name_const(b, n);
b.emit(Op::Swap, 0);
b.emit(Op::CallBuiltin(ops::SETLOCAL, 2), 0);
b.emit(Op::Pop, 0);
}
Expr::Member { object, property, .. } => {
self.compile_expr(b, object)?; self.name_const(b, property); b.emit(Op::Rot, 0); b.emit(Op::CallBuiltin(ops::SETATTR, 3), 0);
b.emit(Op::Pop, 0);
}
Expr::Index { object, index, .. } => {
self.compile_expr(b, object)?; self.compile_expr(b, index)?; b.emit(Op::Rot, 0); b.emit(Op::CallBuiltin(ops::SETITEM, 3), 0);
b.emit(Op::Pop, 0);
}
_ => return Err("SyntaxError: invalid assignment target".into()),
}
Ok(())
}
fn destructure_array(&mut self, b: &mut ChunkBuilder, items: &[Expr], declare: bool) -> Result<(), String> {
let star_idx = items
.iter()
.position(|e| matches!(e, Expr::Spread(_)))
.map(|i| i as i64)
.unwrap_or(-1);
b.emit(Op::LoadInt(items.len() as i64), 0);
b.emit(Op::LoadInt(star_idx), 0);
b.emit(Op::CallBuiltin(ops::UNPACK, 3), 0); for it in items {
match it {
Expr::Undefined => {
b.emit(Op::Pop, 0); }
Expr::Spread(inner) => self.compile_bind(b, inner, declare)?,
_ => self.compile_bind(b, it, declare)?,
}
}
Ok(())
}
fn destructure_object(&mut self, b: &mut ChunkBuilder, props: &[Prop], declare: bool) -> Result<(), String> {
let obj_tmp = self.tmp_name("destr");
self.name_const(b, &obj_tmp);
b.emit(Op::Swap, 0);
b.emit(Op::CallBuiltin(ops::DECLARE, 2), 0);
b.emit(Op::Pop, 0);
let mut named: Vec<String> = Vec::new();
for p in props {
match p {
Prop::KeyValue { key, value, .. } => {
if let Expr::Str(s) = key {
named.push(s.clone());
}
self.load_local(b, &obj_tmp);
self.compile_expr(b, key)?;
b.emit(Op::CallBuiltin(ops::GETITEM, 2), 0); self.compile_bind(b, value, declare)?;
}
Prop::Spread(target) => {
self.load_local(b, &obj_tmp);
for k in &named {
self.strlit(b, k);
}
b.emit(Op::CallBuiltin(ops::MKARR, argc(named.len())?), 0);
b.emit(Op::CallBuiltin(ops::OBJ_REST, 2), 0); self.compile_bind(b, target, declare)?;
}
}
}
Ok(())
}
fn load_local(&self, b: &mut ChunkBuilder, name: &str) {
self.name_const(b, name);
b.emit(Op::CallBuiltin(ops::GETLOCAL, 1), 0);
}
fn compile_condition(&mut self, b: &mut ChunkBuilder, e: &Expr) -> Result<(), String> {
self.compile_expr(b, e)?;
b.emit(Op::CallBuiltin(ops::TRUTHY, 1), 0);
Ok(())
}
fn compile_if(&mut self, b: &mut ChunkBuilder, test: &Expr, cons: &Stmt, alt: &Option<Box<Stmt>>) -> Result<(), String> {
self.compile_condition(b, test)?;
let jfalse = b.emit(Op::JumpIfFalse(0), 0);
self.compile_stmt(b, cons)?;
if let Some(alt) = alt {
let jend = b.emit(Op::Jump(0), 0);
let else_start = b.current_pos();
b.patch_jump(jfalse, else_start);
self.compile_stmt(b, alt)?;
let end = b.current_pos();
b.patch_jump(jend, end);
} else {
let end = b.current_pos();
b.patch_jump(jfalse, end);
}
Ok(())
}
fn compile_while(&mut self, b: &mut ChunkBuilder, test: &Expr, body: &Stmt) -> Result<(), String> {
let start = b.current_pos();
self.compile_condition(b, test)?;
let jfalse = b.emit(Op::JumpIfFalse(0), 0);
self.loops.push(LoopCtx {
breaks: Vec::new(),
continues: Vec::new(),
catches_continue: true,
});
self.compile_stmt(b, body)?;
b.emit(Op::Jump(start), 0);
let ctx = self.loops.pop().unwrap();
for c in ctx.continues {
b.patch_jump(c, start);
}
let end = b.current_pos();
b.patch_jump(jfalse, end);
for br in ctx.breaks {
b.patch_jump(br, end);
}
Ok(())
}
fn compile_do_while(&mut self, b: &mut ChunkBuilder, body: &Stmt, test: &Expr) -> Result<(), String> {
let start = b.current_pos();
self.loops.push(LoopCtx {
breaks: Vec::new(),
continues: Vec::new(),
catches_continue: true,
});
self.compile_stmt(b, body)?;
let cont_target = b.current_pos();
self.compile_condition(b, test)?;
b.emit(Op::JumpIfTrue(start), 0);
let ctx = self.loops.pop().unwrap();
for c in ctx.continues {
b.patch_jump(c, cont_target);
}
let end = b.current_pos();
for br in ctx.breaks {
b.patch_jump(br, end);
}
Ok(())
}
fn compile_for(
&mut self,
b: &mut ChunkBuilder,
init: &Option<Box<Stmt>>,
test: &Option<Expr>,
update: &Option<Expr>,
body: &Stmt,
) -> Result<(), String> {
if let Some(init) = init {
self.compile_stmt(b, init)?;
}
let start = b.current_pos();
let jfalse = match test {
Some(t) => {
self.compile_condition(b, t)?;
Some(b.emit(Op::JumpIfFalse(0), 0))
}
None => None,
};
self.loops.push(LoopCtx {
breaks: Vec::new(),
continues: Vec::new(),
catches_continue: true,
});
self.compile_stmt(b, body)?;
let cont_target = b.current_pos();
if let Some(u) = update {
self.compile_expr(b, u)?;
b.emit(Op::Pop, 0);
}
b.emit(Op::Jump(start), 0);
let ctx = self.loops.pop().unwrap();
for c in ctx.continues {
b.patch_jump(c, cont_target);
}
let end = b.current_pos();
if let Some(jf) = jfalse {
b.patch_jump(jf, end);
}
for br in ctx.breaks {
b.patch_jump(br, end);
}
Ok(())
}
fn compile_for_of(&mut self, b: &mut ChunkBuilder, declare: bool, target: &Expr, iter: &Expr, body: &Stmt) -> Result<(), String> {
self.compile_expr(b, iter)?;
b.emit(Op::CallBuiltin(ops::GETITER, 1), 0); self.loop_over(b, declare, target, body)
}
fn compile_for_in(&mut self, b: &mut ChunkBuilder, declare: bool, target: &Expr, object: &Expr, body: &Stmt) -> Result<(), String> {
self.compile_expr(b, object)?;
b.emit(Op::CallBuiltin(ops::FORIN_KEYS, 1), 0); b.emit(Op::CallBuiltin(ops::GETITER, 1), 0); self.loop_over(b, declare, target, body)
}
fn loop_over(&mut self, b: &mut ChunkBuilder, declare: bool, target: &Expr, body: &Stmt) -> Result<(), String> {
let start = b.current_pos();
b.emit(Op::CallBuiltin(ops::FORITER, 0), 0); let jdone = b.emit(Op::JumpIfFalse(0), 0); self.compile_bind(b, target, declare)?; self.loops.push(LoopCtx {
breaks: Vec::new(),
continues: Vec::new(),
catches_continue: true,
});
self.compile_stmt(b, body)?;
b.emit(Op::Jump(start), 0);
let ctx = self.loops.pop().unwrap();
for c in ctx.continues {
b.patch_jump(c, start);
}
let done = b.current_pos();
b.patch_jump(jdone, done);
b.emit(Op::Pop, 0); let jafter = b.emit(Op::Jump(0), 0);
let break_target = b.current_pos();
b.emit(Op::Pop, 0); let end = b.current_pos();
b.patch_jump(jafter, end);
for br in ctx.breaks {
b.patch_jump(br, break_target);
}
Ok(())
}
fn compile_switch(&mut self, b: &mut ChunkBuilder, disc: &Expr, cases: &[SwitchCase]) -> Result<(), String> {
let disc_tmp = self.tmp_name("switch");
self.compile_expr(b, disc)?;
self.name_const(b, &disc_tmp);
b.emit(Op::Swap, 0);
b.emit(Op::CallBuiltin(ops::DECLARE, 2), 0);
b.emit(Op::Pop, 0);
let mut body_jumps: Vec<Option<usize>> = Vec::new();
let mut default_idx: Option<usize> = None;
for (i, case) in cases.iter().enumerate() {
match &case.test {
Some(t) => {
self.load_local(b, &disc_tmp);
self.compile_expr(b, t)?;
b.emit(Op::CallBuiltin(ops::STRICT_EQ, 2), 0);
let j = b.emit(Op::JumpIfTrue(0), 0);
body_jumps.push(Some(j));
}
None => {
default_idx = Some(i);
body_jumps.push(None);
}
}
}
let no_match_jump = b.emit(Op::Jump(0), 0);
self.loops.push(LoopCtx {
breaks: Vec::new(),
continues: Vec::new(),
catches_continue: false,
});
let mut body_starts: Vec<usize> = Vec::new();
for case in cases {
body_starts.push(b.current_pos());
self.compile_stmts(b, &case.body)?;
}
let end = b.current_pos();
for (i, j) in body_jumps.iter().enumerate() {
if let Some(j) = j {
b.patch_jump(*j, body_starts[i]);
}
}
match default_idx {
Some(i) => b.patch_jump(no_match_jump, body_starts[i]),
None => b.patch_jump(no_match_jump, end),
}
let ctx = self.loops.pop().unwrap();
for br in ctx.breaks {
b.patch_jump(br, end);
}
Ok(())
}
fn compile_try(
&mut self,
b: &mut ChunkBuilder,
block: &[Stmt],
handler: &Option<(Option<Expr>, Vec<Stmt>)>,
finalizer: &Option<Vec<Stmt>>,
) -> Result<(), String> {
let block_chunk = self.compile_block_chunk(block)?;
let handler_def = match handler {
Some((param, body)) => {
let param_name = match param {
Some(Expr::Ident(n)) => Some(n.clone()),
_ => None,
};
let hbody = self.compile_block_chunk(body)?;
Some((param_name, hbody))
}
None => None,
};
let final_chunk = match finalizer {
Some(f) => Some(self.compile_block_chunk(f)?),
None => None,
};
let id = self.tries.len();
self.tries.push(TryDef {
block: block_chunk,
handler: handler_def,
finalizer: final_chunk,
});
b.emit(Op::LoadInt(id as i64), 0);
b.emit(Op::CallBuiltin(ops::TRY, 1), 0);
b.emit(Op::Pop, 0);
Ok(())
}
fn compile_block_chunk(&mut self, stmts: &[Stmt]) -> Result<Chunk, String> {
let mut cb = ChunkBuilder::new();
self.hoist_funcs(&mut cb, stmts)?;
self.compile_stmts(&mut cb, stmts)?;
Ok(cb.build())
}
fn build_function(&mut self, name: &str, params: &[Param], body: &[Stmt]) -> Result<usize, String> {
let (slots, prologue) = self.lower_params(params)?;
let mut fb = ChunkBuilder::new();
self.hoist_funcs(&mut fb, &prologue)?;
self.hoist_funcs(&mut fb, body)?;
self.compile_stmts(&mut fb, &prologue)?;
self.compile_stmts(&mut fb, body)?;
let def = FuncDef {
name: name.to_string(),
params: slots,
chunk: fb.build(),
is_arrow: false,
};
self.functions.push((name.to_string(), def));
Ok(self.functions.len() - 1)
}
fn build_arrow(&mut self, params: &[Param], body: &FnBody) -> Result<usize, String> {
let stmts = match body {
FnBody::Block(b) => b.clone(),
FnBody::Expr(e) => vec![Stmt::from(StmtKind::Return(Some((**e).clone())))],
};
let id = self.build_function("", params, &stmts)?;
self.functions[id].1.is_arrow = true;
Ok(id)
}
fn lower_params(&mut self, params: &[Param]) -> Result<(Vec<ParamSlot>, Vec<Stmt>), String> {
let mut slots = Vec::new();
let mut prologue: Vec<Stmt> = Vec::new();
for (i, p) in params.iter().enumerate() {
if p.rest {
let name = match &p.pattern {
Expr::Ident(n) => n.clone(),
_ => return Err("SyntaxError: rest parameter must be an identifier".into()),
};
slots.push(ParamSlot {
name,
rest: true,
has_default: false,
});
continue;
}
match &p.pattern {
Expr::Ident(name) => {
slots.push(ParamSlot {
name: name.clone(),
rest: false,
has_default: p.default.is_some(),
});
if let Some(d) = &p.default {
prologue.push(default_stmt(name, d));
}
}
pattern => {
let synth = format!(".param{i}");
slots.push(ParamSlot {
name: synth.clone(),
rest: false,
has_default: p.default.is_some(),
});
if let Some(d) = &p.default {
prologue.push(default_stmt(&synth, d));
}
prologue.push(Stmt::from(StmtKind::Decl {
kind: DeclKind::Let,
decls: vec![Declarator {
target: pattern.clone(),
init: Some(Expr::Ident(synth)),
}],
}));
}
}
}
Ok((slots, prologue))
}
fn compile_expr(&mut self, b: &mut ChunkBuilder, e: &Expr) -> Result<(), String> {
match e {
Expr::Undefined => {
b.emit(Op::LoadUndef, 0);
}
Expr::Null => {
b.emit(Op::CallBuiltin(ops::LOAD_NULL, 0), 0);
}
Expr::True => {
b.emit(Op::LoadTrue, 0);
}
Expr::False => {
b.emit(Op::LoadFalse, 0);
}
Expr::Number(n) => {
b.emit(Op::LoadFloat(*n), 0);
}
Expr::Str(s) => self.strlit(b, s),
Expr::Template { quasis, exprs } => self.compile_template(b, quasis, exprs)?,
Expr::Ident(n) => self.load_local(b, n),
Expr::This => {
b.emit(Op::CallBuiltin(ops::THIS, 0), 0);
}
Expr::Array(items) => self.compile_array(b, items)?,
Expr::Object(props) => self.compile_object(b, props)?,
Expr::Spread(inner) => self.compile_expr(b, inner)?,
Expr::Logical(op, l, r) => self.compile_logical(b, *op, l, r)?,
Expr::Unary(op, e) => self.compile_unary(b, *op, e)?,
Expr::Binary(op, l, r) => self.compile_binary(b, *op, l, r)?,
Expr::Conditional { test, cons, alt } => {
self.compile_condition(b, test)?;
let jf = b.emit(Op::JumpIfFalse(0), 0);
self.compile_expr(b, cons)?;
let je = b.emit(Op::Jump(0), 0);
let els = b.current_pos();
b.patch_jump(jf, els);
self.compile_expr(b, alt)?;
let end = b.current_pos();
b.patch_jump(je, end);
}
Expr::Assign { target, value } => {
self.compile_expr(b, value)?;
b.emit(Op::Dup, 0); self.compile_bind(b, target, false)?;
}
Expr::Update { op, prefix, target } => self.compile_update(b, *op, *prefix, target)?,
Expr::Call { func, args, optional } => self.compile_call(b, func, args, *optional)?,
Expr::New { callee, args } => self.compile_new(b, callee, args)?,
Expr::Member { object, property, optional } => {
self.compile_member(b, object, property, *optional)?
}
Expr::Index { object, index, optional } => {
self.compile_index(b, object, index, *optional)?
}
Expr::Function { params, body, is_arrow, name } => {
let def_id = if *is_arrow {
self.build_arrow(params, body)?
} else {
let n = name.clone().unwrap_or_default();
let stmts = match body {
FnBody::Block(b) => b.clone(),
FnBody::Expr(e) => vec![Stmt::from(StmtKind::Return(Some((**e).clone())))],
};
self.build_function(&n, params, &stmts)?
};
self.emit_mkfunc(b, def_id);
}
Expr::Sequence(items) => {
for (i, it) in items.iter().enumerate() {
self.compile_expr(b, it)?;
if i + 1 < items.len() {
b.emit(Op::Pop, 0);
}
}
}
}
Ok(())
}
fn compile_template(&mut self, b: &mut ChunkBuilder, quasis: &[String], exprs: &[Expr]) -> Result<(), String> {
let mut n = 0;
for (i, q) in quasis.iter().enumerate() {
let k = b.add_constant(Value::str(q));
b.emit(Op::LoadConst(k), 0);
n += 1;
if i < exprs.len() {
self.compile_expr(b, &exprs[i])?;
b.emit(Op::CallBuiltin(ops::TOSTR, 1), 0);
n += 1;
}
}
b.emit(Op::CallBuiltin(ops::MKSTR, argc(n)?), 0);
Ok(())
}
fn compile_array(&mut self, b: &mut ChunkBuilder, items: &[Expr]) -> Result<(), String> {
if items.iter().any(|e| matches!(e, Expr::Spread(_))) {
for it in items {
match it {
Expr::Spread(inner) => {
b.emit(Op::LoadInt(1), 0);
self.compile_expr(b, inner)?;
}
_ => {
b.emit(Op::LoadInt(0), 0);
self.compile_expr(b, it)?;
}
}
}
b.emit(Op::CallBuiltin(ops::BUILD_ARGS, argc(items.len() * 2)?), 0);
} else {
for it in items {
self.compile_expr(b, it)?;
}
b.emit(Op::CallBuiltin(ops::MKARR, argc(items.len())?), 0);
}
Ok(())
}
fn compile_object(&mut self, b: &mut ChunkBuilder, props: &[Prop]) -> Result<(), String> {
for p in props {
match p {
Prop::KeyValue { key, value, .. } => {
b.emit(Op::LoadInt(0), 0);
self.compile_expr(b, key)?;
b.emit(Op::CallBuiltin(ops::TOSTR, 1), 0);
self.compile_expr(b, value)?;
}
Prop::Spread(src) => {
b.emit(Op::LoadInt(1), 0);
self.compile_expr(b, src)?;
b.emit(Op::LoadUndef, 0);
}
}
}
b.emit(Op::CallBuiltin(ops::MKOBJ, argc(props.len() * 3)?), 0);
Ok(())
}
fn compile_logical(&mut self, b: &mut ChunkBuilder, op: LogicalOp, l: &Expr, r: &Expr) -> Result<(), String> {
self.compile_expr(b, l)?;
b.emit(Op::Dup, 0);
let test_op = match op {
LogicalOp::And | LogicalOp::Or => ops::TRUTHY,
LogicalOp::Nullish => ops::NULLISH,
};
b.emit(Op::CallBuiltin(test_op, 1), 0);
let jump = match op {
LogicalOp::And => b.emit(Op::JumpIfFalse(0), 0), LogicalOp::Or => b.emit(Op::JumpIfTrue(0), 0), LogicalOp::Nullish => b.emit(Op::JumpIfFalse(0), 0), };
b.emit(Op::Pop, 0); self.compile_expr(b, r)?;
let end = b.current_pos();
b.patch_jump(jump, end);
Ok(())
}
fn compile_unary(&mut self, b: &mut ChunkBuilder, op: UnOp, e: &Expr) -> Result<(), String> {
match op {
UnOp::Neg => {
self.compile_expr(b, e)?;
b.emit(Op::Negate, 0);
}
UnOp::Not => {
self.compile_condition(b, e)?;
b.emit(Op::LogNot, 0);
}
UnOp::Pos => {
b.emit(Op::LoadInt(unop::POS), 0);
self.compile_expr(b, e)?;
b.emit(Op::CallBuiltin(ops::UNARY, 2), 0);
}
UnOp::BitNot => {
b.emit(Op::LoadInt(unop::BITNOT), 0);
self.compile_expr(b, e)?;
b.emit(Op::CallBuiltin(ops::UNARY, 2), 0);
}
UnOp::TypeOf => {
self.compile_expr(b, e)?;
b.emit(Op::CallBuiltin(ops::TYPEOF, 1), 0);
}
UnOp::Void => {
self.compile_expr(b, e)?;
b.emit(Op::Pop, 0);
b.emit(Op::LoadUndef, 0);
}
UnOp::Delete => match e {
Expr::Member { object, property, .. } => {
self.compile_expr(b, object)?;
self.name_const(b, property);
b.emit(Op::CallBuiltin(ops::DELPROP_NAME, 2), 0);
}
Expr::Index { object, index, .. } => {
self.compile_expr(b, object)?;
self.compile_expr(b, index)?;
b.emit(Op::CallBuiltin(ops::DELITEM, 2), 0);
}
_ => {
b.emit(Op::LoadTrue, 0);
}
},
}
Ok(())
}
fn compile_binary(&mut self, b: &mut ChunkBuilder, op: BinOp, l: &Expr, r: &Expr) -> Result<(), String> {
macro_rules! native {
($opc:expr) => {{
self.compile_expr(b, l)?;
self.compile_expr(b, r)?;
b.emit($opc, 0);
return Ok(());
}};
}
match op {
BinOp::Add => native!(Op::Add),
BinOp::Sub => native!(Op::Sub),
BinOp::Mul => native!(Op::Mul),
BinOp::Div => {
self.compile_expr(b, l)?;
self.compile_expr(b, r)?;
b.emit(Op::CallBuiltin(ops::DIV, 2), 0);
return Ok(());
}
BinOp::Mod => native!(Op::Mod),
BinOp::Pow => native!(Op::Pow),
BinOp::Lt => native!(Op::NumLt),
BinOp::Le => native!(Op::NumLe),
BinOp::Gt => native!(Op::NumGt),
BinOp::Ge => native!(Op::NumGe),
BinOp::EqEqEq => {
self.compile_expr(b, l)?;
self.compile_expr(b, r)?;
b.emit(Op::CallBuiltin(ops::STRICT_EQ, 2), 0);
}
BinOp::NeEqEq => {
self.compile_expr(b, l)?;
self.compile_expr(b, r)?;
b.emit(Op::CallBuiltin(ops::STRICT_EQ, 2), 0);
b.emit(Op::LogNot, 0);
}
BinOp::EqEq => {
self.compile_expr(b, l)?;
self.compile_expr(b, r)?;
b.emit(Op::CallBuiltin(ops::LOOSE_EQ, 2), 0);
}
BinOp::NeEq => {
self.compile_expr(b, l)?;
self.compile_expr(b, r)?;
b.emit(Op::CallBuiltin(ops::LOOSE_EQ, 2), 0);
b.emit(Op::LogNot, 0);
}
BinOp::In => {
self.compile_expr(b, l)?;
self.compile_expr(b, r)?;
b.emit(Op::CallBuiltin(ops::CONTAINS, 2), 0);
}
BinOp::InstanceOf => {
self.compile_expr(b, l)?;
self.compile_expr(b, r)?;
b.emit(Op::CallBuiltin(ops::INSTANCEOF, 2), 0);
}
BinOp::BitAnd => self.emit_bitwise(b, bop::BITAND, l, r)?,
BinOp::BitOr => self.emit_bitwise(b, bop::BITOR, l, r)?,
BinOp::BitXor => self.emit_bitwise(b, bop::BITXOR, l, r)?,
BinOp::Shl => self.emit_bitwise(b, bop::SHL, l, r)?,
BinOp::Shr => self.emit_bitwise(b, bop::SHR, l, r)?,
BinOp::UShr => self.emit_bitwise(b, bop::USHR, l, r)?,
}
Ok(())
}
fn emit_bitwise(&mut self, b: &mut ChunkBuilder, tag: i64, l: &Expr, r: &Expr) -> Result<(), String> {
b.emit(Op::LoadInt(tag), 0);
self.compile_expr(b, l)?;
self.compile_expr(b, r)?;
b.emit(Op::CallBuiltin(ops::BINOP, 3), 0);
Ok(())
}
fn compile_update(&mut self, b: &mut ChunkBuilder, op: UpdateOp, prefix: bool, target: &Expr) -> Result<(), String> {
let one = Expr::Number(1.0);
let bin = if matches!(op, UpdateOp::Inc) { BinOp::Add } else { BinOp::Sub };
if prefix {
let newv = Expr::Binary(bin, Box::new(target.clone()), Box::new(one));
self.compile_expr(b, &newv)?;
b.emit(Op::Dup, 0);
self.compile_bind(b, target, false)?;
} else {
b.emit(Op::LoadInt(unop::POS), 0);
self.compile_expr(b, target)?;
b.emit(Op::CallBuiltin(ops::UNARY, 2), 0); b.emit(Op::Dup, 0); b.emit(Op::LoadFloat(1.0), 0);
match op {
UpdateOp::Inc => b.emit(Op::Add, 0),
UpdateOp::Dec => b.emit(Op::Sub, 0),
};
self.compile_bind(b, target, false)?; }
Ok(())
}
fn compile_member(&mut self, b: &mut ChunkBuilder, object: &Expr, property: &str, optional: bool) -> Result<(), String> {
self.compile_expr(b, object)?;
if optional {
let jshort = self.emit_optional_guard(b);
self.name_const(b, property);
b.emit(Op::CallBuiltin(ops::GETATTR, 2), 0);
let end = b.current_pos();
b.patch_jump(jshort, end);
} else {
self.name_const(b, property);
b.emit(Op::CallBuiltin(ops::GETATTR, 2), 0);
}
Ok(())
}
fn compile_index(&mut self, b: &mut ChunkBuilder, object: &Expr, index: &Expr, optional: bool) -> Result<(), String> {
self.compile_expr(b, object)?;
if optional {
let jshort = self.emit_optional_guard(b);
self.compile_expr(b, index)?;
b.emit(Op::CallBuiltin(ops::GETITEM, 2), 0);
let end = b.current_pos();
b.patch_jump(jshort, end);
} else {
self.compile_expr(b, index)?;
b.emit(Op::CallBuiltin(ops::GETITEM, 2), 0);
}
Ok(())
}
fn emit_optional_guard(&mut self, b: &mut ChunkBuilder) -> usize {
b.emit(Op::Dup, 0);
b.emit(Op::CallBuiltin(ops::NULLISH, 1), 0);
let jnull = b.emit(Op::JumpIfFalse(0), 0); b.emit(Op::Pop, 0);
b.emit(Op::LoadUndef, 0);
let jend = b.emit(Op::Jump(0), 0);
let cont = b.current_pos();
b.patch_jump(jnull, cont);
jend
}
fn compile_call(&mut self, b: &mut ChunkBuilder, func: &Expr, args: &[Expr], _optional: bool) -> Result<(), String> {
let has_spread = args.iter().any(|a| matches!(a, Expr::Spread(_)));
match func {
Expr::Member { object, property, .. } => {
self.compile_expr(b, object)?;
self.name_const(b, property);
if has_spread {
self.compile_spread_args(b, args)?; b.emit(Op::CallBuiltin(ops::APPLY_METHOD, 3), 0);
} else {
for a in args {
self.compile_expr(b, a)?;
}
b.emit(Op::CallBuiltin(ops::CALL_METHOD, argc(2 + args.len())?), 0);
}
}
Expr::Index { object, index, .. } => {
self.compile_expr(b, object)?; b.emit(Op::Dup, 0); self.compile_expr(b, index)?; b.emit(Op::CallBuiltin(ops::GETITEM, 2), 0); b.emit(Op::Swap, 0); b.emit(Op::Pop, 0); if has_spread {
self.compile_spread_args(b, args)?;
b.emit(Op::CallBuiltin(ops::APPLY, 2), 0);
} else {
for a in args {
self.compile_expr(b, a)?;
}
b.emit(Op::CallBuiltin(ops::CALL_VALUE, argc(1 + args.len())?), 0);
}
}
Expr::Ident(n) => {
self.name_const(b, n);
if has_spread {
self.compile_spread_args(b, args)?; b.emit(Op::Swap, 0); b.emit(Op::CallBuiltin(ops::GETLOCAL, 1), 0); b.emit(Op::Swap, 0); b.emit(Op::CallBuiltin(ops::APPLY, 2), 0);
} else {
for a in args {
self.compile_expr(b, a)?;
}
b.emit(Op::CallBuiltin(ops::CALL, argc(1 + args.len())?), 0);
}
}
_ => {
self.compile_expr(b, func)?;
if has_spread {
self.compile_spread_args(b, args)?;
b.emit(Op::CallBuiltin(ops::APPLY, 2), 0);
} else {
for a in args {
self.compile_expr(b, a)?;
}
b.emit(Op::CallBuiltin(ops::CALL_VALUE, argc(1 + args.len())?), 0);
}
}
}
Ok(())
}
fn compile_spread_args(&mut self, b: &mut ChunkBuilder, args: &[Expr]) -> Result<(), String> {
for a in args {
match a {
Expr::Spread(inner) => {
b.emit(Op::LoadInt(1), 0);
self.compile_expr(b, inner)?;
}
_ => {
b.emit(Op::LoadInt(0), 0);
self.compile_expr(b, a)?;
}
}
}
b.emit(Op::CallBuiltin(ops::BUILD_ARGS, argc(args.len() * 2)?), 0);
Ok(())
}
fn compile_new(&mut self, b: &mut ChunkBuilder, callee: &Expr, args: &[Expr]) -> Result<(), String> {
self.compile_expr(b, callee)?;
for a in args {
self.compile_expr(b, a)?;
}
b.emit(Op::CallBuiltin(ops::NEW, argc(1 + args.len())?), 0);
Ok(())
}
}
fn default_stmt(name: &str, default: &Expr) -> Stmt {
Stmt::from(StmtKind::If {
test: Expr::Binary(
BinOp::EqEqEq,
Box::new(Expr::Ident(name.to_string())),
Box::new(Expr::Undefined),
),
cons: Box::new(Stmt::from(StmtKind::Expr(Expr::Assign {
target: Box::new(Expr::Ident(name.to_string())),
value: Box::new(default.clone()),
}))),
alt: None,
})
}