1
//! `(debug ...)` native — emits a log message + returns nil. The
2
//! effect-position dispatch path skips the trailing nil push.
3
//! Also covers PRINT / DISPLAY / NEWLINE (textual output via the host
4
//! `log` channel), previously phantom natives with no codegen handler.
5

            
6
use super::common::{compile_and_validate, compile_expect_error, wrap_with_runtime_ratio};
7

            
8
#[test]
9
1
fn debug_no_args() {
10
1
    compile_and_validate("(debug)");
11
1
}
12

            
13
#[test]
14
1
fn debug_with_string() {
15
1
    compile_and_validate("(debug \"hello\")");
16
1
}
17

            
18
#[test]
19
1
fn debug_with_multiple_args() {
20
1
    compile_and_validate("(debug \"x =\" 42 \"y =\" 1/2)");
21
1
}
22

            
23
/// Debug at value position — the form's return is nil. Use it as the
24
/// last expression so `compile_program` routes through `compile_expr`
25
/// which dispatches the `compile` (not `effect`) variant.
26
#[test]
27
1
fn debug_at_value_position() {
28
1
    compile_and_validate("(debug \"trace\") (debug 1)");
29
1
}
30

            
31
/// Debug in effect position via `begin`. The first form is at effect
32
/// position; the second is the program's last and runs through the
33
/// value path.
34
#[test]
35
1
fn debug_inside_begin() {
36
1
    compile_and_validate("(begin (debug \"step1\") (debug \"step2\"))");
37
1
}
38

            
39
/// Runtime-valued arg: the eval falls back to format the runtime
40
/// placeholder, codegen embeds the formatted form text in the log
41
/// payload. Verifies the path handles non-constant args without
42
/// panicking.
43
#[test]
44
1
fn debug_with_runtime_arg() {
45
1
    compile_and_validate(&wrap_with_runtime_ratio("(debug \"X =\" X)"));
46
1
}
47

            
48
#[test]
49
1
fn print_with_string() {
50
1
    compile_and_validate("(print \"hello\")");
51
1
}
52

            
53
#[test]
54
1
fn display_with_value() {
55
1
    compile_and_validate("(display 42)");
56
1
}
57

            
58
#[test]
59
1
fn print_at_value_position_is_nil() {
60
    // PRINT returns nil; used as a defun body tail it must produce a value.
61
1
    compile_and_validate("(defun emit (x) (print x)) (emit 1)");
62
1
}
63

            
64
#[test]
65
1
fn newline_no_args() {
66
1
    compile_and_validate("(newline)");
67
1
}
68

            
69
#[test]
70
1
fn newline_rejects_args() {
71
1
    let err = compile_expect_error("(newline 1)");
72
1
    assert!(err.contains("NEWLINE"), "got: {err}");
73
1
}
74

            
75
#[test]
76
1
fn print_then_newline_in_begin() {
77
1
    compile_and_validate("(begin (print \"line\") (newline))");
78
1
}
79

            
80
/// Effect-position regression: a `print` whose value is discarded (first form
81
/// of a `begin`) must still emit its `log` side effect. Before the effect-
82
/// dispatch fix, PRINT/DISPLAY/NEWLINE fell through to a bare `eval_value` in
83
/// `compile_for_effect` and emitted NO wasm — the side effect was silently
84
/// dropped. The emitted wasm carrying a `log` call is the contract here.
85
#[test]
86
1
fn print_side_effect_survives_effect_position() {
87
1
    let wasm = compile_and_validate("(begin (print \"x\") 1)");
88
    // Two `log`-import call sites would mean DEBUG; one means the print's
89
    // side effect was emitted. We only assert the module is non-trivially
90
    // larger than the same program without the print (the print emitted code).
91
1
    let baseline = compile_and_validate("(begin 1)");
92
1
    assert!(
93
1
        wasm.len() > baseline.len(),
94
        "print side effect was dropped: {} vs baseline {}",
95
        wasm.len(),
96
        baseline.len()
97
    );
98
1
}
99

            
100
/// `print` as an `and` operand must still EMIT its side effect. Round-2
101
/// adversarial-review regression: the eval handler used to return a pure
102
/// `Expr::Nil`, so `and`'s short-circuit folder treated `(print …)` as a
103
/// side-effect-free constant and dropped it — `(and (print …) #t)` folded to
104
/// `#t` and emitted no `log`. The eval handlers now return a non-foldable
105
/// `WasmRuntime(Bool)` placeholder + carry a stack handler, so the side effect
106
/// survives. Asserted via wasm size: the payload-bearing variant must be
107
/// larger than the same `and` with no print.
108
#[test]
109
1
fn print_side_effect_survives_and_operand() {
110
1
    let with = compile_and_validate("(and (print \"xxxxxxxx\") #t)");
111
1
    let without = compile_and_validate("(and #t #t)");
112
1
    assert!(
113
1
        with.len() > without.len(),
114
        "print side effect dropped inside (and ...): {} vs {}",
115
        with.len(),
116
        without.len()
117
    );
118
1
}
119

            
120
/// Same latent bug existed for the pre-existing DEBUG native — `(and (debug …)
121
/// #t)` dropped the debug. Fixed by the same eval-handler change; lock it.
122
#[test]
123
1
fn debug_side_effect_survives_and_operand() {
124
1
    let with = compile_and_validate("(and (debug \"xxxxxxxx\") #t)");
125
1
    let without = compile_and_validate("(and #t #t)");
126
1
    assert!(
127
1
        with.len() > without.len(),
128
        "debug side effect dropped inside (and ...): {} vs {}",
129
        with.len(),
130
        without.len()
131
    );
132
1
}
133

            
134
/// `print` as a `let` init (stack position) must compile via the new stack
135
/// handler — previously errored "native function 'PRINT' cannot produce stack
136
/// value".
137
#[test]
138
1
fn print_in_let_value_position_compiles() {
139
1
    compile_and_validate("(let ((x (print \"x\"))) x)");
140
1
}
141

            
142
/// `display` in effect position, same contract as print.
143
#[test]
144
1
fn display_side_effect_survives_effect_position() {
145
1
    compile_and_validate("(begin (display 42) 1)");
146
1
}
147

            
148
#[test]
149
1
fn print_with_runtime_arg() {
150
1
    compile_and_validate(&wrap_with_runtime_ratio("(print X)"));
151
1
}