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    fn logos_get(&self, index: i64) -> T {
75        if index < 1 {
76            panic!("Index {} is invalid: LOGOS uses 1-based indexing (minimum is 1)", index);
77        }
78        let idx = (index - 1) as usize;
79        if idx >= self.len() {
80            panic!("Index {} is out of bounds for seq of length {}", index, self.len());
81        }
82        self[idx].clone()
83    }
84}
85
86impl<T: Clone> LogosIndexMut<i64> for Vec<T> {
87    fn logos_set(&mut self, index: i64, value: T) {
88        if index < 1 {
89            panic!("Index {} is invalid: LOGOS uses 1-based indexing (minimum is 1)", index);
90        }
91        let idx = (index - 1) as usize;
92        if idx >= self.len() {
93            panic!("Index {} is out of bounds for seq of length {}", index, self.len());
94        }
95        self[idx] = value;
96    }
97}
98
99// === HashMap<K, V> with K (key-based indexing) ===
100
101impl<K: Eq + Hash, V: Clone> LogosIndex<K> for HashMap<K, V> {
102    type Output = V;
103
104    fn logos_get(&self, key: K) -> V {
105        self.get(&key).cloned().expect("Key not found in map")
106    }
107}
108
109impl<K: Eq + Hash, V: Clone> LogosIndexMut<K> for HashMap<K, V> {
110    fn logos_set(&mut self, key: K, value: V) {
111        self.insert(key, value);
112    }
113}
114
115// === &str convenience for HashMap<String, V> ===
116
117impl<V: Clone> LogosIndex<&str> for HashMap<String, V> {
118    type Output = V;
119
120    fn logos_get(&self, key: &str) -> V {
121        self.get(key).cloned().expect("Key not found in map")
122    }
123}
124
125impl<V: Clone> LogosIndexMut<&str> for HashMap<String, V> {
126    fn logos_set(&mut self, key: &str, value: V) {
127        self.insert(key.to_string(), value);
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134
135    #[test]
136    fn vec_1_based_indexing() {
137        let v = vec![10, 20, 30];
138        assert_eq!(LogosIndex::logos_get(&v, 1i64), 10);
139        assert_eq!(LogosIndex::logos_get(&v, 2i64), 20);
140        assert_eq!(LogosIndex::logos_get(&v, 3i64), 30);
141    }
142
143    #[test]
144    #[should_panic(expected = "1-based indexing")]
145    fn vec_zero_index_panics() {
146        let v = vec![10, 20, 30];
147        let _ = LogosIndex::logos_get(&v, 0i64);
148    }
149
150    #[test]
151    fn vec_set_1_based() {
152        let mut v = vec![10, 20, 30];
153        LogosIndexMut::logos_set(&mut v, 2i64, 99);
154        assert_eq!(v, vec![10, 99, 30]);
155    }
156
157    #[test]
158    fn hashmap_string_key() {
159        let mut m: HashMap<String, i64> = HashMap::new();
160        m.insert("iron".to_string(), 42);
161        assert_eq!(LogosIndex::logos_get(&m, "iron".to_string()), 42);
162    }
163
164    #[test]
165    fn hashmap_str_key() {
166        let mut m: HashMap<String, i64> = HashMap::new();
167        m.insert("iron".to_string(), 42);
168        assert_eq!(LogosIndex::logos_get(&m, "iron"), 42);
169    }
170
171    #[test]
172    fn hashmap_set_key() {
173        let mut m: HashMap<String, i64> = HashMap::new();
174        LogosIndexMut::logos_set(&mut m, "iron", 42i64);
175        assert_eq!(m.get("iron"), Some(&42));
176    }
177}