1
use super::super::context::CompileContext;
2
use super::super::emit::FunctionEmitter;
3
use super::super::expr::{compile_nil, eval_value, format_expr};
4
use super::NativeSpec;
5
use crate::ast::{Expr, WasmType};
6
use crate::error::Result;
7
use crate::runtime::SymbolTable;
8

            
9
pub(super) const NATIVES: &[NativeSpec] = &[
10
    NativeSpec {
11
        name: "DEBUG",
12
        eval: debug_call,
13
        stack: Some(compile_debug_to_stack),
14
        effect: Some(compile_debug),
15
    },
16
    NativeSpec {
17
        name: "PRINT",
18
        eval: print_call,
19
        stack: Some(compile_print_to_stack),
20
        effect: Some(compile_print),
21
    },
22
    NativeSpec {
23
        name: "DISPLAY",
24
        eval: print_call,
25
        stack: Some(compile_print_to_stack),
26
        effect: Some(compile_print),
27
    },
28
    NativeSpec {
29
        name: "NEWLINE",
30
        eval: newline_call,
31
        stack: Some(compile_newline_to_stack),
32
        effect: Some(compile_newline),
33
    },
34
];
35

            
36
const SCRATCH_OFFSET: u32 = 0;
37

            
38
// The textual-output natives have a side effect (host `log`), so their eval
39
// handlers must NOT fold to a pure `Expr::Nil` — a constant return lets the
40
// `and`/`or` short-circuit folder (and effect-position constant elision) drop
41
// the call entirely, silently losing the output. Returning a `WasmRuntime`
42
// truth-value placeholder forces every consumer down the runtime path so the
43
// side effect is always emitted. The runtime value is nil-typed (`Bool`,
44
// serializing as Nil) — these forms evaluate to nil.
45

            
46
272
pub(super) fn debug_call(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
47
    // Eval is pure classification — it must NOT perform I/O. The type-inference
48
    // probe (`infer_wasm_type`) evaluates pure-native forms on a cloned table
49
    // for sizing; logging here would emit compile-time output (and leak the
50
    // message) during mere type analysis. The actual `[script]` log is emitted
51
    // by the codegen handler (`compile_debug_effect` → host `log`), exactly
52
    // like PRINT / NEWLINE. Eval only validates args and yields the nil-typed
53
    // runtime placeholder.
54
272
    for arg in args {
55
272
        eval_value(symbols, arg)?;
56
    }
57
272
    Ok(Expr::WasmRuntime(WasmType::Bool))
58
272
}
59

            
60
1020
pub(super) fn print_call(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
61
1020
    for arg in args {
62
1020
        eval_value(symbols, arg)?;
63
    }
64
1020
    Ok(Expr::WasmRuntime(WasmType::Bool))
65
1020
}
66

            
67
pub(super) fn newline_call(_symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
68
    if !args.is_empty() {
69
        return Err(crate::error::Error::Arity {
70
            name: "NEWLINE".to_string(),
71
            expected: 0,
72
            actual: args.len(),
73
        });
74
    }
75
    Ok(Expr::WasmRuntime(WasmType::Bool))
76
}
77

            
78
/// Emits a `log` call for the joined printed forms, then leaves nil — the
79
/// textual-output natives share the host `log` channel (the only output
80
/// import) and all evaluate to nil.
81
5644
fn emit_log_text(ctx: &mut CompileContext, emit: &mut FunctionEmitter, text: &str) -> Result<()> {
82
5644
    let data_idx = ctx.add_data(text.as_bytes())?;
83
5644
    let len = text.len() as u32;
84
5644
    emit.memory_init(data_idx, SCRATCH_OFFSET, len);
85
5644
    emit.i32_const(0);
86
5644
    emit.i32_const(SCRATCH_OFFSET as i32);
87
5644
    emit.i32_const(len as i32);
88
5644
    emit.call(ctx.ids.log);
89
5644
    Ok(())
90
5644
}
91

            
92
272
pub(super) fn compile_print(
93
272
    ctx: &mut CompileContext,
94
272
    emit: &mut FunctionEmitter,
95
272
    symbols: &mut SymbolTable,
96
272
    args: &[Expr],
97
272
) -> Result<()> {
98
272
    compile_print_effect(ctx, emit, symbols, args)?;
99
272
    compile_nil(ctx, emit);
100
272
    Ok(())
101
272
}
102

            
103
204
pub(super) fn compile_newline(
104
204
    ctx: &mut CompileContext,
105
204
    emit: &mut FunctionEmitter,
106
204
    symbols: &mut SymbolTable,
107
204
    args: &[Expr],
108
204
) -> Result<()> {
109
204
    compile_newline_effect(ctx, emit, symbols, args)?;
110
136
    compile_nil(ctx, emit);
111
136
    Ok(())
112
204
}
113

            
114
// Stack-position handlers: emit the `log` side effect, then leave the falsy
115
// i31 that the stack convention uses for nil (typed `Bool` so it serializes as
116
// Nil). Used when these forms appear as a subexpression — e.g. an `and`/`or`
117
// operand or a `let` init — so the value is on the operand stack AND the side
118
// effect is emitted. Agrees with the eval handlers' `WasmRuntime(Bool)`.
119

            
120
1224
pub(super) fn compile_print_to_stack(
121
1224
    ctx: &mut CompileContext,
122
1224
    emit: &mut FunctionEmitter,
123
1224
    symbols: &mut SymbolTable,
124
1224
    args: &[Expr],
125
1224
) -> Result<WasmType> {
126
1224
    compile_print_effect(ctx, emit, symbols, args)?;
127
1224
    emit.i32_const(0);
128
1224
    Ok(WasmType::Bool)
129
1224
}
130

            
131
pub(super) fn compile_newline_to_stack(
132
    ctx: &mut CompileContext,
133
    emit: &mut FunctionEmitter,
134
    symbols: &mut SymbolTable,
135
    args: &[Expr],
136
) -> Result<WasmType> {
137
    compile_newline_effect(ctx, emit, symbols, args)?;
138
    emit.i32_const(0);
139
    Ok(WasmType::Bool)
140
}
141

            
142
68
pub(super) fn compile_debug_to_stack(
143
68
    ctx: &mut CompileContext,
144
68
    emit: &mut FunctionEmitter,
145
68
    symbols: &mut SymbolTable,
146
68
    args: &[Expr],
147
68
) -> Result<WasmType> {
148
68
    compile_debug_effect(ctx, emit, symbols, args)?;
149
68
    emit.i32_const(0);
150
68
    Ok(WasmType::Bool)
151
68
}
152

            
153
3536
pub(in crate::compiler) fn compile_debug_effect(
154
3536
    ctx: &mut CompileContext,
155
3536
    emit: &mut FunctionEmitter,
156
3536
    symbols: &mut SymbolTable,
157
3536
    args: &[Expr],
158
3536
) -> Result<()> {
159
3536
    let msg = join_printed(symbols, args)?;
160
3536
    emit_log_text(ctx, emit, &msg)
161
3536
}
162

            
163
/// Effect-position PRINT / DISPLAY: emit the `log` side effect and leave NO
164
/// value on the stack (effect position discards). The value-position handler
165
/// `compile_print` adds the trailing nil; routing the two apart is why
166
/// `effect.rs` must dispatch these names explicitly (mirrors DEBUG).
167
1972
pub(in crate::compiler) fn compile_print_effect(
168
1972
    ctx: &mut CompileContext,
169
1972
    emit: &mut FunctionEmitter,
170
1972
    symbols: &mut SymbolTable,
171
1972
    args: &[Expr],
172
1972
) -> Result<()> {
173
1972
    let msg = join_printed(symbols, args)?;
174
1972
    emit_log_text(ctx, emit, &msg)
175
1972
}
176

            
177
204
pub(in crate::compiler) fn compile_newline_effect(
178
204
    ctx: &mut CompileContext,
179
204
    emit: &mut FunctionEmitter,
180
204
    _symbols: &mut SymbolTable,
181
204
    args: &[Expr],
182
204
) -> Result<()> {
183
204
    if !args.is_empty() {
184
68
        return Err(crate::error::Error::Arity {
185
68
            name: "NEWLINE".to_string(),
186
68
            expected: 0,
187
68
            actual: args.len(),
188
68
        });
189
136
    }
190
136
    emit_log_text(ctx, emit, "\n")
191
204
}
192

            
193
/// Evaluate args and join their printed forms with a space — the shared
194
/// payload builder for the textual-output natives.
195
5508
fn join_printed(symbols: &mut SymbolTable, args: &[Expr]) -> Result<String> {
196
5508
    let resolved: std::result::Result<Vec<_>, _> =
197
5916
        args.iter().map(|a| eval_value(symbols, a)).collect();
198
5508
    Ok(resolved?
199
5508
        .iter()
200
5508
        .map(format_expr)
201
5508
        .collect::<Vec<_>>()
202
5508
        .join(" "))
203
5508
}
204

            
205
680
pub(super) fn compile_debug(
206
680
    ctx: &mut CompileContext,
207
680
    emit: &mut FunctionEmitter,
208
680
    symbols: &mut SymbolTable,
209
680
    args: &[Expr],
210
680
) -> Result<()> {
211
680
    compile_debug_effect(ctx, emit, symbols, args)?;
212
680
    compile_nil(ctx, emit);
213
680
    Ok(())
214
680
}