1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
//! # S-expression multi-line formatting tests
//!
//! Herein are the tests that exercise the wrap-on-overflow branches of
//! [`SExpressible::write_s_expr`](crate::s_expr::SExpressible::write_s_expr).
//! The [`soft_limit`](crate::s_expr::SExpressibleOptions::soft_limit) is
//! deliberately shrunk to force the writer to break content across multiple
//! lines, exposing the arithmetic that predicts whether each form will fit.
//! Every wrap branch — function parameters and body, parameter lists,
//! integer-face lists, binary and ternary keyword forms, and unary forms — has
//! at least one dedicated case.
use crate::;
use assert_eq;
////////////////////////////////////////////////////////////////////////////////
// S-expression multi-line formatting tests. //
////////////////////////////////////////////////////////////////////////////////
/// Render `source` with the given `soft_limit` and assert the resulting
/// multi-line layout exactly matches `expected`. Panics if the writer produces
/// a single line (i.e., the soft-limit boundary was miscalibrated and no wrap
/// occurred).
/// At `soft_limit` 19, `(function [] (add 1 2))` does not fit on one line. The
/// parameter list still fits on the `(function` line, so only the body wraps to
/// its own line at indent 1. This pins down the fits/wraps boundary for
/// [`Function::write_s_expr`](crate::s_expr::SExpressible) — mutants that
/// perturb the subtraction arithmetic around `- 9` or the comparison against
/// `body.size_s_expr` flip this boundary.
/// At `soft_limit` 15, the parameter list `[alpha beta gamma]` does not fit on
/// the `(function ` line, so the writer wraps the parameters onto their own
/// line at indent 1 and splits each parameter onto its own line at indent 2.
/// The body `42` still fits on the closing-bracket line. This pins down the
/// wrap branch of
/// [`<&[Parameter<'_>]>::write_s_expr`](crate::s_expr::SExpressible).
/// At `soft_limit` 13, the body `(add 1 2)` wraps to indent 1 and — since the
/// tab-widened budget at indent 1 is exactly one character short of fitting the
/// ternary form plus its trailing paren — its two subexpressions wrap again to
/// indent 2. This pins down the wrap branch of
/// [`<(A, B, C)>::write_s_expr`](crate::s_expr::SExpressible) at the boundary
/// where `size + indent` straddles the remaining space: a mutant that replaces
/// `+` with `*` would compute `size * 1 = size`, flipping the fit/wrap decision
/// from wrap to fit.
/// At `soft_limit` 14, the body `(neg abcd)` wraps to indent 1 and its single
/// operand wraps again to indent 2. This pins down the wrap branch of
/// [`<(A, B)>::write_s_expr`](crate::s_expr::SExpressible) at the boundary
/// where `size + indent` straddles the remaining space — a mutant that replaces
/// `+` with `*` would compute `size * 1 = size`, flipping the fit/wrap
/// decision. [`Neg`](crate::ast::Neg) of an identifier exercises the unary
/// tuple impl (the lexer folds `-<integer>` into a signed [`Constant`] at parse
/// time, so a literal like `-3` would never reach this path).
/// Recursive wrapping must push the indentation one tab deeper at each level. A
/// left-associated `1 + 2 + 3` forces three nested wraps at `soft_limit` 12:
/// the outer function body, the outer `add`, and the inner `add`. The expected
/// tab depths (1, 2, 3 for the innermost constants) verify
/// [`SExpressibleOptions::increase_indent`](crate::s_expr::SExpressibleOptions::increase_indent)
/// applied iteratively.
/// An integer-face list wraps to one face per line when the enclosing
/// `custom-dice` form does not fit. At `soft_limit` 15, the body, the
/// `custom-dice`, and the faces vector all wrap, producing a cascade that pins
/// down the wrap branch of
/// [`<&[i32]>::write_s_expr`](crate::s_expr::SExpressible).
/// The writer emits `^[start end]` prefixes at every level when
/// [`with_spans`](crate::s_expr::SExpressibleOptions::with_spans) is enabled —
/// at the outer function, at the body, and at each constant. Under a tight
/// [`soft_limit`], wrapping interleaves with the prefixes without corrupting
/// their placement (each prefix sits outside its form's opening paren, with a
/// trailing space).
///
/// [`soft_limit`]: crate::s_expr::SExpressibleOptions::soft_limit
/// Lossless round-trip invariant under tight soft-limits: serializing every
/// parser-originated AST from `test_parse.txt` and reading it back must produce
/// an equivalent function even when the writer wraps across multiple lines.
/// This extends the default-limit coverage in
/// [`test_s_expr_lossless_roundtrip`] by forcing every wrap branch.
///
/// [`test_s_expr_lossless_roundtrip`]: crate::tests::parser::s_expr_format
/// When the function has no parameters (parser-originated functions with no
/// formal parameters produce `None`), the writer renders `[]` on the same line
/// as the keyword — the empty-list sizer returns 2 and the fits-check compares
/// against that. If the sizer were mutated to return 0 or 1, the body would
/// still fit on the line (bringing the overall length in under soft_limit by
/// the mutation's offset), but the space accounting would be one or two
/// characters short for the trailing close-paren. This test checks a
/// tight-budget case where the size prediction is load-bearing.