1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
use std::ops;

use crate::{Cons, Value};

/// A type that can be used to index into a `lexpr::Value`.
///
/// The [`get`] method of `Value` accept any type that implements
/// `Index`, as does the [square-bracket indexing operator]. This
/// trait is implemented for strings and `Value`, both of which can be
/// used to index into association lists, and for `usize` which is
/// used to index into to lists by element index.
///
/// Note that improper lists are only indexable by `usize`, not by
/// strings.
///
/// [`get`]: enum.Value.html#method.get
/// [square-bracket indexing operator]: enum.Value.html#impl-Index%3CI%3E
///
/// This trait is sealed and cannot be implemented for types outside
/// of `lexpr`.
///
/// # Examples
///
/// ```
/// # use lexpr::sexp;
/// #
/// let data = sexp!(((foo 42) (bar . (1 2 3))));
///
/// // Data is an association list so it can be indexed with a string.
/// let bar = &data["bar"];
///
/// // Bar is a list so it can be indexed with an integer.
/// let second = &bar[1];
///
/// assert_eq!(second, 2);
/// ```
pub trait Index: private::Sealed {
    /// Return None if the key is not already in the array or object.
    #[doc(hidden)]
    fn index_into<'v>(&self, v: &'v Value) -> Option<&'v Value>;
}

// Prevent users from implementing the Index trait.
mod private {
    pub trait Sealed {}
    impl Sealed for usize {}
    impl Sealed for str {}
    impl Sealed for String {}
    impl<'a, T: ?Sized> Sealed for &'a T where T: Sealed {}
    impl Sealed for super::Value {}
}

impl Index for usize {
    fn index_into<'v>(&self, v: &'v Value) -> Option<&'v Value> {
        match v {
            Value::Vector(elements) => elements.get(*self),
            Value::Cons(cons) => {
                let mut cursor = cons;
                for _ in 0..*self {
                    match cursor.cdr() {
                        Value::Cons(next) => cursor = next,
                        _ => return None,
                    }
                }
                Some(cursor.car())
            }
            _ => None,
        }
    }
}

fn match_pair_name<'a>(name: &str, pair: &'a Cons) -> Option<&'a Value> {
    match pair.car() {
        Value::Cons(inner) if inner.car().as_name() == Some(name) => Some(inner.cdr()),
        _ => None,
    }
}

impl Index for str {
    fn index_into<'v>(&self, v: &'v Value) -> Option<&'v Value> {
        match v {
            Value::Cons(pair) => pair.iter().find_map(|e| match_pair_name(self, e)),
            _ => None,
        }
    }
}

impl Index for String {
    fn index_into<'v>(&self, v: &'v Value) -> Option<&'v Value> {
        self[..].index_into(v)
    }
}

impl<'a, T: ?Sized> Index for &'a T
where
    T: Index,
{
    fn index_into<'v>(&self, v: &'v Value) -> Option<&'v Value> {
        (**self).index_into(v)
    }
}

fn match_pair_key<'a>(value: &Value, pair: &'a Cons) -> Option<&'a Value> {
    match pair.car() {
        Value::Cons(inner) if inner.car() == value => Some(inner.cdr()),
        _ => None,
    }
}

impl Index for Value {
    fn index_into<'v>(&self, v: &'v Value) -> Option<&'v Value> {
        match v {
            Value::Cons(pair) => pair.iter().find_map(|e| match_pair_key(self, e)),
            _ => None,
        }
    }
}

// The usual semantics of Index is to panic on invalid indexing.
//
// That said, the usual semantics are for things like Vec and BTreeMap which
// have different use cases than Value. If you are working with a Vec, you know
// that you are working with a Vec and you can get the len of the Vec and make
// sure your indices are within bounds. The Value use cases are more
// loosey-goosey. You got some S-expression from an endpoint and you want to
// pull values out of it. Outside of this Index impl, you already have the
// option of using value.as_array() and working with the Vec directly, or
// matching on Value::Array and getting the Vec directly. The Index impl means
// you can skip that and index directly into the thing using a concise
// syntax. You don't have to check the type, you don't have to check the len, it
// is all about what you expect the Value to look like.
//
// Basically the use cases that would be well served by panicking here are
// better served by using one of the other approaches: get and get_mut,
// as_array, or match. The value of this impl is that it adds a way of working
// with Value that is not well served by the existing approaches: concise and
// careless and sometimes that is exactly what you want.
impl<I> ops::Index<I> for Value
where
    I: Index,
{
    type Output = Value;

    /// Index into a `lexpr::Value` using the syntax `value[0]` or
    /// `value["k"]`.
    ///
    /// Returns the nil value if the type of `self` does not match the
    /// type of the index, for example if the index is a string and
    /// `self` is not an association list. Also returns the nil value
    /// if the given key does not exist in the assication list or the
    /// given index is not within the bounds of the list.
    ///
    /// Note that repeatedly indexing with a string is not possible,
    /// as the indexing operation returns the found association list
    /// entry, which is not an association list itself. This behavior,
    /// i.e. returning the whole entry including the key is due to the
    /// design decison of representing lists as Rust vectors.
    ///
    /// # Examples
    ///
    /// ```
    /// # use lexpr::sexp;
    /// #
    /// let data = sexp!(((a . 42) (x . (y (z zz)))));
    ///
    /// assert_eq!(data["x"], sexp!((y (z zz))));
    ///
    /// assert_eq!(data["a"], sexp!(42)); // returns nil for undefined values
    /// assert_eq!(data["b"], sexp!(#nil)); // does not panic
    /// ```
    fn index(&self, index: I) -> &Value {
        static NIL: Value = Value::Nil;
        index.index_into(self).unwrap_or(&NIL)
    }
}