Skip to main content

mical_cli_config/
lib.rs

1use mical_cli_syntax::ast;
2use smallvec::{SmallVec, ToSmallVec};
3
4mod text_arena;
5use text_arena::{TextArena, TextId};
6
7mod error;
8pub use error::ConfigError;
9
10mod eval;
11pub mod json;
12
13pub struct Config {
14    arena: TextArena,
15    /// Entry list in insertion order
16    entries: Vec<(TextId, ValueRaw)>,
17    /// Sorted list of indices into `entries` by key string (for binary search)
18    sorted_indices: Vec<u32>,
19    /// Group information for unique keys (sorted by first occurrence order).
20    /// element (group_start, count) means sorted_indices[group_start..group_start+count] are the indices of entries with the same key.
21    group_order: Vec<(u32, u32)>,
22}
23
24#[derive(Clone, Debug, PartialEq, Eq)]
25pub enum Value<'s> {
26    Bool(bool),
27    Integer(&'s str),
28    String(&'s str),
29}
30
31#[derive(Copy, Clone, PartialEq, Eq)]
32pub(crate) enum ValueRaw {
33    Bool(bool),
34    Integer(TextId),
35    String(TextId),
36}
37
38impl ValueRaw {
39    fn to_value<'s>(self, arena: &'s TextArena) -> Value<'s> {
40        match self {
41            ValueRaw::Bool(b) => Value::Bool(b),
42            ValueRaw::Integer(id) => Value::Integer(&arena[id]),
43            ValueRaw::String(id) => Value::String(&arena[id]),
44        }
45    }
46}
47
48impl Config {
49    pub fn from_source_file(source_file: ast::SourceFile) -> (Self, Vec<ConfigError>) {
50        let ctx = eval::eval(&source_file);
51        let eval::EvalContext { arena, entries, errors, .. } = ctx;
52
53        let (sorted_indices, group_order) = Self::build_indices(&arena, &entries);
54        (Config { arena, entries, sorted_indices, group_order }, errors)
55    }
56
57    pub fn from_kv_entries<'a>(items: impl IntoIterator<Item = (&'a str, Value<'a>)>) -> Self {
58        let mut arena = TextArena::new();
59        let mut entries = Vec::new();
60        for (key, val) in items {
61            let key_id = arena.alloc(key);
62            let raw = match val {
63                Value::Bool(b) => ValueRaw::Bool(b),
64                Value::Integer(s) => ValueRaw::Integer(arena.alloc(s)),
65                Value::String(s) => ValueRaw::String(arena.alloc(s)),
66            };
67            entries.push((key_id, raw));
68        }
69        let (sorted_indices, group_order) = Self::build_indices(&arena, &entries);
70        Config { arena, entries, sorted_indices, group_order }
71    }
72
73    fn build_indices(
74        arena: &TextArena,
75        entries: &[(TextId, ValueRaw)],
76    ) -> (Vec<u32>, Vec<(u32, u32)>) {
77        let sorted_indices = {
78            let mut indices = (0..entries.len() as u32).collect::<Vec<_>>();
79            indices.sort_unstable_by(|&a, &b| {
80                arena[entries[a as usize].0].cmp(&arena[entries[b as usize].0])
81            });
82            indices
83        };
84        let group_order = {
85            let mut groups: Vec<(u32, u32, u32)> = Vec::new(); // (sorted_start, count, first_entry_idx)
86            let mut i = 0;
87            while i < sorted_indices.len() {
88                let group_start = i;
89                let cur_key_id = entries[sorted_indices[i] as usize].0;
90                let mut min_idx = sorted_indices[i];
91                i += 1;
92                while i < sorted_indices.len() {
93                    let next_key_id = entries[sorted_indices[i] as usize].0;
94                    if arena[cur_key_id] != arena[next_key_id] {
95                        break;
96                    }
97                    min_idx = min_idx.min(sorted_indices[i]); // chmin
98                    i += 1;
99                }
100                groups.push((group_start as u32, (i - group_start) as u32, min_idx));
101            }
102            groups.sort_unstable_by_key(|&(_, _, first)| first);
103            groups.into_iter().map(|(s, c, _)| (s, c)).collect()
104        };
105        (sorted_indices, group_order)
106    }
107
108    /// Return values that exactly match `key` in insertion order (grouped by first occurrence).
109    pub fn query<'a>(&'a self, key: &str) -> impl Iterator<Item = Value<'a>> + 'a {
110        let lo = self.sorted_indices.partition_point(|i| {
111            let key_id = self.entries[*i as usize].0;
112            &self.arena[key_id] < key
113        });
114        let hi = self.sorted_indices[lo..].partition_point(|i| {
115            let key_id = self.entries[*i as usize].0;
116            &self.arena[key_id] <= key
117        }) + lo;
118        let idxs: SmallVec<[u32; 2]> = {
119            const _: () = {
120                assert!(size_of::<SmallVec<[u32; 2]>>() == size_of::<SmallVec<[u32; 1]>>());
121                assert!(size_of::<SmallVec<[u32; 3]>>() > size_of::<SmallVec<[u32; 1]>>());
122            };
123            let mut v = self.sorted_indices[lo..hi].to_smallvec();
124            v.sort_unstable(); // insertion order
125            v
126        };
127        idxs.into_iter().map(move |i| {
128            let (_, raw) = self.entries[i as usize];
129            raw.to_value(&self.arena)
130        })
131    }
132
133    /// Return (key, value) pairs whose keys start with `prefix` in insertion order (grouped by first occurrence).
134    pub fn query_prefix<'a>(
135        &'a self,
136        prefix: &str,
137    ) -> impl Iterator<Item = (&'a str, Value<'a>)> + 'a {
138        let lo = self.sorted_indices.partition_point(|i| {
139            let key_id = self.entries[*i as usize].0;
140            &self.arena[key_id] < prefix
141        });
142        let hi = self.sorted_indices.partition_point(|i| {
143            let key_id = self.entries[*i as usize].0;
144            let key = &self.arena[key_id];
145            key.starts_with(prefix) || key < prefix
146        });
147        self.iter_range(lo, hi)
148    }
149
150    /// Return all (key, value) pairs in the order they were inserted. (grouped by first occurrence)
151    pub fn entries<'a>(&'a self) -> impl Iterator<Item = (&'a str, Value<'a>)> + 'a {
152        let n = self.sorted_indices.len();
153        self.iter_range(0, n)
154    }
155
156    fn iter_range<'a>(
157        &'a self,
158        lo: usize,
159        hi: usize,
160    ) -> impl Iterator<Item = (&'a str, Value<'a>)> + 'a {
161        let matching =
162            self.group_order.iter().filter(move |&(gs, _)| *gs >= lo as u32 && *gs < hi as u32);
163        matching.into_iter().flat_map(move |(group_start, count)| {
164            let idxs: SmallVec<[u32; 2]> = {
165                const _: () = {
166                    assert!(size_of::<SmallVec<[u32; 2]>>() == size_of::<SmallVec<[u32; 1]>>());
167                    assert!(size_of::<SmallVec<[u32; 3]>>() > size_of::<SmallVec<[u32; 1]>>());
168                };
169                let (group_start, count) = (*group_start as usize, *count as usize);
170                let mut v = self.sorted_indices[group_start..(group_start + count)].to_smallvec();
171                v.sort_unstable(); // insertion order
172                v
173            };
174            idxs.into_iter().map(move |i| {
175                let (key_id, raw) = self.entries[i as usize];
176                (&self.arena[key_id], raw.to_value(&self.arena))
177            })
178        })
179    }
180}