Skip to main content

logicaffeine_data/
indexing.rs

1//! Polymorphic indexing traits for Logos collections.
2//!
3//! Logos uses **1-based indexing** to match natural language conventions.
4//! These traits provide get/set operations that automatically convert
5//! 1-based indices to 0-based for underlying Rust collections.
6//!
7//! # Supported Collections
8//!
9//! - [`Vec<T>`]: Indexed by `i64` (1-based, converted to 0-based internally)
10//! - [`HashMap<K, V>`]: Indexed by key `K` (pass-through semantics)
11//! - [`HashMap<String, V>`]: Also supports `&str` keys for convenience
12//!
13//! # Panics
14//!
15//! Vector indexing operations panic if the index is out of bounds
16//! (less than 1 or greater than collection length). Map operations
17//! panic if the key is not found.
18
19use std::collections::HashMap;
20use std::hash::Hash;
21
22/// Immutable element access by index.
23///
24/// Provides 1-based indexing for Logos collections. Index `1` refers
25/// to the first element, index `2` to the second, and so on.
26///
27/// # Examples
28///
29/// ```
30/// use logicaffeine_data::LogosIndex;
31///
32/// let v = vec!["a", "b", "c"];
33/// assert_eq!(v.logos_get(1i64), "a");  // 1-based!
34/// assert_eq!(v.logos_get(3i64), "c");
35/// ```
36///
37/// # Panics
38///
39/// Panics if the index is less than 1 or greater than the collection length.
40pub trait LogosIndex<I> {
41    /// The type of element returned by indexing.
42    type Output;
43    /// Get the element at the given index.
44    fn logos_get(&self, index: I) -> Self::Output;
45}
46
47/// Mutable element access by index.
48///
49/// Provides 1-based mutable indexing for Logos collections.
50///
51/// # Examples
52///
53/// ```
54/// use logicaffeine_data::LogosIndexMut;
55///
56/// let mut v = vec![1, 2, 3];
57/// v.logos_set(2i64, 20);
58/// assert_eq!(v, vec![1, 20, 3]);
59/// ```
60///
61/// # Panics
62///
63/// Panics if the index is less than 1 or greater than the collection length.
64pub trait LogosIndexMut<I>: LogosIndex<I> {
65    /// Set the element at the given index.
66    fn logos_set(&mut self, index: I, value: Self::Output);
67}
68
69// === Vec<T> with i64 (1-based indexing) ===
70
71impl<T: Clone> LogosIndex<i64> for Vec<T> {
72    type Output = T;
73
74    #[inline(always)]
75    fn logos_get(&self, index: i64) -> T {
76        if index < 1 {
77            panic!("Index {} is invalid: LOGOS uses 1-based indexing (minimum is 1)", index);
78        }
79        let idx = (index - 1) as usize;
80        if idx >= self.len() {
81            panic!("Index {} is out of bounds for seq of length {}", index, self.len());
82        }
83        unsafe { self.get_unchecked(idx).clone() }
84    }
85}
86
87impl<T: Clone> LogosIndexMut<i64> for Vec<T> {
88    #[inline(always)]
89    fn logos_set(&mut self, index: i64, value: T) {
90        if index < 1 {
91            panic!("Index {} is invalid: LOGOS uses 1-based indexing (minimum is 1)", index);
92        }
93        let idx = (index - 1) as usize;
94        if idx >= self.len() {
95            panic!("Index {} is out of bounds for seq of length {}", index, self.len());
96        }
97        unsafe { *self.get_unchecked_mut(idx) = value; }
98    }
99}
100
101// === HashMap<K, V> with K (key-based indexing) ===
102
103impl<K: Eq + Hash, V: Clone> LogosIndex<K> for HashMap<K, V> {
104    type Output = V;
105
106    #[inline(always)]
107    fn logos_get(&self, key: K) -> V {
108        self.get(&key).cloned().expect("Key not found in map")
109    }
110}
111
112impl<K: Eq + Hash, V: Clone> LogosIndexMut<K> for HashMap<K, V> {
113    #[inline(always)]
114    fn logos_set(&mut self, key: K, value: V) {
115        self.insert(key, value);
116    }
117}
118
119// === &str convenience for HashMap<String, V> ===
120
121impl<V: Clone> LogosIndex<&str> for HashMap<String, V> {
122    type Output = V;
123
124    #[inline(always)]
125    fn logos_get(&self, key: &str) -> V {
126        self.get(key).cloned().expect("Key not found in map")
127    }
128}
129
130impl<V: Clone> LogosIndexMut<&str> for HashMap<String, V> {
131    #[inline(always)]
132    fn logos_set(&mut self, key: &str, value: V) {
133        self.insert(key.to_string(), value);
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140
141    #[test]
142    fn vec_1_based_indexing() {
143        let v = vec![10, 20, 30];
144        assert_eq!(LogosIndex::logos_get(&v, 1i64), 10);
145        assert_eq!(LogosIndex::logos_get(&v, 2i64), 20);
146        assert_eq!(LogosIndex::logos_get(&v, 3i64), 30);
147    }
148
149    #[test]
150    #[should_panic(expected = "1-based indexing")]
151    fn vec_zero_index_panics() {
152        let v = vec![10, 20, 30];
153        let _ = LogosIndex::logos_get(&v, 0i64);
154    }
155
156    #[test]
157    fn vec_set_1_based() {
158        let mut v = vec![10, 20, 30];
159        LogosIndexMut::logos_set(&mut v, 2i64, 99);
160        assert_eq!(v, vec![10, 99, 30]);
161    }
162
163    #[test]
164    fn hashmap_string_key() {
165        let mut m: HashMap<String, i64> = HashMap::new();
166        m.insert("iron".to_string(), 42);
167        assert_eq!(LogosIndex::logos_get(&m, "iron".to_string()), 42);
168    }
169
170    #[test]
171    fn hashmap_str_key() {
172        let mut m: HashMap<String, i64> = HashMap::new();
173        m.insert("iron".to_string(), 42);
174        assert_eq!(LogosIndex::logos_get(&m, "iron"), 42);
175    }
176
177    #[test]
178    fn hashmap_set_key() {
179        let mut m: HashMap<String, i64> = HashMap::new();
180        LogosIndexMut::logos_set(&mut m, "iron", 42i64);
181        assert_eq!(m.get("iron"), Some(&42));
182    }
183}