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}