Lines
73.36 %
Functions
23.33 %
Branches
100 %
//! `DO*` (sequential-step variant) compile + eval handlers.
//!
//! DO* evaluates inits sequentially — each binding is visible to
//! later inits in the same form. Steps similarly apply one var at a
//! time. The sequential flag rides through `DoLoop.sequential` so the
//! shared `runtime::compile_do_runtime_loop` does the right thing.
use crate::ast::{Expr, WasmType};
use crate::compiler::context::CompileContext;
use crate::compiler::emit::FunctionEmitter;
use crate::compiler::expr::{compile_body, compile_for_effect, compile_nil, eval_value};
use crate::error::{Error, Result};
use crate::runtime::{Symbol, SymbolKind, SymbolTable};
use super::super::binding::eval_body;
use super::super::control::is_truthy;
use super::common::{
DoLoop, infer_wasm_type, parse_do_vars, parse_end_clause, static_loop_terminates,
};
use super::runtime::{
compile_do_runtime, compile_do_runtime_for_effect, compile_do_runtime_for_stack,
pub(super) fn compile_do_star_for_stack(
ctx: &mut CompileContext,
emit: &mut FunctionEmitter,
symbols: &mut SymbolTable,
args: &[Expr],
) -> Result<WasmType> {
if args.len() < 2 {
return Err(Error::Compile(
"DO* requires a variable list and an end clause".to_string(),
));
}
let vars = parse_do_vars("DO*", &args[0])?;
let (end_test, result_forms) = parse_end_clause("DO*", &args[1])?;
let end_test = end_test.clone();
let result_forms: Vec<Expr> = result_forms.to_vec();
let body = &args[2..];
let dl = DoLoop {
vars: &vars,
end_test: &end_test,
result_forms: &result_forms,
body,
sequential: true,
compile_do_runtime_for_stack(ctx, emit, symbols, &dl)
pub(super) fn compile_do_star(
) -> Result<()> {
// DO* evaluates inits sequentially — each binding is visible to later inits
let mut local = symbols.clone();
let mut needs_runtime = false;
for v in &vars {
let val = match &v.init {
Some(expr) => eval_value(&mut local, expr)?,
None => Expr::Nil,
if val.is_wasm_runtime() {
needs_runtime = true;
local.define(Symbol::new(&v.name, SymbolKind::Variable).with_value(val));
if !needs_runtime {
let test_result = eval_value(&mut local, &end_test)?;
needs_runtime = test_result.is_wasm_runtime();
if needs_runtime {
return compile_do_runtime(ctx, emit, symbols, &dl);
let mut stepped: Vec<(String, Option<Expr>)> = Vec::new();
stepped.push((v.name.clone(), v.step.clone()));
if !static_loop_terminates(&local, &end_test, &stepped, true) {
loop {
let test = eval_value(&mut local, &end_test)?;
if is_truthy(&test) {
return if result_forms.is_empty() {
compile_nil(ctx, emit);
Ok(())
} else {
compile_body(ctx, emit, &mut local, &result_forms)
for expr in body {
compile_for_effect(ctx, emit, &mut local, expr)?;
for (name, step) in &stepped {
if let Some(s) = step {
let val = eval_value(&mut local, s)?;
local
.lookup_mut(name)
.expect("DO* variable must exist")
.set_value(val);
pub(super) fn compile_do_star_for_effect(
return compile_do_runtime_for_effect(ctx, emit, symbols, &dl);
let stepped: Vec<(String, Option<Expr>)> = vars
.iter()
.map(|v| (v.name.clone(), v.step.clone()))
.collect();
for expr in &result_forms {
return Ok(());
pub(super) fn do_star_form(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
for v in vars {
let val = match v.init {
Some(expr) => eval_value(&mut local, &expr)?,
stepped.push((v.name, v.step));
if test_result.is_wasm_runtime() {
// Match `do_form`: derive the result's PairElement from the
// accumulator's body-setf `(setf <result> (cons V <result>))` so
// the static type matches the PairRef shape the codegen path emits.
// Each DO* var is resolved as its RUNTIME stack type, not its const
// init — an integer init (`(i 0 …)`) makes `i` a runtime Index (I32),
// so `(cons i …)` is an I32 cell; resolving `i` to the const `0` would
// type it as Ratio (the numeric-literal cell slot) and a consumer
// dolist would downcast to the wrong element → a runtime cast trap.
let mut infer_env = symbols.clone();
let init_val = local
.lookup(name)
.and_then(|s| s.value().cloned())
.unwrap_or(Expr::Nil);
let ty = infer_wasm_type(&init_val, step.as_ref(), &infer_env);
infer_env.define(Symbol::new(name, SymbolKind::Variable).with_value(Expr::WasmRuntime(ty)));
let result_ty = super::do_form::runtime_result_type(&result_forms, body, &stepped, &infer_env);
return Ok(Expr::WasmRuntime(result_ty));
Ok(Expr::Nil)
eval_body(&mut local, &result_forms)
eval_value(&mut local, expr)?;