1
//! Control-flow special forms.
2
//!
3
//! Split by form:
4
//! - [`quote`] — `QUOTE`.
5
//! - [`if_form`] — `IF` (compile + for-stack + eval).
6
//! - [`begin`] — `BEGIN` (sequencing).
7
//! - [`and_or`] — `AND` / `OR` short-circuit boolean forms.
8
//! - [`cond`] — `COND` clause chain (rewrites to nested IF for stack).
9
//! - [`block`] — `BLOCK` / `RETURN-FROM` lexical labelled exits.
10

            
11
mod and_or;
12
mod arms;
13
mod begin;
14
mod block;
15
mod block_exits;
16
mod cond;
17
mod error_form;
18
mod handler_case;
19
mod if_form;
20
mod quote;
21
mod tagbody;
22
mod unwind_protect;
23

            
24
use crate::ast::{Expr, WasmType};
25
use crate::error::{Error, Result};
26
use crate::runtime::SymbolTable;
27

            
28
use super::SpecialFormSpec;
29

            
30
/// Re-export of the divergence classifier for IF/COND callers outside the
31
/// `control` module (the effect-position IF path in `expr::effect`). Must be
32
/// run on a CLONED symbol table — see [`block_exits::form_diverges`].
33
26975
pub(in crate::compiler) fn form_diverges_for_test(
34
26975
    symbols: &mut SymbolTable,
35
26975
    form: &Expr,
36
26975
) -> Result<bool> {
37
26975
    block_exits::form_diverges(symbols, form)
38
26975
}
39

            
40
/// A Bool runtime value branches; an index test folds true (a count is never
41
/// nil) through [`apply_const_test`]. Every IF / COND path (eval, stack,
42
/// effect) shares this, so no path branches where another folds.
43
35275
pub(in crate::compiler) fn is_runtime_test(test: &Expr) -> bool {
44
13031
    matches!(
45
21995
        test,
46
        Expr::WasmRuntime(WasmType::Bool) | Expr::WasmLocal(_, WasmType::Bool)
47
    )
48
35275
}
49

            
50
/// Applies a const-path IF/COND test exactly once and returns the value to
51
/// branch on: an index test (always truthy) is compiled for its runtime
52
/// effects, a constant test applies its compile-time effects to the live table.
53
9213
pub(in crate::compiler) fn apply_const_test(
54
9213
    ctx: &mut crate::compiler::context::CompileContext,
55
9213
    emit: &mut crate::compiler::emit::FunctionEmitter,
56
9213
    symbols: &mut SymbolTable,
57
9213
    form: &Expr,
58
9213
    classified: &Expr,
59
9213
) -> Result<Expr> {
60
9213
    if classified.is_wasm_runtime() {
61
83
        crate::compiler::expr::compile_for_effect(ctx, emit, symbols, form)?;
62
83
        return Ok(classified.clone());
63
9130
    }
64
9130
    crate::compiler::expr::eval_value(symbols, form)
65
9213
}
66

            
67
/// A wasm `if` consumes an i32 condition. A runtime test of any other type
68
/// (e.g. a `Ratio` returned by a host fn) has no boolean lowering — silently
69
/// const-folding it via [`Expr::is_nil`] would mis-branch, so reject it loudly.
70
/// Constants fold, a Bool runtime value branches and an index runtime value
71
/// folds true; any other runtime type is the error. Shared by IF and COND.
72
///
73
/// A *diverging* test (`(return-from …)`, `(error …)`) is exempt: it never
74
/// produces a value used as a condition — control transfers away first — so
75
/// its nominal non-i32 type is irrelevant. `diverges` must be classified on a
76
/// cloned symbol table by the caller (the test runs before the branches, so
77
/// emit-time discovery still collects its exit).
78
35690
pub(in crate::compiler) fn reject_non_boolean_runtime_test(
79
35690
    test: &Expr,
80
35690
    diverges: bool,
81
35690
) -> Result<()> {
82
35690
    if diverges {
83
664
        return Ok(());
84
35026
    }
85
21746
    match test {
86
        Expr::WasmRuntime(WasmType::I32 | WasmType::Bool)
87
22327
        | Expr::WasmLocal(_, WasmType::I32 | WasmType::Bool) => Ok(()),
88
        Expr::WasmRuntime(ty) | Expr::WasmLocal(_, ty) => Err(Error::Compile(format!(
89
            "IF/COND test must be a boolean (i32) runtime value, got {ty}"
90
        ))),
91
12699
        _ => Ok(()),
92
    }
93
35690
}
94

            
95
pub(super) const FORMS: &[SpecialFormSpec] = &[
96
    SpecialFormSpec {
97
        name: "QUOTE",
98
        eval: quote::eval_quote,
99
        compile: quote::spec_compile_quote,
100
        stack: None,
101
        effect: None,
102
    },
103
    SpecialFormSpec {
104
        name: "IF",
105
        eval: if_form::if_form,
106
        compile: if_form::compile_if,
107
        stack: Some(if_form::compile_if_for_stack),
108
        effect: None,
109
    },
110
    SpecialFormSpec {
111
        name: "BEGIN",
112
        eval: begin::begin_form,
113
        compile: begin::compile_begin,
114
        stack: Some(begin::compile_begin_for_stack),
115
        effect: None,
116
    },
117
    SpecialFormSpec {
118
        name: "AND",
119
        eval: and_or::and_form,
120
        compile: and_or::compile_and,
121
        stack: Some(and_or::compile_and_for_stack),
122
        effect: Some(and_or::compile_and_for_effect),
123
    },
124
    SpecialFormSpec {
125
        name: "OR",
126
        eval: and_or::or_form,
127
        compile: and_or::compile_or,
128
        stack: Some(and_or::compile_or_for_stack),
129
        effect: Some(and_or::compile_or_for_effect),
130
    },
131
    SpecialFormSpec {
132
        name: "COND",
133
        eval: cond::cond_form,
134
        compile: cond::compile_cond,
135
        stack: Some(cond::compile_cond_for_stack),
136
        effect: Some(cond::compile_cond_for_effect),
137
    },
138
    SpecialFormSpec {
139
        name: "ERROR",
140
        eval: error_form::eval_error,
141
        compile: error_form::compile_error,
142
        stack: Some(error_form::compile_error_for_stack),
143
        effect: None,
144
    },
145
    SpecialFormSpec {
146
        name: "BLOCK",
147
        eval: block::eval_block,
148
        compile: block::compile_block,
149
        stack: Some(block::compile_block_for_stack),
150
        effect: Some(block::compile_block_for_effect),
151
    },
152
    SpecialFormSpec {
153
        name: "RETURN-FROM",
154
        eval: block::eval_return_from,
155
        compile: block::compile_return_from,
156
        stack: Some(block::compile_return_from_for_stack),
157
        effect: None,
158
    },
159
    SpecialFormSpec {
160
        name: "TAGBODY",
161
        eval: tagbody::eval_tagbody,
162
        compile: tagbody::compile_tagbody,
163
        stack: Some(tagbody::compile_tagbody_for_stack),
164
        effect: Some(tagbody::compile_tagbody_for_effect),
165
    },
166
    SpecialFormSpec {
167
        name: "GO",
168
        eval: tagbody::eval_go,
169
        compile: tagbody::compile_go,
170
        stack: Some(tagbody::compile_go_for_stack),
171
        effect: None,
172
    },
173
    SpecialFormSpec {
174
        name: "HANDLER-CASE",
175
        eval: handler_case::eval_handler_case,
176
        compile: handler_case::compile_handler_case,
177
        stack: Some(handler_case::compile_handler_case_for_stack),
178
        effect: Some(handler_case::compile_handler_case_for_effect),
179
    },
180
    SpecialFormSpec {
181
        name: "UNWIND-PROTECT",
182
        eval: unwind_protect::eval_unwind_protect,
183
        compile: unwind_protect::compile_unwind_protect,
184
        stack: Some(unwind_protect::compile_unwind_protect_for_stack),
185
        effect: Some(unwind_protect::compile_unwind_protect_for_effect),
186
    },
187
];