Lines
91.89 %
Functions
33.33 %
Branches
100 %
//! Control-flow special forms.
//!
//! Split by form:
//! - [`quote`] — `QUOTE`.
//! - [`if_form`] — `IF` (compile + for-stack + eval).
//! - [`begin`] — `BEGIN` (sequencing).
//! - [`and_or`] — `AND` / `OR` short-circuit boolean forms.
//! - [`cond`] — `COND` clause chain (rewrites to nested IF for stack).
//! - [`block`] — `BLOCK` / `RETURN-FROM` lexical labelled exits.
mod and_or;
mod arms;
mod begin;
mod block;
mod block_exits;
mod cond;
mod error_form;
mod handler_case;
mod if_form;
mod quote;
mod tagbody;
mod unwind_protect;
use crate::ast::{Expr, WasmType};
use crate::error::{Error, Result};
use crate::runtime::SymbolTable;
use super::SpecialFormSpec;
/// Re-export of the divergence classifier for IF/COND callers outside the
/// `control` module (the effect-position IF path in `expr::effect`). Must be
/// run on a CLONED symbol table — see [`block_exits::form_diverges`].
pub(in crate::compiler) fn form_diverges_for_test(
symbols: &mut SymbolTable,
form: &Expr,
) -> Result<bool> {
block_exits::form_diverges(symbols, form)
}
/// A Bool runtime value branches; an index test folds true (a count is never
/// nil) through [`apply_const_test`]. Every IF / COND path (eval, stack,
/// effect) shares this, so no path branches where another folds.
pub(in crate::compiler) fn is_runtime_test(test: &Expr) -> bool {
matches!(
test,
Expr::WasmRuntime(WasmType::Bool) | Expr::WasmLocal(_, WasmType::Bool)
)
/// Applies a const-path IF/COND test exactly once and returns the value to
/// branch on: an index test (always truthy) is compiled for its runtime
/// effects, a constant test applies its compile-time effects to the live table.
pub(in crate::compiler) fn apply_const_test(
ctx: &mut crate::compiler::context::CompileContext,
emit: &mut crate::compiler::emit::FunctionEmitter,
classified: &Expr,
) -> Result<Expr> {
if classified.is_wasm_runtime() {
crate::compiler::expr::compile_for_effect(ctx, emit, symbols, form)?;
return Ok(classified.clone());
crate::compiler::expr::eval_value(symbols, form)
/// A wasm `if` consumes an i32 condition. A runtime test of any other type
/// (e.g. a `Ratio` returned by a host fn) has no boolean lowering — silently
/// const-folding it via [`Expr::is_nil`] would mis-branch, so reject it loudly.
/// Constants fold, a Bool runtime value branches and an index runtime value
/// folds true; any other runtime type is the error. Shared by IF and COND.
///
/// A *diverging* test (`(return-from …)`, `(error …)`) is exempt: it never
/// produces a value used as a condition — control transfers away first — so
/// its nominal non-i32 type is irrelevant. `diverges` must be classified on a
/// cloned symbol table by the caller (the test runs before the branches, so
/// emit-time discovery still collects its exit).
pub(in crate::compiler) fn reject_non_boolean_runtime_test(
test: &Expr,
diverges: bool,
) -> Result<()> {
if diverges {
return Ok(());
match test {
Expr::WasmRuntime(WasmType::I32 | WasmType::Bool)
| Expr::WasmLocal(_, WasmType::I32 | WasmType::Bool) => Ok(()),
Expr::WasmRuntime(ty) | Expr::WasmLocal(_, ty) => Err(Error::Compile(format!(
"IF/COND test must be a boolean (i32) runtime value, got {ty}"
))),
_ => Ok(()),
pub(super) const FORMS: &[SpecialFormSpec] = &[
SpecialFormSpec {
name: "QUOTE",
eval: quote::eval_quote,
compile: quote::spec_compile_quote,
stack: None,
effect: None,
},
name: "IF",
eval: if_form::if_form,
compile: if_form::compile_if,
stack: Some(if_form::compile_if_for_stack),
name: "BEGIN",
eval: begin::begin_form,
compile: begin::compile_begin,
stack: Some(begin::compile_begin_for_stack),
name: "AND",
eval: and_or::and_form,
compile: and_or::compile_and,
stack: Some(and_or::compile_and_for_stack),
effect: Some(and_or::compile_and_for_effect),
name: "OR",
eval: and_or::or_form,
compile: and_or::compile_or,
stack: Some(and_or::compile_or_for_stack),
effect: Some(and_or::compile_or_for_effect),
name: "COND",
eval: cond::cond_form,
compile: cond::compile_cond,
stack: Some(cond::compile_cond_for_stack),
effect: Some(cond::compile_cond_for_effect),
name: "ERROR",
eval: error_form::eval_error,
compile: error_form::compile_error,
stack: Some(error_form::compile_error_for_stack),
name: "BLOCK",
eval: block::eval_block,
compile: block::compile_block,
stack: Some(block::compile_block_for_stack),
effect: Some(block::compile_block_for_effect),
name: "RETURN-FROM",
eval: block::eval_return_from,
compile: block::compile_return_from,
stack: Some(block::compile_return_from_for_stack),
name: "TAGBODY",
eval: tagbody::eval_tagbody,
compile: tagbody::compile_tagbody,
stack: Some(tagbody::compile_tagbody_for_stack),
effect: Some(tagbody::compile_tagbody_for_effect),
name: "GO",
eval: tagbody::eval_go,
compile: tagbody::compile_go,
stack: Some(tagbody::compile_go_for_stack),
name: "HANDLER-CASE",
eval: handler_case::eval_handler_case,
compile: handler_case::compile_handler_case,
stack: Some(handler_case::compile_handler_case_for_stack),
effect: Some(handler_case::compile_handler_case_for_effect),
name: "UNWIND-PROTECT",
eval: unwind_protect::eval_unwind_protect,
compile: unwind_protect::compile_unwind_protect,
stack: Some(unwind_protect::compile_unwind_protect_for_stack),
effect: Some(unwind_protect::compile_unwind_protect_for_effect),
];