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
//! Shared formatting helpers for compact Links Notation records.
use format_indented_ordered;
/// A name the codec's key escape leaves alone, so the probe record's shape in
/// [`format_lino_value`] is known exactly.
const PROBE: &str = "v";
/// Quote one value exactly the way Links Notation quotes it, and change nothing
/// else about it.
///
/// Links Notation escapes a quote by *doubling* it and picks a delimiter the
/// value does not already carry; it has no backslash escape at all. Renderers
/// that hand-rolled a C-style one therefore emitted documents a reader ends
/// early or rejects outright, which is what [`format_lino_record`] already
/// exists to prevent — but that helper only writes a flat two-level record, so
/// a nested tree had no way to reach it and kept its own escaper.
///
/// The codec's always-quoting encoder is private, and the grammar crate's is
/// too, so the rule is borrowed from the one public function that applies it:
/// a single-field record is formatted and the field taken back off it. That
/// costs an allocation per value and buys the property that matters — this
/// cannot drift from the notation, because it *is* the notation's encoder.
///
/// Prefer this whenever the grammar is the document's only reader. Use
/// [`format_lino_value`] when `seed::parser` reads the document back.
/// Quote one value for a reader that reads a document one line at a time.
///
/// This is [`format_lino_value_verbatim`] with [`sanitize_lino_value`] in front
/// of it, and the sanitizing is the whole difference. `seed::parser` is
/// line-based — `for line in text.lines()` — so it structurally cannot see a
/// value that spans lines, and every document it reads back must keep each
/// value on the line it opened on.
///
/// That escape is not free: it is the grammar, not `seed::parser`, that defines
/// the notation, and the grammar carries a raw newline inside a quoted value
/// losslessly, nesting included (`experiments/issue_715_nested_newline_probe.rs`).
/// A sanitized value therefore reads back through the grammar with a literal
/// backslash and `n` where the newline was. The two helpers collapse into one
/// the day `seed::parser` can read a value that spans lines.
/// Write one `name "value"` node at `indent` spaces, the way Links Notation
/// quotes. A node with no value is a bare group header.
/// Escape a value so that [`crate::seed::parser::unescape_value`] decodes back
/// to exactly this input. Use it only where the value is *quoted* and read back.
///
/// The backslash goes first, and that ordering is the whole correctness
/// argument: this function *introduces* backslashes, so escaping it afterwards
/// would escape its own output. Escaping it at all is what makes the pair
/// invertible. A value is otherwise free to contain a backslash — it is content,
/// not syntax — and an unescaped one arrives at the decoder looking exactly like
/// an escape this function wrote. That is not hypothetical for the subject of
/// issue #715: rewriting `println!("\n")` wrote the value back verbatim, and
/// reading it returned a real newline in place of the two characters the Rust
/// source actually holds.
///
/// Prefer [`flatten_lino_value`] when nothing decodes the value.
/// Keep a value on one line in a document that is *rendered* rather than read
/// back, changing nothing else about it.
///
/// This is what [`sanitize_lino_value`] used to be, and the split is not
/// cosmetic: the two have different correctness conditions, because their
/// documents have different readers.
///
/// A quoted value is decoded by `unescape_value`, so its escape must be
/// invertible — the backslash has to be escaped, or content is read as syntax.
/// These callers instead interpolate the value *unquoted* (`step_0 {kind}
/// {payload}`, `text {statement}`), and `seed::parser` only unescapes a value it
/// found a delimiter around. So nothing decodes these, and escaping the
/// backslash would not survive a round trip — it would simply be shown, turning
/// a summary of `println!("\n")` into a summary of `println!("\\n")` and
/// misreporting the source it exists to describe.
///
/// What these callers do need is the line: the document is line-oriented, and a
/// raw newline in a value would silently invent a record. That is the one job
/// left here.