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::Error;
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<Error>) {
50        let eval::Output { arena, entries, errors } = eval::eval_source_file(&source_file);
51        let (sorted_indices, group_order) = Self::build_indices(&arena, &entries);
52        (Config { arena, entries, sorted_indices, group_order }, errors)
53    }
54
55    pub fn from_kv_entries<'a>(items: impl IntoIterator<Item = (&'a str, Value<'a>)>) -> Self {
56        let mut arena = TextArena::new();
57        let mut entries = Vec::new();
58        for (key, val) in items {
59            let key_id = arena.alloc(key);
60            let raw = match val {
61                Value::Bool(b) => ValueRaw::Bool(b),
62                Value::Integer(s) => ValueRaw::Integer(arena.alloc(s)),
63                Value::String(s) => ValueRaw::String(arena.alloc(s)),
64            };
65            entries.push((key_id, raw));
66        }
67        let (sorted_indices, group_order) = Self::build_indices(&arena, &entries);
68        Config { arena, entries, sorted_indices, group_order }
69    }
70
71    fn build_indices(
72        arena: &TextArena,
73        entries: &[(TextId, ValueRaw)],
74    ) -> (Vec<u32>, Vec<(u32, u32)>) {
75        let sorted_indices = {
76            let mut indices = (0..entries.len() as u32).collect::<Vec<_>>();
77            indices.sort_unstable_by(|&a, &b| {
78                arena[entries[a as usize].0].cmp(&arena[entries[b as usize].0])
79            });
80            indices
81        };
82        let group_order = {
83            let mut groups: Vec<(u32, u32, u32)> = Vec::new(); // (sorted_start, count, first_entry_idx)
84            let mut i = 0;
85            while i < sorted_indices.len() {
86                let group_start = i;
87                let cur_key_id = entries[sorted_indices[i] as usize].0;
88                let mut min_idx = sorted_indices[i];
89                i += 1;
90                while i < sorted_indices.len() {
91                    let next_key_id = entries[sorted_indices[i] as usize].0;
92                    if arena[cur_key_id] != arena[next_key_id] {
93                        break;
94                    }
95                    min_idx = min_idx.min(sorted_indices[i]); // chmin
96                    i += 1;
97                }
98                groups.push((group_start as u32, (i - group_start) as u32, min_idx));
99            }
100            groups.sort_unstable_by_key(|&(_, _, first)| first);
101            groups.into_iter().map(|(s, c, _)| (s, c)).collect()
102        };
103        (sorted_indices, group_order)
104    }
105
106    /// Return values that exactly match `key` in insertion order (grouped by first occurrence).
107    pub fn query<'a>(&'a self, key: &str) -> impl Iterator<Item = Value<'a>> + 'a {
108        let lo = self.sorted_indices.partition_point(|i| {
109            let key_id = self.entries[*i as usize].0;
110            &self.arena[key_id] < key
111        });
112        let hi = self.sorted_indices[lo..].partition_point(|i| {
113            let key_id = self.entries[*i as usize].0;
114            &self.arena[key_id] <= key
115        }) + lo;
116        let idxs: SmallVec<[u32; 2]> = {
117            const _: () = {
118                assert!(size_of::<SmallVec<[u32; 2]>>() == size_of::<SmallVec<[u32; 1]>>());
119                assert!(size_of::<SmallVec<[u32; 3]>>() > size_of::<SmallVec<[u32; 1]>>());
120            };
121            let mut v = self.sorted_indices[lo..hi].to_smallvec();
122            v.sort_unstable(); // insertion order
123            v
124        };
125        idxs.into_iter().map(move |i| {
126            let (_, raw) = self.entries[i as usize];
127            raw.to_value(&self.arena)
128        })
129    }
130
131    /// Return (key, value) pairs whose keys start with `prefix` in insertion order (grouped by first occurrence).
132    pub fn query_prefix<'a>(
133        &'a self,
134        prefix: &str,
135    ) -> impl Iterator<Item = (&'a str, Value<'a>)> + 'a {
136        let lo = self.sorted_indices.partition_point(|i| {
137            let key_id = self.entries[*i as usize].0;
138            &self.arena[key_id] < prefix
139        });
140        let hi = self.sorted_indices.partition_point(|i| {
141            let key_id = self.entries[*i as usize].0;
142            let key = &self.arena[key_id];
143            key.starts_with(prefix) || key < prefix
144        });
145        self.iter_range(lo, hi)
146    }
147
148    /// Return all (key, value) pairs in the order they were inserted. (grouped by first occurrence)
149    pub fn entries<'a>(&'a self) -> impl Iterator<Item = (&'a str, Value<'a>)> + 'a {
150        let n = self.sorted_indices.len();
151        self.iter_range(0, n)
152    }
153
154    fn iter_range<'a>(
155        &'a self,
156        lo: usize,
157        hi: usize,
158    ) -> impl Iterator<Item = (&'a str, Value<'a>)> + 'a {
159        let matching =
160            self.group_order.iter().filter(move |&(gs, _)| *gs >= lo as u32 && *gs < hi as u32);
161        matching.into_iter().flat_map(move |(group_start, count)| {
162            let idxs: SmallVec<[u32; 2]> = {
163                const _: () = {
164                    assert!(size_of::<SmallVec<[u32; 2]>>() == size_of::<SmallVec<[u32; 1]>>());
165                    assert!(size_of::<SmallVec<[u32; 3]>>() > size_of::<SmallVec<[u32; 1]>>());
166                };
167                let (group_start, count) = (*group_start as usize, *count as usize);
168                let mut v = self.sorted_indices[group_start..(group_start + count)].to_smallvec();
169                v.sort_unstable(); // insertion order
170                v
171            };
172            idxs.into_iter().map(move |i| {
173                let (key_id, raw) = self.entries[i as usize];
174                (&self.arena[key_id], raw.to_value(&self.arena))
175            })
176        })
177    }
178}