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
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
use std::rc::Rc;
use std::fmt::{Debug};
use std::iter::empty;

use render_error::DataError;
use owning_ref::{OwningRef, Erased};
use {Var, Output, Number, Comparable};

pub type VarRef<'render> = OwningRef<Rc<Erased+'render>,
                                     Variable<'render>+'render>;

pub enum Val<'a, 'render: 'a>{
    Ref(&'a (Variable<'render> + 'render)),
    Rc(VarRef<'render>),
}

#[derive(Debug)]
pub struct Undefined;

#[derive(Debug)]
pub struct Empty;

pub const UNDEFINED: &'static Undefined = &Undefined;
pub const EMPTY: &'static Empty = &Empty;
pub const TRUE: &'static bool = &true;
pub const FALSE: &'static bool = &false;

/// A trait that you need to implement to put variable into the rendering
/// context
///
/// Note: by default all operations return unsupported error
pub trait Variable<'render>: Debug {
    /// Evaluates `a.b` operation
    ///
    /// Depending on your domain `a.x` may be equivalent of `a["x"]` or
    /// maybe not. Integer arguments `a.0` are not supported.
    fn attr<'x>(&'x self,  _attr: &str)
        -> Result<Var<'x, 'render>, DataError>
        where 'render: 'x
    {
        Err(DataError::AttrUnsupported(self.typename()))
    }
    /// Evaluates `a[b]`
    ///
    /// Depending on your domain `a["x"]` may be equivalent of `a.x` or
    /// maybe not.
    ///
    /// You may exract string value for a key with `key.as_str_key()`
    /// and `key.as_int_key()`.
    ///
    /// Note that actual key may have a (rust) type that is different from
    /// type of self (i.e. may come from different library).
    fn index<'x>(&'x self, _key: &(Variable<'render> + 'render))
        -> Result<Var<'x, 'render>, DataError>
        where 'render: 'x
    {
        Err(DataError::IndexUnsupported(self.typename()))
    }
    /// Evaluates `{{ x }}`
    ///
    /// This operation may not be useful for array-, and mapping-like values
    fn output(&self) -> Result<Output, DataError> {
        Err(DataError::OutputUnsupported(self.typename()))
    }
    /// Returns type name to use in error messages
    ///
    /// Note this must return actual type of value from user point of view
    /// not just rust type. For example for `Json` type it should describe
    /// `Json::Object` as a mapping and `Json::Array` as an array, not just
    /// return `Json`
    fn typename(&self) -> &'static str;
    /// Return string value of the variable used as key in index
    ///
    /// String keys are used for indexing dicts
    ///
    /// It's okay not to implement this method for complex variables
    fn as_str_key<'x>(&'x self) -> Result<&'x str, DataError> {
        Err(DataError::StrKeyUnsupported(self.typename()))
    }
    /// Return intenger value of the variable used as key
    ///
    /// Integer keys are used for indexing arrays
    ///
    /// It's okay not to implement this method for complex variables
    fn as_int_key(&self) -> Result<usize, DataError> {
        Err(DataError::IntKeyUnsupported(self.typename()))
    }
    /// Return boolean value of this object
    ///
    /// This is used in conditions `## if x`
    fn as_bool(&self) -> Result<bool, DataError> {
        Err(DataError::BoolUnsupported(self.typename()))
    }

    /// Return value as it could be treated in numeric context
    ///
    /// Numeric context is where `+,-,*,/,%` operators are used. Use standard
    /// `into()` conversion to convert built-in value into internal
    /// representation.
    fn as_number(&self) -> Result<Number, DataError> {
        Err(DataError::NumberUnsupported(self.typename()))
    }

    /// Return value of the object that might be compared to another value
    ///
    /// Note we can only compare numbers with number and strings with
    /// strings. All other types of comparisons are unsupported. Use standard
    /// `into()` conversion to convert built-in value into internal
    /// representation.
    fn as_comparable(&self) -> Result<Comparable, DataError> {
        Err(DataError::ComparisonUnsupported(self.typename()))
    }

    /// Return iterator over the value if appropriate
    fn iterate<'x>(&'x self)
        -> Result<Box<Iterator<Item=Var<'x, 'render>>+'x>, DataError>
        where 'render: 'x
    {
        Err(DataError::IterationUnsupported(self.typename()))
    }

    /// Return iterator over pairs if appropriate
    fn iterate_pairs<'x>(&'x self)
        -> Result<Box<Iterator<Item=(Var<'x, 'render>, Var<'x, 'render>)>+'x>,
                  DataError>
        where 'render: 'x
    {
        Err(DataError::PairIterationUnsupported(self.typename()))
    }
}

impl<'a> Variable<'a> for Undefined {
    fn attr<'x>(&'x self, _attr: &str)
        -> Result<Var<'x, 'a>, DataError>
        where 'a: 'x
    {
        Ok(Var::undefined())
    }
    fn index<'x>(&'x self,  _key: &Variable)
        -> Result<Var<'x, 'a>, DataError>
        where 'a: 'x
    {
        Ok(Var::undefined())
    }
    fn output(&self) -> Result<Output, DataError> {
        Ok(Output::empty())
    }
    fn typename(&self) -> &'static str {
        "undefined"
    }
    fn as_bool(&self) -> Result<bool, DataError> {
        Ok(false)
    }
    fn iterate<'x>(&'x self)
        -> Result<Box<Iterator<Item=Var<'x, 'a>>+'x>, DataError>
        where 'a: 'x
    {
        Ok(Box::new(empty()))
    }
    fn iterate_pairs<'x>(&'x self)
        -> Result<Box<Iterator<Item=(Var<'x, 'a>, Var<'x, 'a>)>+'x>,
                  DataError>
        where 'a: 'x
    {
        Ok(Box::new(empty()))
    }
}

impl<'a> Variable<'a> for Empty {
    fn output(&self) -> Result<Output, DataError> {
        Ok(Output::empty())
    }
    fn typename(&self) -> &'static str {
        "str"
    }
    fn as_bool(&self) -> Result<bool, DataError> {
        Ok(false)
    }
}

impl<'a, 'render> Var<'a, 'render> {
    /// Embed and owned reference to a value
    pub fn owned<'x, 'y: 'x, T: Variable<'y>+'y>(x: T) -> Var<'x, 'y>
        where 'y: 'x, T: 'y
    {
        Var(Val::Rc(OwningRef::new(Rc::new(x))
                .map(|x| x as &Variable).erase_owner()))
    }
    /// Embed a static string as a variable
    ///
    /// Currently this uses reference counted object that contains pointer,
    /// but we want to figure out better way to reference static strings
    pub fn str(x: &'static str) -> Var<'a, 'render> {
        // This is a limitation of a rust type system
        Var(Val::Rc(OwningRef::new(Rc::new(x))
                .map(|x| x as &Variable)
                .erase_owner()))
    }
    /// Create a borrowed reference to the variable
    pub fn borrow<'x, T: Variable<'render>+'render>(x: &'x T)
        -> Var<'x, 'render>
        where 'render: 'x
    {
        Var(Val::Ref(x))
    }
    /// Create an undefined variable reference
    pub fn undefined<'x, 'y: 'x>() -> Var<'x, 'y> {
        Var(Val::Ref(UNDEFINED))
    }
    /// Create a variable that contains an empty string
    pub fn empty<'x, 'y: 'x>() -> Var<'x, 'y> {
        Var(Val::Ref(EMPTY))
    }
    /// Create a variable that boolean true
    pub fn bool_true<'x, 'y: 'x>() -> Var<'x, 'y> {
        Var(Val::Ref(EMPTY))
    }
    /// Create a variable that boolean false
    pub fn bool_false<'x, 'y: 'x>() -> Var<'x, 'y> {
        Var(Val::Ref(EMPTY))
    }
}