1
//! `COND` clause chain. The stack-result variant rewrites the chain
2
//! into a nested `IF` and delegates to `compile_if_for_stack` so the
3
//! resulting wasm block carries a value back to the caller. Effect
4
//! and runtime paths walk the chain directly so a known-true clause
5
//! can stop emission early.
6

            
7
use wasm_encoder::BlockType;
8

            
9
use crate::ast::{Expr, WasmType};
10
use crate::compiler::context::CompileContext;
11
use crate::compiler::emit::FunctionEmitter;
12
use crate::compiler::expr::{
13
    compile_body, compile_expr, compile_for_effect, compile_for_stack, compile_nil, eval_value,
14
    serialize_stack_to_output,
15
};
16
use crate::error::{Error, Result};
17
use crate::runtime::SymbolTable;
18

            
19
use super::is_truthy;
20

            
21
852
pub(super) fn compile_cond(
22
852
    ctx: &mut CompileContext,
23
852
    emit: &mut FunctionEmitter,
24
852
    symbols: &mut SymbolTable,
25
852
    args: &[Expr],
26
852
) -> Result<()> {
27
994
    for (i, clause) in args.iter().enumerate() {
28
994
        let elems = clause.as_list().ok_or_else(|| {
29
            Error::Compile(format!("COND: clause must be a list, got {clause:?}"))
30
        })?;
31
994
        if elems.is_empty() {
32
            return Err(Error::Compile("COND: empty clause".to_string()));
33
994
        }
34
        // Classify on a CLONE so the test's compile-time effects reach the
35
        // live table only via the single emit below.
36
994
        let test = eval_value(&mut symbols.clone(), &elems[0])?;
37
994
        let test_diverges = super::block_exits::form_diverges(&mut symbols.clone(), &elems[0])?;
38
994
        super::reject_non_boolean_runtime_test(&test, test_diverges)?;
39
994
        if super::is_runtime_test(&test) || test_diverges {
40
            // Runtime (or diverging) test: emit ONE merged `if (result T)`
41
            // chain via the stack path, then serialize the single result.
42
            // Serializing each clause body separately double-advances the
43
            // compile-time output cursor (every clause bakes an entity header,
44
            // but only one runs) — the decoder then reads a garbage entity
45
            // slot. The IF-chain the stack path builds also fires a diverging
46
            // test correctly (vs. const-folding past it). Mirror IF's fix.
47
284
            let ty = compile_cond_for_stack(ctx, emit, symbols, &args[i..])?;
48
284
            return serialize_stack_to_output(ctx, emit, ty);
49
710
        }
50
        // Const test: apply its effects to the live table once.
51
710
        let test = eval_value(symbols, &elems[0])?;
52
710
        if is_truthy(&test) {
53
426
            if elems.len() == 1 {
54
71
                return compile_expr(ctx, emit, symbols, &test);
55
355
            }
56
355
            return compile_body(ctx, emit, symbols, &elems[1..]);
57
284
        }
58
    }
59
142
    compile_nil(ctx, emit);
60
142
    Ok(())
61
852
}
62

            
63
355
pub(super) fn compile_cond_for_stack(
64
355
    ctx: &mut CompileContext,
65
355
    emit: &mut FunctionEmitter,
66
355
    symbols: &mut SymbolTable,
67
355
    args: &[Expr],
68
355
) -> Result<WasmType> {
69
355
    let chain = rewrite_cond_to_if_chain(args)?;
70
355
    compile_for_stack(ctx, emit, symbols, &chain)
71
355
}
72

            
73
1065
fn rewrite_cond_to_if_chain(clauses: &[Expr]) -> Result<Expr> {
74
1065
    if clauses.is_empty() {
75
355
        return Ok(Expr::Nil);
76
710
    }
77
710
    let head = clauses[0].as_list().ok_or_else(|| {
78
        Error::Compile(format!("COND: clause must be a list, got {:?}", clauses[0]))
79
    })?;
80
710
    if head.is_empty() {
81
        return Err(Error::Compile("COND: empty clause".to_string()));
82
710
    }
83
710
    let test = head[0].clone();
84
710
    let body = match head.len() {
85
        1 => test.clone(),
86
710
        2 => head[1].clone(),
87
        _ => {
88
            let mut forms = Vec::with_capacity(head.len());
89
            forms.push(Expr::Symbol("BEGIN".to_string()));
90
            forms.extend_from_slice(&head[1..]);
91
            Expr::List(forms)
92
        }
93
    };
94
710
    let else_branch = rewrite_cond_to_if_chain(&clauses[1..])?;
95
710
    Ok(Expr::List(vec![
96
710
        Expr::Symbol("IF".to_string()),
97
710
        test,
98
710
        body,
99
710
        else_branch,
100
710
    ]))
101
1065
}
102

            
103
568
pub(super) fn compile_cond_for_effect(
104
568
    ctx: &mut CompileContext,
105
568
    emit: &mut FunctionEmitter,
106
568
    symbols: &mut SymbolTable,
107
568
    args: &[Expr],
108
568
) -> Result<()> {
109
568
    for (i, clause) in args.iter().enumerate() {
110
568
        let elems = clause.as_list().ok_or_else(|| {
111
            Error::Compile(format!("COND: clause must be a list, got {clause:?}"))
112
        })?;
113
568
        if elems.is_empty() {
114
            return Err(Error::Compile("COND: empty clause".to_string()));
115
568
        }
116
        // Classify on a CLONE so the test's compile-time side effects are not
117
        // applied to the live table during classification — they ride the
118
        // single emit below exactly once.
119
568
        let test = eval_value(&mut symbols.clone(), &elems[0])?;
120
568
        let test_diverges = super::block_exits::form_diverges(&mut symbols.clone(), &elems[0])?;
121
568
        super::reject_non_boolean_runtime_test(&test, test_diverges)?;
122
568
        if super::is_runtime_test(&test) || test_diverges {
123
568
            return compile_cond_runtime_for_effect(ctx, emit, symbols, &args[i..]);
124
        }
125
        // Const-false/true test: apply its effects to the live table once.
126
        let test = eval_value(symbols, &elems[0])?;
127
        if is_truthy(&test) {
128
            for expr in &elems[1..] {
129
                compile_for_effect(ctx, emit, symbols, expr)?;
130
            }
131
            return Ok(());
132
        }
133
    }
134
    Ok(())
135
568
}
136

            
137
/// Effect-position codegen for a COND suffix whose first clause has a runtime
138
/// (or diverging) test. Each runtime clause opens a guard `if` and (when not
139
/// last) an `else`; clause bodies compile for effect — so effect-only forms
140
/// (`DOLIST`, etc.) that have no stack lowering still work, unlike a
141
/// stack+drop rewrite. Tracks the ACTUAL number of open guard blocks (not the
142
/// clause index, which over-counts when const-false clauses sit between
143
/// runtime ones) so the closing `block_end`s match exactly.
144
568
fn compile_cond_runtime_for_effect(
145
568
    ctx: &mut CompileContext,
146
568
    emit: &mut FunctionEmitter,
147
568
    symbols: &mut SymbolTable,
148
568
    args: &[Expr],
149
568
) -> Result<()> {
150
568
    let last = args.len() - 1;
151
568
    let mut open_guards = 0u32;
152
1207
    for (i, clause) in args.iter().enumerate() {
153
1207
        let elems = clause.as_list().ok_or_else(|| {
154
            Error::Compile(format!("COND: clause must be a list, got {clause:?}"))
155
        })?;
156
1207
        if elems.is_empty() {
157
            return Err(Error::Compile("COND: empty clause".to_string()));
158
1207
        }
159
1207
        let test = eval_value(&mut symbols.clone(), &elems[0])?;
160
1207
        let test_diverges = super::block_exits::form_diverges(&mut symbols.clone(), &elems[0])?;
161
1207
        super::reject_non_boolean_runtime_test(&test, test_diverges)?;
162
1207
        if test_diverges {
163
            // Compile the test for effect so its exit fires; the remaining
164
            // chain is dead. Close exactly the guards opened so far.
165
213
            compile_for_effect(ctx, emit, symbols, &elems[0])?;
166
213
            for _ in 0..open_guards {
167
                emit.block_end();
168
            }
169
213
            return Ok(());
170
994
        }
171
994
        if super::is_runtime_test(&test) {
172
923
            compile_for_stack(ctx, emit, symbols, &elems[0])?;
173
923
            emit.if_block(BlockType::Empty);
174
            // Each runtime clause opens exactly one `if` block (one `end`),
175
            // whether or not an `else` arm follows — count it.
176
923
            open_guards += 1;
177
923
            for expr in &elems[1..] {
178
923
                compile_for_effect(ctx, emit, symbols, expr)?;
179
            }
180
923
            if i != last {
181
568
                emit.else_block();
182
568
            }
183
        } else {
184
            // Const test: apply effects to the live table once.
185
71
            let test = eval_value(symbols, &elems[0])?;
186
71
            if is_truthy(&test) {
187
                for expr in &elems[1..] {
188
                    compile_for_effect(ctx, emit, symbols, expr)?;
189
                }
190
                for _ in 0..open_guards {
191
                    emit.block_end();
192
                }
193
                return Ok(());
194
71
            }
195
        }
196
    }
197
923
    for _ in 0..open_guards {
198
923
        emit.block_end();
199
923
    }
200
355
    Ok(())
201
568
}
202

            
203
142
pub(super) fn cond_form(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
204
284
    for (i, clause) in args.iter().enumerate() {
205
284
        let elems = clause.as_list().ok_or_else(|| {
206
            Error::Compile(format!("COND: clause must be a list, got {clause:?}"))
207
        })?;
208
284
        if elems.is_empty() {
209
            return Err(Error::Compile("COND: empty clause".to_string()));
210
284
        }
211
        // Classify the test on a CLONE so its compile-time side effects
212
        // (setf / macro expansion) are NOT applied to the live table here:
213
        // a const-false test re-evals once on `symbols` below, and a
214
        // runtime/diverging test's effects ride the single `eval_value` of
215
        // the synthesized chain — never twice.
216
284
        let test = eval_value(&mut symbols.clone(), &elems[0])?;
217
284
        let test_diverges = super::block_exits::form_diverges(&mut symbols.clone(), &elems[0])?;
218
284
        super::reject_non_boolean_runtime_test(&test, test_diverges)?;
219
284
        if super::is_runtime_test(&test) || test_diverges {
220
            // First runtime (or diverging) test: the result type is the
221
            // unified type of the remaining IF-chain (the same chain
222
            // `compile_cond_for_stack` emits), NOT a blanket I32 — a binder
223
            // sizing a local from this type must match the codegen. This is
224
            // the SINGLE place the suffix's side effects reach `symbols`.
225
            let chain = rewrite_cond_to_if_chain(&args[i..])?;
226
            return eval_value(symbols, &chain);
227
284
        }
228
        // Const test: apply its effects to the live table exactly once.
229
284
        let test = eval_value(symbols, &elems[0])?;
230
284
        if is_truthy(&test) {
231
142
            if elems.len() == 1 {
232
                return Ok(test);
233
142
            }
234
142
            return super::super::binding::eval_body(symbols, &elems[1..]);
235
142
        }
236
    }
237
    Ok(Expr::Nil)
238
142
}