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

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

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

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

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

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

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

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