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
//! The shared [`is_truthy`] predicate lives here so every submodule
12
//! plus the iteration forms can reach it via `super::is_truthy`.
13

            
14
mod and_or;
15
mod begin;
16
mod block;
17
mod block_exits;
18
mod cond;
19
mod error_form;
20
mod handler_case;
21
mod if_form;
22
mod quote;
23
mod tagbody;
24
mod unwind_protect;
25

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

            
30
use super::SpecialFormSpec;
31

            
32
47940
pub(in crate::compiler) fn is_truthy(expr: &Expr) -> bool {
33
47940
    !matches!(expr, Expr::Nil | Expr::Bool(false))
34
47940
}
35

            
36
/// Whether an evaluated IF / COND test is a *runtime* i32 condition — i.e.
37
/// it must be lowered to a wasm `if` rather than const-folded. A wasm `if`
38
/// consumes an i32, so the only valid runtime test is an i32-typed runtime
39
/// value: a host-fn/comparison result (`WasmRuntime(I32)`) or a let-bound
40
/// runtime boolean (`WasmLocal(_, I32)`). All IF/COND paths (eval, stack,
41
/// effect) MUST share this predicate, or a let-bound runtime boolean is
42
/// const-folded by one path and branched by another — a wrong-branch
43
/// miscompile.
44
/// Re-export of the divergence classifier for IF/COND callers outside the
45
/// `control` module (the effect-position IF path in `expr::effect`). Must be
46
/// run on a CLONED symbol table — see [`block_exits::form_diverges`].
47
19374
pub(in crate::compiler) fn form_diverges_for_test(
48
19374
    symbols: &mut SymbolTable,
49
19374
    form: &Expr,
50
19374
) -> Result<bool> {
51
19374
    block_exits::form_diverges(symbols, form)
52
19374
}
53

            
54
25470
pub(in crate::compiler) fn is_runtime_test(test: &Expr) -> bool {
55
10268
    matches!(
56
14726
        test,
57
        Expr::WasmRuntime(WasmType::I32 | WasmType::Bool)
58
            | Expr::WasmLocal(_, WasmType::I32 | WasmType::Bool)
59
    )
60
25470
}
61

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

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