use crate::ast::{Attr, For, ForGen, ForNum};
use crate::compiler::{offset, Compiler, ScopeType};
use crate::error::Result;
use crate::vm::{BinOp, Literal, OpCode, Oper};
impl Compiler {
pub(super) fn compile_for(&mut self, r#for: For) -> Result<()> {
match r#for {
For::Numeric(f) => self.compile_for_num(f),
For::Generic(f) => self.compile_for_gen(f),
}
}
fn compile_for_num(&mut self, r#for: ForNum) -> Result<()> {
self.scope_enter(ScopeType::Loop);
let ctrl_reg = self.compile_exp(*r#for.init)?;
let ctrl = self.declare_local(r#for.name, None, r#for.pos_for);
let limit_reg = self.compile_exp(*r#for.limit)?;
let step_reg = r#for.step.map(|step| self.compile_exp(*step)).transpose()?;
let for_start = self.code.current_pc();
self.code.emit(OpCode::Jump { off: 0 }, r#for.pos_for);
let start = self.code.current_pc();
self.compile_block(*r#for.block)?;
self.scopes.mark_loop_end(self.code.current_pc());
let for_end = self.code.current_pc();
self.code.emit(OpCode::Jump { off: 0 }, r#for.pos_for);
let end = self.code.current_pc();
self.code.set_op(
for_start,
OpCode::ForNum {
ctrl_reg,
limit_reg,
step_reg,
off_start: offset(for_start, start),
off_end: offset(for_start, end),
ctrl_loc: ctrl,
},
);
self.code.set_op(
for_end,
OpCode::ForNum {
ctrl_reg,
limit_reg,
step_reg,
off_start: offset(for_end, start),
off_end: offset(for_end, end),
ctrl_loc: ctrl,
},
);
self.scopes.reg_free(ctrl_reg);
self.scopes.reg_free(limit_reg);
if let Some(step_reg) = step_reg {
self.scopes.reg_free(step_reg);
}
self.scope_leave(ScopeType::Loop, r#for.pos_end)?;
Ok(())
}
fn compile_for_gen(&mut self, mut r#for: ForGen) -> Result<()> {
let exp_reg = self.scopes.reg_reserve();
let args_reg = self.scopes.reg_reserve();
self.code.emit(
OpCode::Lit {
val: Literal::Empty,
dst_reg: exp_reg,
},
r#for.pos_for,
);
let exp_last = r#for.exps.pop();
for exp in r#for.exps {
let src_reg = self.compile_exp(exp)?;
self.code.emit(
OpCode::Append {
src_reg,
dst_reg: exp_reg,
},
r#for.pos_for,
);
self.scopes.reg_free(src_reg);
}
if let Some(exp) = exp_last {
let src_reg = self.compile_exp(exp)?;
self.code.emit(
OpCode::Extend {
src_reg,
dst_reg: exp_reg,
},
r#for.pos_for,
);
self.scopes.reg_free(src_reg);
}
self.scope_enter(ScopeType::Loop);
let mut vars = Vec::new();
for name in r#for.names.into_iter() {
let loc = self.declare_local(name, None, r#for.pos_for);
vars.push(loc);
}
let iter = self.declare_local("(for state)".to_string(), None, r#for.pos_for);
self.code.emit(
OpCode::MovMult {
src_reg: exp_reg,
ind: 0,
dst_reg: args_reg,
},
r#for.pos_for,
);
self.code.emit(
OpCode::LocalSet {
src_reg: args_reg,
dst_loc: iter,
},
r#for.pos_for,
);
let state = self.declare_local("(for state)".to_string(), None, r#for.pos_for);
self.code.emit(
OpCode::MovMult {
src_reg: exp_reg,
ind: 1,
dst_reg: args_reg,
},
r#for.pos_for,
);
self.code.emit(
OpCode::LocalSet {
src_reg: args_reg,
dst_loc: state,
},
r#for.pos_for,
);
self.declare_local("(for state)".to_string(), None, r#for.pos_for);
let ctrl = vars[0];
self.code.emit(
OpCode::MovMult {
src_reg: exp_reg,
ind: 2,
dst_reg: args_reg,
},
r#for.pos_for,
);
self.code.emit(
OpCode::LocalSet {
src_reg: args_reg,
dst_loc: ctrl,
},
r#for.pos_for,
);
let close = self.declare_local("(for state)".to_string(), Some(Attr::Close), r#for.pos_for);
self.code.emit(
OpCode::MovMult {
src_reg: exp_reg,
ind: 3,
dst_reg: args_reg,
},
r#for.pos_for,
);
self.code.emit(
OpCode::LocalSet {
src_reg: args_reg,
dst_loc: close,
},
r#for.pos_for,
);
let start = self.code.current_pc();
self.code.emit(
OpCode::Lit {
val: Literal::Empty,
dst_reg: args_reg,
},
r#for.pos_call,
);
self.code.emit(
OpCode::LocalGet {
src_loc: state,
dst_reg: exp_reg,
},
r#for.pos_call,
);
self.code.emit(
OpCode::Append {
src_reg: exp_reg,
dst_reg: args_reg,
},
r#for.pos_call,
);
self.code.emit(
OpCode::LocalGet {
src_loc: ctrl,
dst_reg: exp_reg,
},
r#for.pos_call,
);
self.code.emit(
OpCode::Append {
src_reg: exp_reg,
dst_reg: args_reg,
},
r#for.pos_call,
);
self.code.emit(
OpCode::LocalGet {
src_loc: iter,
dst_reg: exp_reg,
},
r#for.pos_call,
);
self.code.emit(
OpCode::Call {
func_reg: exp_reg,
args_reg,
ret_reg: exp_reg,
tail: false,
},
r#for.pos_call,
);
for (ind, var) in vars.into_iter().enumerate() {
self.code.emit(
OpCode::MovMult {
src_reg: exp_reg,
ind,
dst_reg: args_reg,
},
r#for.pos_for,
);
self.code.emit(
OpCode::LocalSet {
src_reg: args_reg,
dst_loc: var,
},
r#for.pos_for,
);
}
self.code.emit(
OpCode::LocalGet {
src_loc: ctrl,
dst_reg: exp_reg,
},
r#for.pos_for,
);
self.code.emit(
OpCode::Lit {
val: Literal::Nil,
dst_reg: args_reg,
},
r#for.pos_for,
);
self.code.emit(
OpCode::BinOp {
lhs: Oper::Reg(exp_reg),
rhs: Oper::Reg(args_reg),
op: BinOp::Eq,
dst_reg: exp_reg,
},
r#for.pos_for,
);
let jump_end = self.code.current_pc();
self.code.emit(OpCode::Jump { off: 0 }, r#for.pos_for);
self.compile_block(*r#for.block)?;
self.scopes.mark_loop_end(self.code.current_pc());
let off = offset(self.code.current_pc(), start);
self.code
.emit(OpCode::Jump { off }, self.code.get_pos_last());
let off = offset(jump_end, self.code.current_pc());
self.code.set_op(
jump_end,
OpCode::JumpIf {
cmp_reg: exp_reg,
off,
},
);
self.scopes.reg_free(exp_reg);
self.scopes.reg_free(args_reg);
self.scope_leave(ScopeType::Loop, r#for.pos_end)?;
Ok(())
}
}