Skip to main content

tabnas_css/
value.rs

1/* Copyright (c) 2025 Richard Rodger, MIT License */
2
3//! The AST value model: [`Node`], an insertion-ordered map with a `type`
4//! discriminator, and [`Value`], what a node field can hold.
5//!
6//! The TypeScript port returns plain JavaScript objects and the Go port
7//! returns `map[string]any`. Rust has no such universal value, so this module
8//! supplies one. Insertion order is preserved because the AST is usually read
9//! back as JSON and `{"type": …}` first reads better than a sorted map — the
10//! conformance runners compare key sets, not key order, so nothing depends on
11//! it.
12//!
13//! [`Value::Undefined`] is the one non-obvious variant. It is JavaScript's
14//! `undefined`: a key that EXISTS in insertion order but is absent from the
15//! serialised JSON. The canonical port writes `node.position = { start, end:
16//! undefined }` at construction and fills `end` in later, so a node whose end
17//! is never recorded (a declaration with an empty value, say) serialises as
18//! `{"start":…}` with no `end` at all. Emitting `null` there instead would be
19//! a different AST.
20
21use std::fmt;
22use std::fmt::Write as _;
23
24/// A value held by an AST node field.
25///
26/// Every traversal of a value is ITERATIVE: dropping, cloning, comparing,
27/// writing it as JSON and formatting it for [`std::fmt::Debug`]. An AST is as
28/// deep as the CSS that produced it, so a derived implementation of any of
29/// them would recurse once per level and abort the process on nothing worse
30/// than untrusted input. None of them is derived, for that reason.
31pub enum Value {
32    /// A key that exists but serialises to nothing (JavaScript `undefined`).
33    Undefined,
34    /// JSON `null`.
35    Null,
36    /// JSON `true` / `false`.
37    Bool(bool),
38    /// A JSON number. Integral values serialise without a decimal point.
39    Num(f64),
40    /// A JSON string.
41    Str(String),
42    /// A JSON array.
43    List(Vec<Value>),
44    /// A nested AST node (a JSON object).
45    Node(Node),
46}
47
48impl Value {
49    /// The string held here, if this is a [`Value::Str`].
50    pub fn as_str(&self) -> Option<&str> {
51        match self {
52            Value::Str(s) => Some(s),
53            _ => None,
54        }
55    }
56
57    /// The node held here, if this is a [`Value::Node`].
58    pub fn as_node(&self) -> Option<&Node> {
59        match self {
60            Value::Node(n) => Some(n),
61            _ => None,
62        }
63    }
64
65    /// The list held here, if this is a [`Value::List`].
66    pub fn as_list(&self) -> Option<&[Value]> {
67        match self {
68            Value::List(l) => Some(l),
69            _ => None,
70        }
71    }
72
73    /// This value as JSON. `Undefined` renders as `null` at the top level and
74    /// inside an array — only an object KEY can be dropped entirely, which is
75    /// what JavaScript's `JSON.stringify` does too.
76    pub fn to_json(&self) -> String {
77        let mut out = String::new();
78        write_json(self, &mut out);
79        out
80    }
81}
82
83impl From<&str> for Value {
84    fn from(s: &str) -> Self {
85        Value::Str(s.to_string())
86    }
87}
88
89impl From<String> for Value {
90    fn from(s: String) -> Self {
91        Value::Str(s)
92    }
93}
94
95impl From<Node> for Value {
96    fn from(n: Node) -> Self {
97        Value::Node(n)
98    }
99}
100
101/// An AST node: an insertion-ordered string-keyed map.
102///
103/// Like [`Value`], every traversal of it is iterative rather than derived.
104#[derive(Default)]
105pub struct Node {
106    entries: Vec<(String, Value)>,
107}
108
109impl Node {
110    /// An empty node.
111    pub fn new() -> Self {
112        Node {
113            entries: Vec::new(),
114        }
115    }
116
117    /// The node's `type` discriminator, if it has one.
118    pub fn node_type(&self) -> Option<&str> {
119        self.get("type").and_then(Value::as_str)
120    }
121
122    /// The value at `key`, if the key is present.
123    pub fn get(&self, key: &str) -> Option<&Value> {
124        self.entries.iter().find(|(k, _)| k == key).map(|(_, v)| v)
125    }
126
127    /// A mutable reference to the value at `key`, if the key is present.
128    pub fn get_mut(&mut self, key: &str) -> Option<&mut Value> {
129        self.entries
130            .iter_mut()
131            .find(|(k, _)| k == key)
132            .map(|(_, v)| v)
133    }
134
135    /// Set `key`, keeping its existing position when it is already present
136    /// (as assigning to a JavaScript object property does).
137    pub fn set(&mut self, key: impl Into<String>, value: impl Into<Value>) {
138        let key = key.into();
139        let value = value.into();
140        match self.get_mut(&key) {
141            Some(slot) => *slot = value,
142            None => self.entries.push((key, value)),
143        }
144    }
145
146    /// Append to the list at `key`, creating the list if it is absent.
147    pub fn push_to(&mut self, key: &str, value: Value) {
148        match self.get_mut(key) {
149            Some(Value::List(list)) => list.push(value),
150            Some(slot) => *slot = Value::List(vec![value]),
151            None => self
152                .entries
153                .push((key.to_string(), Value::List(vec![value]))),
154        }
155    }
156
157    /// The entries, in insertion order.
158    pub fn iter(&self) -> impl Iterator<Item = (&str, &Value)> {
159        self.entries.iter().map(|(k, v)| (k.as_str(), v))
160    }
161
162    /// How many keys the node holds, `Undefined` ones included.
163    pub fn len(&self) -> usize {
164        self.entries.len()
165    }
166
167    /// Whether the node holds no keys at all.
168    pub fn is_empty(&self) -> bool {
169        self.entries.is_empty()
170    }
171
172    /// This node as JSON. Keys holding [`Value::Undefined`] are omitted.
173    pub fn to_json(&self) -> String {
174        let mut out = String::new();
175        write_steps(vec![Emit::Node(self)], &mut out, Mode::Json);
176        out
177    }
178}
179
180/// Dismantle a node iteratively when it is dropped.
181///
182/// An AST is as deep as the CSS that produced it, and a derived drop would
183/// recurse once per level — so a stylesheet nested a few thousand rules deep
184/// would abort the process, on nothing worse than untrusted input, at the
185/// moment the tree went out of scope. The rule machine keeps its own stack
186/// for the same reason; this is the other half of that promise.
187impl Drop for Node {
188    fn drop(&mut self) {
189        let mut pending: Vec<Value> = self.entries.drain(..).map(|(_, v)| v).collect();
190        while let Some(value) = pending.pop() {
191            match value {
192                Value::List(items) => pending.extend(items),
193                // `node` is emptied here, so its own drop finds nothing left
194                // to do and does not recurse.
195                Value::Node(mut node) => {
196                    pending.extend(node.entries.drain(..).map(|(_, v)| v));
197                }
198                _ => {}
199            }
200        }
201    }
202}
203
204/// What [`write_steps`] is producing.
205#[derive(Clone, Copy, PartialEq, Eq)]
206enum Mode {
207    /// JSON: an `Undefined` key is dropped, as `JSON.stringify` drops a key
208    /// whose value is `undefined`.
209    Json,
210    /// A debug view: an `Undefined` key is kept and shown, because the point
211    /// of the debug view is to show what is there.
212    Debug,
213}
214
215/// One step of the iterative writer.
216enum Emit<'a> {
217    /// Write this value (pushing more steps for a node or a list).
218    Value(&'a Value),
219    /// Write this node — the same work as `Value(Value::Node(..))`, for a
220    /// node that is not wrapped in a [`Value`].
221    Node(&'a Node),
222    /// Write this object key and the `:` after it.
223    Key(&'a str),
224    /// Write this punctuation verbatim.
225    Raw(&'static str),
226}
227
228/// Write a value as JSON, iteratively.
229///
230/// Iterative for the same reason [`Node`]'s drop is: recursing once per
231/// nesting level turns a deeply nested stylesheet into a stack overflow, and
232/// serialising the AST is the most ordinary thing a caller does with it.
233fn write_json(root: &Value, out: &mut String) {
234    write_steps(vec![Emit::Value(root)], out, Mode::Json)
235}
236
237fn write_steps(mut steps: Vec<Emit<'_>>, out: &mut String, mode: Mode) {
238    while let Some(step) = steps.pop() {
239        match step {
240            Emit::Raw(text) => out.push_str(text),
241            Emit::Key(key) => {
242                write_json_string(key, out);
243                out.push(':');
244            }
245            Emit::Value(Value::Node(node)) | Emit::Node(node) => {
246                out.push('{');
247                steps.push(Emit::Raw("}"));
248                let entries: Vec<(&str, &Value)> = node
249                    .iter()
250                    .filter(|(_, v)| Mode::Debug == mode || !matches!(v, Value::Undefined))
251                    .collect();
252                // Pushed in reverse so they come back off the stack in order.
253                for (i, (key, value)) in entries.iter().enumerate().rev() {
254                    steps.push(Emit::Value(value));
255                    steps.push(Emit::Key(key));
256                    if 0 < i {
257                        steps.push(Emit::Raw(","));
258                    }
259                }
260            }
261            Emit::Value(Value::List(items)) => {
262                out.push('[');
263                steps.push(Emit::Raw("]"));
264                for (i, item) in items.iter().enumerate().rev() {
265                    steps.push(Emit::Value(item));
266                    if 0 < i {
267                        steps.push(Emit::Raw(","));
268                    }
269                }
270            }
271            Emit::Value(Value::Undefined) => out.push_str(match mode {
272                Mode::Json => "null",
273                Mode::Debug => "undefined",
274            }),
275            Emit::Value(Value::Null) => out.push_str("null"),
276            Emit::Value(Value::Bool(b)) => out.push_str(if *b { "true" } else { "false" }),
277            Emit::Value(Value::Num(n)) => write_num(*n, out),
278            Emit::Value(Value::Str(s)) => write_json_string(s, out),
279        }
280    }
281}
282
283/// The debug view is the JSON, with `Undefined` keys shown rather than
284/// dropped.
285///
286/// Not derived, and not a struct-shaped `{:#?}` dump. A derived formatter
287/// recurses once per nesting level, so `format!("{ast:?}")` on a stylesheet a
288/// few thousand rules deep aborts the process — on an AST whose parse, drop
289/// and serialisation are all depth-safe, and in the one operation a caller
290/// reaches for while debugging. This shares the iterative writer instead.
291impl fmt::Debug for Value {
292    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
293        let mut out = String::new();
294        write_steps(vec![Emit::Value(self)], &mut out, Mode::Debug);
295        f.write_str(&out)
296    }
297}
298
299impl fmt::Debug for Node {
300    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
301        let mut out = String::new();
302        write_steps(vec![Emit::Node(self)], &mut out, Mode::Debug);
303        f.write_str(&out)
304    }
305}
306
307/// One step of the iterative deep clone.
308enum CloneStep<'a> {
309    /// Clone this value, pushing more steps for a node or a list.
310    Enter(&'a Value),
311    /// Take the last `n` cloned values off the output and make a node of
312    /// them, under this node's keys.
313    CloseNode(&'a Node),
314    /// Take the last `n` cloned values off the output and make a list.
315    CloseList(usize),
316}
317
318/// A deep clone, iteratively.
319///
320/// A derived `Clone` recurses once per nesting level. Cloning a parsed AST is
321/// an ordinary thing to do, so it is depth-safe here like everything else.
322fn clone_value(root: &Value) -> Value {
323    let mut steps = vec![CloneStep::Enter(root)];
324    let mut done: Vec<Value> = Vec::new();
325    while let Some(step) = steps.pop() {
326        match step {
327            // Children are pushed after the close step, so they pop first and
328            // land on `done` in order, before the close reads them back.
329            CloneStep::Enter(Value::Node(node)) => {
330                steps.push(CloneStep::CloseNode(node));
331                for (_, value) in node.entries.iter().rev() {
332                    steps.push(CloneStep::Enter(value));
333                }
334            }
335            CloneStep::Enter(Value::List(items)) => {
336                steps.push(CloneStep::CloseList(items.len()));
337                for item in items.iter().rev() {
338                    steps.push(CloneStep::Enter(item));
339                }
340            }
341            CloneStep::Enter(leaf) => done.push(match leaf {
342                Value::Undefined => Value::Undefined,
343                Value::Null => Value::Null,
344                Value::Bool(b) => Value::Bool(*b),
345                Value::Num(n) => Value::Num(*n),
346                Value::Str(s) => Value::Str(s.clone()),
347                // Handled by the arms above.
348                Value::List(_) | Value::Node(_) => unreachable!(),
349            }),
350            CloneStep::CloseNode(node) => {
351                let values = done.split_off(done.len() - node.entries.len());
352                let entries = node
353                    .entries
354                    .iter()
355                    .map(|(k, _)| k.clone())
356                    .zip(values)
357                    .collect();
358                done.push(Value::Node(Node { entries }));
359            }
360            CloneStep::CloseList(n) => {
361                let items = done.split_off(done.len() - n);
362                done.push(Value::List(items));
363            }
364        }
365    }
366    done.pop().expect("the clone produced no value")
367}
368
369impl Clone for Value {
370    fn clone(&self) -> Value {
371        clone_value(self)
372    }
373}
374
375impl Clone for Node {
376    fn clone(&self) -> Node {
377        let mut out = Node::new();
378        // One level by hand, then the iterative walk for each field, so that
379        // cloning a node never wraps it in a temporary Value.
380        for (key, value) in &self.entries {
381            out.entries.push((key.clone(), clone_value(value)));
382        }
383        out
384    }
385}
386
387/// An equality walk, iteratively, for the same reason as the rest.
388fn values_eq(a: &Value, b: &Value) -> bool {
389    let mut pending: Vec<(&Value, &Value)> = vec![(a, b)];
390    while let Some((a, b)) = pending.pop() {
391        match (a, b) {
392            (Value::Undefined, Value::Undefined) | (Value::Null, Value::Null) => {}
393            (Value::Bool(a), Value::Bool(b)) if a == b => {}
394            (Value::Num(a), Value::Num(b)) if a == b => {}
395            (Value::Str(a), Value::Str(b)) if a == b => {}
396            (Value::List(a), Value::List(b)) if a.len() == b.len() => {
397                pending.extend(a.iter().zip(b.iter()));
398            }
399            (Value::Node(a), Value::Node(b)) if a.entries.len() == b.entries.len() => {
400                for ((ak, av), (bk, bv)) in a.entries.iter().zip(b.entries.iter()) {
401                    if ak != bk {
402                        return false;
403                    }
404                    pending.push((av, bv));
405                }
406            }
407            _ => return false,
408        }
409    }
410    true
411}
412
413impl PartialEq for Value {
414    fn eq(&self, other: &Value) -> bool {
415        values_eq(self, other)
416    }
417}
418
419impl PartialEq for Node {
420    fn eq(&self, other: &Node) -> bool {
421        self.entries.len() == other.entries.len()
422            && self
423                .entries
424                .iter()
425                .zip(other.entries.iter())
426                .all(|((ak, _), (bk, _))| ak == bk)
427            && self
428                .entries
429                .iter()
430                .zip(other.entries.iter())
431                .all(|((_, av), (_, bv))| values_eq(av, bv))
432    }
433}
434
435/// Write a JSON number. Integral values lose the `.0` Rust would print, so
436/// a line number reads as `2` and not `2.0`.
437fn write_num(n: f64, out: &mut String) {
438    if n.is_finite() && n == n.trunc() && n.abs() < 1e15 {
439        let _ = write!(out, "{}", n as i64);
440    } else if n.is_finite() {
441        let _ = write!(out, "{}", n);
442    } else {
443        // JSON has no infinity or NaN; JavaScript's JSON.stringify writes
444        // null for both.
445        out.push_str("null");
446    }
447}
448
449/// Write a JSON string literal, escaping exactly what `JSON.stringify` does.
450fn write_json_string(s: &str, out: &mut String) {
451    out.push('"');
452    for c in s.chars() {
453        match c {
454            '"' => out.push_str("\\\""),
455            '\\' => out.push_str("\\\\"),
456            '\n' => out.push_str("\\n"),
457            '\r' => out.push_str("\\r"),
458            '\t' => out.push_str("\\t"),
459            '\u{08}' => out.push_str("\\b"),
460            '\u{0c}' => out.push_str("\\f"),
461            c if (c as u32) < 0x20 => {
462                let _ = write!(out, "\\u{:04x}", c as u32);
463            }
464            c => out.push(c),
465        }
466    }
467    out.push('"');
468}