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
816
pub(super) fn compile_cond(
22
816
    ctx: &mut CompileContext,
23
816
    emit: &mut FunctionEmitter,
24
816
    symbols: &mut SymbolTable,
25
816
    args: &[Expr],
26
816
) -> Result<()> {
27
952
    for (i, clause) in args.iter().enumerate() {
28
952
        let elems = clause.as_list().ok_or_else(|| {
29
            Error::Compile(format!("COND: clause must be a list, got {clause:?}"))
30
        })?;
31
952
        if elems.is_empty() {
32
            return Err(Error::Compile("COND: empty clause".to_string()));
33
952
        }
34
        // Classify on a CLONE so the test's compile-time effects reach the
35
        // live table only via the single emit below.
36
952
        let test = eval_value(&mut symbols.clone(), &elems[0])?;
37
952
        let test_diverges = super::block_exits::form_diverges(&mut symbols.clone(), &elems[0])?;
38
952
        super::reject_non_boolean_runtime_test(&test, test_diverges)?;
39
952
        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
272
            let ty = compile_cond_for_stack(ctx, emit, symbols, &args[i..])?;
48
272
            return serialize_stack_to_output(ctx, emit, ty);
49
680
        }
50
        // Const test: apply its effects to the live table once.
51
680
        let test = eval_value(symbols, &elems[0])?;
52
680
        if is_truthy(&test) {
53
408
            if elems.len() == 1 {
54
68
                return compile_expr(ctx, emit, symbols, &test);
55
340
            }
56
340
            return compile_body(ctx, emit, symbols, &elems[1..]);
57
272
        }
58
    }
59
136
    compile_nil(ctx, emit);
60
136
    Ok(())
61
816
}
62

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

            
73
1020
fn rewrite_cond_to_if_chain(clauses: &[Expr]) -> Result<Expr> {
74
1020
    if clauses.is_empty() {
75
340
        return Ok(Expr::Nil);
76
680
    }
77
680
    let head = clauses[0].as_list().ok_or_else(|| {
78
        Error::Compile(format!("COND: clause must be a list, got {:?}", clauses[0]))
79
    })?;
80
680
    if head.is_empty() {
81
        return Err(Error::Compile("COND: empty clause".to_string()));
82
680
    }
83
680
    let test = head[0].clone();
84
680
    let body = match head.len() {
85
        1 => test.clone(),
86
680
        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
680
    let else_branch = rewrite_cond_to_if_chain(&clauses[1..])?;
95
680
    Ok(Expr::List(vec![
96
680
        Expr::Symbol("IF".to_string()),
97
680
        test,
98
680
        body,
99
680
        else_branch,
100
680
    ]))
101
1020
}
102

            
103
546
pub(super) fn compile_cond_for_effect(
104
546
    ctx: &mut CompileContext,
105
546
    emit: &mut FunctionEmitter,
106
546
    symbols: &mut SymbolTable,
107
546
    args: &[Expr],
108
546
) -> Result<()> {
109
546
    for (i, clause) in args.iter().enumerate() {
110
546
        let elems = clause.as_list().ok_or_else(|| {
111
            Error::Compile(format!("COND: clause must be a list, got {clause:?}"))
112
        })?;
113
546
        if elems.is_empty() {
114
            return Err(Error::Compile("COND: empty clause".to_string()));
115
546
        }
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
546
        let test = eval_value(&mut symbols.clone(), &elems[0])?;
120
546
        let test_diverges = super::block_exits::form_diverges(&mut symbols.clone(), &elems[0])?;
121
546
        super::reject_non_boolean_runtime_test(&test, test_diverges)?;
122
546
        if super::is_runtime_test(&test) || test_diverges {
123
546
            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
546
}
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
546
fn compile_cond_runtime_for_effect(
145
546
    ctx: &mut CompileContext,
146
546
    emit: &mut FunctionEmitter,
147
546
    symbols: &mut SymbolTable,
148
546
    args: &[Expr],
149
546
) -> Result<()> {
150
546
    let last = args.len() - 1;
151
546
    let mut open_guards = 0u32;
152
1162
    for (i, clause) in args.iter().enumerate() {
153
1162
        let elems = clause.as_list().ok_or_else(|| {
154
            Error::Compile(format!("COND: clause must be a list, got {clause:?}"))
155
        })?;
156
1162
        if elems.is_empty() {
157
            return Err(Error::Compile("COND: empty clause".to_string()));
158
1162
        }
159
1162
        let test = eval_value(&mut symbols.clone(), &elems[0])?;
160
1162
        let test_diverges = super::block_exits::form_diverges(&mut symbols.clone(), &elems[0])?;
161
1162
        super::reject_non_boolean_runtime_test(&test, test_diverges)?;
162
1162
        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
204
            compile_for_effect(ctx, emit, symbols, &elems[0])?;
166
204
            for _ in 0..open_guards {
167
                emit.block_end();
168
            }
169
204
            return Ok(());
170
958
        }
171
958
        if super::is_runtime_test(&test) {
172
890
            compile_for_stack(ctx, emit, symbols, &elems[0])?;
173
890
            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
890
            open_guards += 1;
177
890
            for expr in &elems[1..] {
178
890
                compile_for_effect(ctx, emit, symbols, expr)?;
179
            }
180
890
            if i != last {
181
548
                emit.else_block();
182
548
            }
183
        } else {
184
            // Const test: apply effects to the live table once.
185
68
            let test = eval_value(symbols, &elems[0])?;
186
68
            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
68
            }
195
        }
196
    }
197
890
    for _ in 0..open_guards {
198
890
        emit.block_end();
199
890
    }
200
342
    Ok(())
201
546
}
202

            
203
136
pub(super) fn cond_form(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
204
272
    for (i, clause) in args.iter().enumerate() {
205
272
        let elems = clause.as_list().ok_or_else(|| {
206
            Error::Compile(format!("COND: clause must be a list, got {clause:?}"))
207
        })?;
208
272
        if elems.is_empty() {
209
            return Err(Error::Compile("COND: empty clause".to_string()));
210
272
        }
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
272
        let test = eval_value(&mut symbols.clone(), &elems[0])?;
217
272
        let test_diverges = super::block_exits::form_diverges(&mut symbols.clone(), &elems[0])?;
218
272
        super::reject_non_boolean_runtime_test(&test, test_diverges)?;
219
272
        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
272
        }
228
        // Const test: apply its effects to the live table exactly once.
229
272
        let test = eval_value(symbols, &elems[0])?;
230
272
        if is_truthy(&test) {
231
136
            if elems.len() == 1 {
232
                return Ok(test);
233
136
            }
234
136
            return super::super::binding::eval_body(symbols, &elems[1..]);
235
136
        }
236
    }
237
    Ok(Expr::Nil)
238
136
}