lexpr/value/index.rs
1use std::ops;
2
3use crate::{Cons, Value};
4
5/// A type that can be used to index into a `lexpr::Value`.
6///
7/// The [`get`] method of `Value` accept any type that implements
8/// `Index`, as does the [square-bracket indexing operator]. This
9/// trait is implemented for strings and `Value`, both of which can be
10/// used to index into association lists, and for `usize` which is
11/// used to index into to lists by element index.
12///
13/// Note that improper lists are only indexable by `usize`, not by
14/// strings.
15///
16/// [`get`]: enum.Value.html#method.get
17/// [square-bracket indexing operator]: enum.Value.html#impl-Index%3CI%3E
18///
19/// This trait is sealed and cannot be implemented for types outside
20/// of `lexpr`.
21///
22/// # Examples
23///
24/// ```
25/// # use lexpr::sexp;
26/// #
27/// let data = sexp!(((foo 42) (bar . (1 2 3))));
28///
29/// // Data is an association list so it can be indexed with a string.
30/// let bar = &data["bar"];
31///
32/// // Bar is a list so it can be indexed with an integer.
33/// let second = &bar[1];
34///
35/// assert_eq!(second, 2);
36/// ```
37pub trait Index: private::Sealed {
38 /// Return None if the key is not already in the array or object.
39 #[doc(hidden)]
40 fn index_into<'v>(&self, v: &'v Value) -> Option<&'v Value>;
41}
42
43// Prevent users from implementing the Index trait.
44mod private {
45 pub trait Sealed {}
46 impl Sealed for usize {}
47 impl Sealed for str {}
48 impl Sealed for String {}
49 impl<'a, T: ?Sized> Sealed for &'a T where T: Sealed {}
50 impl Sealed for super::Value {}
51}
52
53impl Index for usize {
54 fn index_into<'v>(&self, v: &'v Value) -> Option<&'v Value> {
55 match v {
56 Value::Vector(elements) => elements.get(*self),
57 Value::Cons(cons) => {
58 let mut cursor = cons;
59 for _ in 0..*self {
60 match cursor.cdr() {
61 Value::Cons(next) => cursor = next,
62 _ => return None,
63 }
64 }
65 Some(cursor.car())
66 }
67 _ => None,
68 }
69 }
70}
71
72fn match_pair_name<'a>(name: &str, pair: &'a Cons) -> Option<&'a Value> {
73 match pair.car() {
74 Value::Cons(inner) if inner.car().as_name() == Some(name) => Some(inner.cdr()),
75 _ => None,
76 }
77}
78
79impl Index for str {
80 fn index_into<'v>(&self, v: &'v Value) -> Option<&'v Value> {
81 match v {
82 Value::Cons(pair) => pair.iter().find_map(|e| match_pair_name(self, e)),
83 _ => None,
84 }
85 }
86}
87
88impl Index for String {
89 fn index_into<'v>(&self, v: &'v Value) -> Option<&'v Value> {
90 self[..].index_into(v)
91 }
92}
93
94impl<'a, T: ?Sized> Index for &'a T
95where
96 T: Index,
97{
98 fn index_into<'v>(&self, v: &'v Value) -> Option<&'v Value> {
99 (**self).index_into(v)
100 }
101}
102
103fn match_pair_key<'a>(value: &Value, pair: &'a Cons) -> Option<&'a Value> {
104 match pair.car() {
105 Value::Cons(inner) if inner.car() == value => Some(inner.cdr()),
106 _ => None,
107 }
108}
109
110impl Index for Value {
111 fn index_into<'v>(&self, v: &'v Value) -> Option<&'v Value> {
112 match v {
113 Value::Cons(pair) => pair.iter().find_map(|e| match_pair_key(self, e)),
114 _ => None,
115 }
116 }
117}
118
119// The usual semantics of Index is to panic on invalid indexing.
120//
121// That said, the usual semantics are for things like Vec and BTreeMap which
122// have different use cases than Value. If you are working with a Vec, you know
123// that you are working with a Vec and you can get the len of the Vec and make
124// sure your indices are within bounds. The Value use cases are more
125// loosey-goosey. You got some S-expression from an endpoint and you want to
126// pull values out of it. Outside of this Index impl, you already have the
127// option of using value.as_array() and working with the Vec directly, or
128// matching on Value::Array and getting the Vec directly. The Index impl means
129// you can skip that and index directly into the thing using a concise
130// syntax. You don't have to check the type, you don't have to check the len, it
131// is all about what you expect the Value to look like.
132//
133// Basically the use cases that would be well served by panicking here are
134// better served by using one of the other approaches: get and get_mut,
135// as_array, or match. The value of this impl is that it adds a way of working
136// with Value that is not well served by the existing approaches: concise and
137// careless and sometimes that is exactly what you want.
138impl<I> ops::Index<I> for Value
139where
140 I: Index,
141{
142 type Output = Value;
143
144 /// Index into a `lexpr::Value` using the syntax `value[0]` or
145 /// `value["k"]`.
146 ///
147 /// Returns the nil value if the type of `self` does not match the
148 /// type of the index, for example if the index is a string and
149 /// `self` is not an association list. Also returns the nil value
150 /// if the given key does not exist in the assication list or the
151 /// given index is not within the bounds of the list.
152 ///
153 /// Note that repeatedly indexing with a string is not possible,
154 /// as the indexing operation returns the found association list
155 /// entry, which is not an association list itself. This behavior,
156 /// i.e. returning the whole entry including the key is due to the
157 /// design decison of representing lists as Rust vectors.
158 ///
159 /// # Examples
160 ///
161 /// ```
162 /// # use lexpr::sexp;
163 /// #
164 /// let data = sexp!(((a . 42) (x . (y (z zz)))));
165 ///
166 /// assert_eq!(data["x"], sexp!((y (z zz))));
167 ///
168 /// assert_eq!(data["a"], sexp!(42)); // returns nil for undefined values
169 /// assert_eq!(data["b"], sexp!(#nil)); // does not panic
170 /// ```
171 fn index(&self, index: I) -> &Value {
172 static NIL: Value = Value::Nil;
173 index.index_into(self).unwrap_or(&NIL)
174 }
175}