1
//! `AND` / `OR` short-circuit boolean special forms. Each provides
2
//! three compile paths (effect, stack, runtime short-circuit) plus
3
//! the constant-folding eval path. The `_runtime` helpers handle
4
//! the mixed compile-time-known + runtime-tail case where the prefix
5
//! folds but a later arg is a runtime value.
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_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
/// A runtime `and`/`or` short-circuit chain lowers each operand into an
20
/// `if (result i32)` whose other edge is a 0/1 filler, so every operand value
21
/// must be i32-shaped (`I32` or `Bool`). A ref-typed operand (Ratio / String /
22
/// Pair / …) can't ride that block, and forcing it through would emit a
23
/// malformed module. Reject it with a structured error instead — the strict
24
/// numeric/bool lattice (ADR-0014) does not implicitly truthify ref values.
25
11703
fn require_i32_shaped(op: &str, ty: WasmType) -> Result<()> {
26
11703
    match ty {
27
11620
        WasmType::I32 | WasmType::Bool => Ok(()),
28
83
        other => Err(error_ref_operand(op, other)),
29
    }
30
11703
}
31

            
32
/// Reject a *runtime* operand that isn't an i32-shaped truth value, mirroring
33
/// IF/COND's [`reject_non_boolean_runtime_test`]: nomiscript does NOT do
34
/// implicit truthiness for runtime ref values (a runtime ref may be null, so
35
/// "always truthy" would silently mis-branch). A ref must be tested explicitly
36
/// (e.g. `(null? x)`). Const-foldable values pass through untouched — they
37
/// fold via [`Expr::is_nil`]. Returns the operand's i32-shaped type when it IS a
38
/// valid runtime truth value, so callers can keep routing through the chain.
39
6972
fn classify_runtime_operand(op: &str, value: &Expr) -> Result<Option<WasmType>> {
40
6142
    match value {
41
5976
        Expr::WasmRuntime(t @ (WasmType::I32 | WasmType::Bool))
42
6308
        | Expr::WasmLocal(_, t @ (WasmType::I32 | WasmType::Bool)) => Ok(Some(*t)),
43
332
        Expr::WasmRuntime(ty) | Expr::WasmLocal(_, ty) => Err(error_ref_operand(op, *ty)),
44
332
        _ => Ok(None),
45
    }
46
6972
}
47

            
48
415
fn error_ref_operand(op: &str, ty: WasmType) -> Error {
49
415
    Error::Compile(format!(
50
415
        "{op}: runtime operands must be boolean / i32 truth values, got {ty}; \
51
415
         wrap a ref-typed value in an explicit predicate (e.g. `(null? …)`)"
52
415
    ))
53
415
}
54

            
55
/// Compiles an operand as a 0/1 truth value: a bool as itself, an index — a
56
/// count, never nil — for its effects and then true, matching the constant
57
/// [`Expr::is_nil`] fold.
58
11703
fn compile_truth(
59
11703
    ctx: &mut CompileContext,
60
11703
    emit: &mut FunctionEmitter,
61
11703
    symbols: &mut SymbolTable,
62
11703
    op: &str,
63
11703
    arg: &Expr,
64
11703
) -> Result<()> {
65
11703
    let ty = compile_for_stack(ctx, emit, symbols, arg)?;
66
11703
    require_i32_shaped(op, ty)?;
67
11620
    if ty == WasmType::I32 {
68
498
        emit.drop_value();
69
498
        emit.i32_const(1);
70
11122
    }
71
11620
    Ok(())
72
11703
}
73

            
74
913
pub(super) fn compile_and(
75
913
    ctx: &mut CompileContext,
76
913
    emit: &mut FunctionEmitter,
77
913
    symbols: &mut SymbolTable,
78
913
    args: &[Expr],
79
913
) -> Result<()> {
80
913
    if args.is_empty() {
81
        return compile_expr(ctx, emit, symbols, &Expr::T);
82
913
    }
83

            
84
913
    let mut last = Expr::T;
85
1079
    for (i, arg) in args.iter().enumerate() {
86
1079
        let value = eval_value(symbols, arg)?;
87
1079
        if classify_runtime_operand("AND", &value)?.is_some() {
88
            // Emit ONE merged `if (result T)` chain via the stack path and
89
            // serialize the single result. The old effect-only short-circuit
90
            // helper produced NO value on the early-exit branch (a top-level
91
            // `(and …)` / `(or …)` that short-circuited emitted no output
92
            // entity). The stack path yields a value on every path. Same
93
            // remedy as the IF/COND single-serialization fix.
94
664
            let ty = compile_and_for_stack(ctx, emit, symbols, &args[i..])?;
95
581
            return serialize_stack_to_output(ctx, emit, ty);
96
332
        }
97
332
        if value.is_nil() {
98
            return compile_expr(ctx, emit, symbols, &value);
99
332
        }
100
332
        last = value;
101
    }
102
166
    compile_expr(ctx, emit, symbols, &last)
103
913
}
104

            
105
498
pub(super) fn compile_or(
106
498
    ctx: &mut CompileContext,
107
498
    emit: &mut FunctionEmitter,
108
498
    symbols: &mut SymbolTable,
109
498
    args: &[Expr],
110
498
) -> Result<()> {
111
498
    if args.is_empty() {
112
        compile_nil(ctx, emit);
113
        return Ok(());
114
498
    }
115

            
116
498
    let mut last = Expr::Nil;
117
498
    for (i, arg) in args.iter().enumerate() {
118
498
        let value = eval_value(symbols, arg)?;
119
498
        if classify_runtime_operand("OR", &value)?.is_some() {
120
            // See `compile_and`: route through the value-producing stack path
121
            // so the short-circuit branch still yields a serializable result.
122
415
            let ty = compile_or_for_stack(ctx, emit, symbols, &args[i..])?;
123
415
            return serialize_stack_to_output(ctx, emit, ty);
124
        }
125
        if !value.is_nil() {
126
            return compile_expr(ctx, emit, symbols, &value);
127
        }
128
        last = value;
129
    }
130
    compile_expr(ctx, emit, symbols, &last)
131
498
}
132

            
133
5063
pub(super) fn compile_and_for_stack(
134
5063
    ctx: &mut CompileContext,
135
5063
    emit: &mut FunctionEmitter,
136
5063
    symbols: &mut SymbolTable,
137
5063
    args: &[Expr],
138
5063
) -> Result<WasmType> {
139
5063
    if args.is_empty() {
140
        emit.i32_const(1);
141
        return Ok(WasmType::Bool);
142
5063
    }
143
5063
    if args.len() == 1 {
144
166
        classify_runtime_operand("AND", &eval_value(symbols, &args[0])?)?;
145
166
        compile_truth(ctx, emit, symbols, "AND", &args[0])?;
146
166
        return Ok(WasmType::Bool);
147
4897
    }
148
4897
    compile_truth(ctx, emit, symbols, "AND", &args[0])?;
149
5478
    for arg in &args[1..] {
150
5478
        emit.if_block(BlockType::Result(wasm_encoder::ValType::I32));
151
5478
        compile_truth(ctx, emit, symbols, "AND", arg)?;
152
5395
        emit.else_block();
153
5395
        emit.i32_const(0);
154
5395
        emit.block_end();
155
    }
156
4814
    Ok(WasmType::Bool)
157
5063
}
158

            
159
664
pub(super) fn compile_or_for_stack(
160
664
    ctx: &mut CompileContext,
161
664
    emit: &mut FunctionEmitter,
162
664
    symbols: &mut SymbolTable,
163
664
    args: &[Expr],
164
664
) -> Result<WasmType> {
165
664
    if args.is_empty() {
166
        emit.i32_const(0);
167
        return Ok(WasmType::Bool);
168
664
    }
169
664
    if args.len() == 1 {
170
166
        classify_runtime_operand("OR", &eval_value(symbols, &args[0])?)?;
171
166
        compile_truth(ctx, emit, symbols, "OR", &args[0])?;
172
166
        return Ok(WasmType::Bool);
173
498
    }
174
498
    compile_truth(ctx, emit, symbols, "OR", &args[0])?;
175
498
    for arg in &args[1..] {
176
498
        emit.i32_eqz();
177
498
        emit.if_block(BlockType::Result(wasm_encoder::ValType::I32));
178
498
        compile_truth(ctx, emit, symbols, "OR", arg)?;
179
498
        emit.else_block();
180
498
        emit.i32_const(1);
181
498
        emit.block_end();
182
    }
183
498
    Ok(WasmType::Bool)
184
664
}
185

            
186
83
pub(super) fn compile_and_for_effect(
187
83
    ctx: &mut CompileContext,
188
83
    emit: &mut FunctionEmitter,
189
83
    symbols: &mut SymbolTable,
190
83
    args: &[Expr],
191
83
) -> Result<()> {
192
83
    for (i, arg) in args.iter().enumerate() {
193
83
        let value = eval_value(symbols, arg)?;
194
83
        if classify_runtime_operand("AND", &value)?.is_some() {
195
            // i32-shaped runtime condition: the rest runs only if it's truthy.
196
            compile_truth(ctx, emit, symbols, "AND", arg)?;
197
            emit.if_block(BlockType::Empty);
198
            for remaining in &args[i + 1..] {
199
                compile_for_effect(ctx, emit, symbols, remaining)?;
200
            }
201
            emit.block_end();
202
            return Ok(());
203
        }
204
        if value.is_nil() {
205
            return Ok(());
206
        }
207
    }
208
    Ok(())
209
83
}
210

            
211
83
pub(super) fn compile_or_for_effect(
212
83
    ctx: &mut CompileContext,
213
83
    emit: &mut FunctionEmitter,
214
83
    symbols: &mut SymbolTable,
215
83
    args: &[Expr],
216
83
) -> Result<()> {
217
83
    for (i, arg) in args.iter().enumerate() {
218
83
        let value = eval_value(symbols, arg)?;
219
83
        if classify_runtime_operand("OR", &value)?.is_some() {
220
            // i32-shaped runtime condition: the rest runs only if it's falsy.
221
            compile_truth(ctx, emit, symbols, "OR", arg)?;
222
            emit.i32_eqz();
223
            emit.if_block(BlockType::Empty);
224
            for remaining in &args[i + 1..] {
225
                compile_for_effect(ctx, emit, symbols, remaining)?;
226
            }
227
            emit.block_end();
228
            return Ok(());
229
        }
230
        if !value.is_nil() {
231
            return Ok(());
232
        }
233
    }
234
    Ok(())
235
83
}
236

            
237
4648
pub(super) fn and_form(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
238
4648
    if args.is_empty() {
239
        return Ok(Expr::T);
240
4648
    }
241

            
242
4648
    let mut last = Expr::T;
243
4648
    for arg in args {
244
4648
        let value = eval_value(symbols, arg)?;
245
4648
        if classify_runtime_operand("AND", &value)?.is_some() {
246
4648
            return Ok(Expr::WasmRuntime(WasmType::Bool));
247
        }
248
        if value.is_nil() {
249
            return Ok(value);
250
        }
251
        last = value;
252
    }
253
    Ok(last)
254
4648
}
255

            
256
249
pub(super) fn or_form(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
257
249
    if args.is_empty() {
258
        return Ok(Expr::Nil);
259
249
    }
260

            
261
249
    let mut last = Expr::Nil;
262
249
    for arg in args {
263
249
        let value = eval_value(symbols, arg)?;
264
249
        if classify_runtime_operand("OR", &value)?.is_some() {
265
249
            return Ok(Expr::WasmRuntime(WasmType::Bool));
266
        }
267
        if !value.is_nil() {
268
            return Ok(value);
269
        }
270
        last = value;
271
    }
272
    Ok(last)
273
249
}