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