Skip to main content

interscript_rs/
item.rs

1//! The item model — mirrors `Interscript::Node::Item` as produced by
2//! `Interscript::Isc::NodeAdapter` from a parsed map.
3
4use crate::stdlib::Prim;
5
6/// A `sub`-rule item tree (`from`, `to`, constraint items).
7#[derive(Debug, Clone, PartialEq)]
8pub enum Item {
9    /// Literal string (Item::String).
10    Str(String),
11    /// The `none` keyword — empty string.
12    None,
13    /// Zero-width / class primitive (NodeAdapter: Alias(stdlib symbol)).
14    Prim(Prim),
15    /// upcase/downcase function item. Only usable as a `sub` target and
16    /// only as Upcase/Downcase (the Ruby adapter raises on the rest).
17    Func(FuncName),
18    /// Alias reference: `map` for `dep_alias.name` qualified refs.
19    Alias { map: Option<String>, name: String },
20    /// capture(...). `id` is the 1-based regex group number, assigned
21    /// by the builder in lookaround assembly order
22    /// (before, not_before, from, not_after, after).
23    CaptureGroup { id: usize, inner: Box<Item> },
24    /// ref(N) — backreference to the Nth capture group.
25    CaptureRef(usize),
26    /// maybe(...)
27    Maybe(Box<Item>),
28    /// some(...)
29    Some(Box<Item>),
30    /// any("lo".."hi") — inclusive codepoint range.
31    Range(char, char),
32    /// any(...) — char set / alternation list (entries may be nested
33    /// Items: ranges, sets, primitives, alias refs).
34    Set(Vec<Item>),
35    /// Concatenation (`+` / whitespace joins). Also Item::Group.
36    Concat(Vec<Item>),
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40pub enum FuncName {
41    Upcase,
42    Downcase,
43}
44
45impl Item {
46    /// `Interscript::Node::Item::Group`-style concatenation with
47    /// flattening and None-elimination (`Items::Concat.from_parts`).
48    pub fn concat_from_parts(parts: Vec<Item>) -> Item {
49        let mut flat: Vec<Item> = Vec::new();
50        for p in parts {
51            match p {
52                Item::Concat(inner) => flat.extend(inner),
53                Item::None => {}
54                other => flat.push(other),
55            }
56        }
57        match flat.len() {
58            0 => Item::None,
59            1 => flat.pop().unwrap(),
60            _ => Item::Concat(flat),
61        }
62    }
63
64    /// `first_string` collapse (Ruby `nth_string` without the fiber
65    /// selection): what this item yields as a single replacement
66    /// string. Any → first entry; Range → lo. Alias/Prim/CaptureRef
67    /// keep identity (resolved later, like build_item).
68    pub fn first_string(&self) -> Item {
69        match self {
70            Item::Set(entries) => entries
71                .first()
72                .map(|e| e.first_string())
73                .unwrap_or(Item::None),
74            Item::Range(lo, _) => Item::Str(lo.to_string()),
75            Item::Maybe(inner) | Item::Some(inner) => inner.first_string(),
76            Item::CaptureGroup { inner, .. } => inner.first_string(),
77            Item::Concat(parts) => Item::Concat(parts.iter().map(|p| p.first_string()).collect()),
78            other => other.clone(),
79        }
80    }
81
82    /// `max_length` for the deterministic parallel sort. Primitives
83    /// count as 1 (0 for none), mirroring `Node::Item::Alias#max_length`.
84    pub fn max_length(&self) -> usize {
85        match self {
86            Item::Str(s) => s.chars().count(),
87            Item::None => 0,
88            Item::Prim(_) | Item::Func(_) | Item::Alias { .. } | Item::CaptureRef(_) => 1,
89            Item::CaptureGroup { inner, .. }
90            | Item::Maybe(inner)
91            | Item::Some(inner) => inner.max_length(),
92            Item::Range(_, _) => 1,
93            Item::Set(entries) => entries.iter().map(|e| e.max_length()).max().unwrap_or(0),
94            Item::Concat(parts) => parts.iter().map(|p| p.max_length()).sum(),
95        }
96    }
97}