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
332
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
332
    for arg in args {
55
332
        eval_value(symbols, arg)?;
56
    }
57
332
    Ok(Expr::WasmRuntime(WasmType::Bool))
58
332
}
59

            
60
1743
pub(super) fn print_call(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
61
1743
    for arg in args {
62
1743
        eval_value(symbols, arg)?;
63
    }
64
1743
    Ok(Expr::WasmRuntime(WasmType::Bool))
65
1743
}
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
6889
fn emit_log_text(ctx: &mut CompileContext, emit: &mut FunctionEmitter, text: &str) -> Result<()> {
82
6889
    let data_idx = ctx.add_data(text.as_bytes())?;
83
6889
    let len = text.len() as u32;
84
6889
    emit.memory_init(data_idx, SCRATCH_OFFSET, len);
85
6889
    emit.i32_const(0);
86
6889
    emit.i32_const(SCRATCH_OFFSET as i32);
87
6889
    emit.i32_const(len as i32);
88
6889
    emit.call(ctx.ids.log);
89
6889
    Ok(())
90
6889
}
91

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

            
103
249
pub(super) fn compile_newline(
104
249
    ctx: &mut CompileContext,
105
249
    emit: &mut FunctionEmitter,
106
249
    symbols: &mut SymbolTable,
107
249
    args: &[Expr],
108
249
) -> Result<()> {
109
249
    compile_newline_effect(ctx, emit, symbols, args)?;
110
166
    compile_nil(ctx, emit);
111
166
    Ok(())
112
249
}
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
1494
pub(super) fn compile_print_to_stack(
121
1494
    ctx: &mut CompileContext,
122
1494
    emit: &mut FunctionEmitter,
123
1494
    symbols: &mut SymbolTable,
124
1494
    args: &[Expr],
125
1494
) -> Result<WasmType> {
126
1494
    compile_print_effect(ctx, emit, symbols, args)?;
127
1494
    emit.i32_const(0);
128
1494
    Ok(WasmType::Bool)
129
1494
}
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
83
pub(super) fn compile_debug_to_stack(
143
83
    ctx: &mut CompileContext,
144
83
    emit: &mut FunctionEmitter,
145
83
    symbols: &mut SymbolTable,
146
83
    args: &[Expr],
147
83
) -> Result<WasmType> {
148
83
    compile_debug_effect(ctx, emit, symbols, args)?;
149
83
    emit.i32_const(0);
150
83
    Ok(WasmType::Bool)
151
83
}
152

            
153
4316
pub(in crate::compiler) fn compile_debug_effect(
154
4316
    ctx: &mut CompileContext,
155
4316
    emit: &mut FunctionEmitter,
156
4316
    symbols: &mut SymbolTable,
157
4316
    args: &[Expr],
158
4316
) -> Result<()> {
159
4316
    let msg = join_printed(symbols, args)?;
160
4316
    emit_log_text(ctx, emit, &msg)
161
4316
}
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
2407
pub(in crate::compiler) fn compile_print_effect(
168
2407
    ctx: &mut CompileContext,
169
2407
    emit: &mut FunctionEmitter,
170
2407
    symbols: &mut SymbolTable,
171
2407
    args: &[Expr],
172
2407
) -> Result<()> {
173
2407
    let msg = join_printed(symbols, args)?;
174
2407
    emit_log_text(ctx, emit, &msg)
175
2407
}
176

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

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

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