Skip to main content

brink_format/
line.rs

1/// The content of a single output line — either a plain string or a template
2/// with interpolation slots and plural selects.
3#[derive(Debug, Clone, PartialEq)]
4pub enum LineContent {
5    Plain(String),
6    Template(LineTemplate),
7}
8
9bitflags::bitflags! {
10    /// Whitespace characteristics of a line, precomputed at compile time.
11    ///
12    /// Used by the output buffer to make filtering decisions (suppress leading
13    /// whitespace, collapse adjacent whitespace) without eagerly resolving
14    /// deferred `LineRef` parts.
15    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
16    pub struct LineFlags: u8 {
17        /// The resolved content starts with whitespace.
18        const STARTS_WITH_WS = 0b0001;
19        /// The resolved content ends with whitespace.
20        const ENDS_WITH_WS   = 0b0010;
21        /// The resolved content is entirely whitespace (but not empty).
22        const ALL_WS         = 0b0100;
23        /// The resolved content is empty.
24        const EMPTY          = 0b1000;
25    }
26}
27
28impl LineFlags {
29    /// Compute flags from a `LineContent`.
30    ///
31    /// For `Plain` content, flags are exact. For `Template` content, flags
32    /// are conservative: `Slot`/`Select` parts are assumed to produce
33    /// non-whitespace content.
34    pub fn from_content(content: &LineContent) -> Self {
35        match content {
36            LineContent::Plain(s) => Self::from_plain(s),
37            LineContent::Template(parts) => Self::from_template(parts),
38        }
39    }
40
41    /// Compute flags from a plain string.
42    pub fn from_plain(s: &str) -> Self {
43        if s.is_empty() {
44            return Self::EMPTY;
45        }
46        let mut flags = Self::empty();
47        if s.starts_with(char::is_whitespace) {
48            flags |= Self::STARTS_WITH_WS;
49        }
50        if s.ends_with(char::is_whitespace) {
51            flags |= Self::ENDS_WITH_WS;
52        }
53        if s.trim().is_empty() {
54            flags |= Self::ALL_WS;
55        }
56        flags
57    }
58
59    fn from_template(parts: &[LinePart]) -> Self {
60        if parts.is_empty() {
61            return Self::EMPTY;
62        }
63        let mut flags = Self::empty();
64
65        // Check first part for leading whitespace.
66        if let Some(LinePart::Literal(s)) = parts.first()
67            && s.starts_with(char::is_whitespace)
68        {
69            flags |= Self::STARTS_WITH_WS;
70        }
71
72        // Check last part for trailing whitespace.
73        match parts.last() {
74            Some(LinePart::Literal(s)) if s.ends_with(char::is_whitespace) => {
75                flags |= Self::ENDS_WITH_WS;
76            }
77            _ => {}
78        }
79
80        // ALL_WS: only true if every part is whitespace-only literals.
81        // Any Slot/Select means we can't guarantee all-whitespace.
82        let all_ws = parts.iter().all(|p| match p {
83            LinePart::Literal(s) => s.trim().is_empty(),
84            _ => false,
85        });
86        if all_ws {
87            flags |= Self::ALL_WS;
88        }
89
90        flags
91    }
92}
93
94/// A sequence of literal and dynamic parts that compose an output line.
95pub type LineTemplate = Vec<LinePart>;
96
97/// One segment of a [`LineTemplate`].
98#[derive(Debug, Clone, PartialEq)]
99pub enum LinePart {
100    /// A literal string fragment.
101    Literal(String),
102    /// A value interpolation slot (index into the evaluation stack snapshot).
103    Slot(u8),
104    /// A plural/keyword select over a slot value.
105    Select {
106        slot: u8,
107        variants: Vec<(SelectKey, String)>,
108        default: String,
109    },
110}
111
112/// The key for matching a branch in a [`LinePart::Select`].
113#[derive(Debug, Clone, PartialEq)]
114pub enum SelectKey {
115    Cardinal(PluralCategory),
116    Ordinal(PluralCategory),
117    Exact(i32),
118    Keyword(String),
119}
120
121/// CLDR plural category.
122#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
123pub enum PluralCategory {
124    Zero,
125    One,
126    Two,
127    Few,
128    Many,
129    Other,
130}
131
132/// Trait for resolving plural categories at runtime.
133///
134/// Implementors provide locale-aware plural resolution. The `brink-intl` crate
135/// ships a batteries-included implementation backed by ICU4X baked data.
136pub trait PluralResolver {
137    /// Resolve the cardinal plural category for the given integer.
138    ///
139    /// `locale_override` allows overriding the resolver's default locale.
140    fn cardinal(&self, n: i64, locale_override: Option<&str>) -> PluralCategory;
141
142    /// Resolve the ordinal plural category for the given integer.
143    fn ordinal(&self, n: i64) -> PluralCategory;
144}