Lines
84.85 %
Functions
17.33 %
Branches
100 %
use super::super::context::CompileContext;
use super::super::emit::FunctionEmitter;
use super::super::expr::{compile_nil, eval_value, format_expr};
use super::NativeSpec;
use crate::ast::{Expr, WasmType};
use crate::error::Result;
use crate::runtime::SymbolTable;
pub(super) const NATIVES: &[NativeSpec] = &[
NativeSpec {
name: "DEBUG",
eval: debug_call,
stack: Some(compile_debug_to_stack),
effect: Some(compile_debug),
},
name: "PRINT",
eval: print_call,
stack: Some(compile_print_to_stack),
effect: Some(compile_print),
name: "DISPLAY",
name: "NEWLINE",
eval: newline_call,
stack: Some(compile_newline_to_stack),
effect: Some(compile_newline),
];
const SCRATCH_OFFSET: u32 = 0;
// The textual-output natives have a side effect (host `log`), so their eval
// handlers must NOT fold to a pure `Expr::Nil` — a constant return lets the
// `and`/`or` short-circuit folder (and effect-position constant elision) drop
// the call entirely, silently losing the output. Returning a `WasmRuntime`
// truth-value placeholder forces every consumer down the runtime path so the
// side effect is always emitted. The runtime value is nil-typed (`Bool`,
// serializing as Nil) — these forms evaluate to nil.
pub(super) fn debug_call(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
// Eval is pure classification — it must NOT perform I/O. The type-inference
// probe (`infer_wasm_type`) evaluates pure-native forms on a cloned table
// for sizing; logging here would emit compile-time output (and leak the
// message) during mere type analysis. The actual `[script]` log is emitted
// by the codegen handler (`compile_debug_effect` → host `log`), exactly
// like PRINT / NEWLINE. Eval only validates args and yields the nil-typed
// runtime placeholder.
for arg in args {
eval_value(symbols, arg)?;
}
Ok(Expr::WasmRuntime(WasmType::Bool))
pub(super) fn print_call(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
pub(super) fn newline_call(_symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
if !args.is_empty() {
return Err(crate::error::Error::Arity {
name: "NEWLINE".to_string(),
expected: 0,
actual: args.len(),
});
/// Emits a `log` call for the joined printed forms, then leaves nil — the
/// textual-output natives share the host `log` channel (the only output
/// import) and all evaluate to nil.
fn emit_log_text(ctx: &mut CompileContext, emit: &mut FunctionEmitter, text: &str) -> Result<()> {
let data_idx = ctx.add_data(text.as_bytes())?;
let len = text.len() as u32;
emit.memory_init(data_idx, SCRATCH_OFFSET, len);
emit.i32_const(0);
emit.i32_const(SCRATCH_OFFSET as i32);
emit.i32_const(len as i32);
emit.call(ctx.ids.log);
Ok(())
pub(super) fn compile_print(
ctx: &mut CompileContext,
emit: &mut FunctionEmitter,
symbols: &mut SymbolTable,
args: &[Expr],
) -> Result<()> {
compile_print_effect(ctx, emit, symbols, args)?;
compile_nil(ctx, emit);
pub(super) fn compile_newline(
compile_newline_effect(ctx, emit, symbols, args)?;
// Stack-position handlers: emit the `log` side effect, then leave the falsy
// i31 that the stack convention uses for nil (typed `Bool` so it serializes as
// Nil). Used when these forms appear as a subexpression — e.g. an `and`/`or`
// operand or a `let` init — so the value is on the operand stack AND the side
// effect is emitted. Agrees with the eval handlers' `WasmRuntime(Bool)`.
pub(super) fn compile_print_to_stack(
) -> Result<WasmType> {
Ok(WasmType::Bool)
pub(super) fn compile_newline_to_stack(
pub(super) fn compile_debug_to_stack(
compile_debug_effect(ctx, emit, symbols, args)?;
pub(in crate::compiler) fn compile_debug_effect(
let msg = join_printed(symbols, args)?;
emit_log_text(ctx, emit, &msg)
/// Effect-position PRINT / DISPLAY: emit the `log` side effect and leave NO
/// value on the stack (effect position discards). The value-position handler
/// `compile_print` adds the trailing nil; routing the two apart is why
/// `effect.rs` must dispatch these names explicitly (mirrors DEBUG).
pub(in crate::compiler) fn compile_print_effect(
pub(in crate::compiler) fn compile_newline_effect(
_symbols: &mut SymbolTable,
emit_log_text(ctx, emit, "\n")
/// Evaluate args and join their printed forms with a space — the shared
/// payload builder for the textual-output natives.
fn join_printed(symbols: &mut SymbolTable, args: &[Expr]) -> Result<String> {
let resolved: std::result::Result<Vec<_>, _> =
args.iter().map(|a| eval_value(symbols, a)).collect();
Ok(resolved?
.iter()
.map(format_expr)
.collect::<Vec<_>>()
.join(" "))
pub(super) fn compile_debug(