expry 0.4.0

Execute an expression on an encoded (binary) value, yielding another binary value (either in decoded or encoded form). Supports custom functions. Supports parsing the expression and converting the expression to bytecode.

`expry` is a fast schemaless binary format for data, with the ability for filtering and selecting parts of the information using expressions over the stored binary data. Expressions are compiled and optimized to bytecode, and during evaluation this bytecode is interpreted.
JSON can be read into these data structures (using `expry_parse_json`). Because `expry` supports binary strings, not all `expry` values can be exported to JSON.
The following data types are supported:
- `null`;
- `bool`;
- `int64`;
- `float`;
- `double`;
- `string`;
- `array`;
- `object`.

## Expressions

Expressions over binary objects are compiled to byte code, and can be transmitted over the network. This bytecode can be quickly evaluated, as the bytecode is optimized on the way the binary objects are stored on a low level. Keys inside objects are stored in an ordered way. If keys are not needed any more for evaluation of the current expression, they are permanently skipped.

- Constants: `null`, `false`, `true`, numbers (including: `0x...`, `0b...`, `...f`), strings with `'string'` or `"string"` (including raw strings using `r#"..."#` with variable number of `#`, and hex strings with `x'hex'` and `x"hex"` and ).
- Conditionals: if-then-else is support with the conditional (ternary) operator: `cond ? then-part : else-part`.
- Object expression with `{` fields? `}`, with optional fields, individual fields separated by `,` (last entry can have a trailing `,`):
  - fields can be `name: expr` with expr an expression or a primary data type (see above);
  - fields can be `"name": expr` with expr an expression or a primary data type (see above);
  - fields can be `field` (or `field???`), which copies the field from the `this` object;
  - fields can be `(expr): expr` to dynamically generate a field name (must be a string), e.g. `{ (somefield): 42, }` (results in `{x: 42}` if `somefield` is `"x"`).
- Array expressions: `[` expressions? `]`, with the optional `expressions` which are one or more expressions delimited by `,` (last entry can have a trailing `,`). Example: `[ 42+42, 37+37, ]`.
- Arithmetic operators: `+`, `-`, `*`, `/`, `**` (pow), `%` (mod).
- Bitwise operators: `|`, `&`, `^` (xor), `<<`, `>>`
- Logical operators: `&&`, `||`, `a ? b : c`, `==`, `!=`, `>`, `>=`, `<`, `<=`.
- Special `this` field, to signify current object.
- Array subscription: `a[b]` (array subscription) is supported to access indiviual elements in arrays
- Length operator:
  - `field.len()` which result in the length of the field if it is a string (number of bytes), array (number of elements), or object (number of key-values).
- String operators:
  - `a .. b`: concatenate strings `a` and `b`;
  - `a *= b`: true if string `a` contains with string `b`
  - `a $= b`: true if string `a` ends with string `b`
  - `a ^= b`: true if string `a` starts with string `b`
- Field operators:
  - `a.b.c.d` is supported to access subfields;
  - `a.get(b).get(c).get(d)` is supported for dynamic subfields;
- Error operators:
  - try operator: `a ??? b`: if there is an error during evaluation of `a` (like an undefined field, or division by zero). The shorthand `a ???` is equivalent to `a ??? null`. Alternative syntax is `try(a, b)` and `try(a)`.
  - not-null-else operator: `a ?? b`: if `a` is null, return `b`.
  - `defined(x)` and `undefined(x)`: checks if value is (un)defined (such as a field lookup).
- String methods:
  - `.trim()`
  - `.lower()`
  - `.upper()`
  - `.hex()`
  - `.htmlescape()`
  - `.urlescape()`
  - `.sub(int[, int])` (third argument is length of resulting string, default the max integer value)
  - `.basename()`
  - `.dirname()`
  - `.splitn(max, split_on)` (split_on is the splitting string, max is the max number of elements in the resulting array), results in an array of strings
- Type methods:
  - `.tostring()`
  - `.tointeger()`
  - `.tofloat()`
  - `.todouble()`
- Array methods:
  - `array.extend(array)` combines two arrays.
- Methods taking a lambda function as argument. Lambda functions are specified as `|x| expr`
  with `x` the argument to the lambda function.
  - `array.filter(lambda)` filters array to only contain elements `e` where `lambda(e)` returns true (note that lambda should always return a boolean, otherwise the filter will fail)
  - `array.map(lambda)` maps array of elements `e` to an array of elements `lambda(e)`
  - `array.sort_by_key(lambda)` sorts array of elements `e` based on key `lambda(e)`. Sorting
    of values is based on this type order: null, bool, int, float, double, string, array, object. If the type matches,
    the sorting is performed on the contents itself. Note that sorting of NaN floats will yield
    a dynamic error as this is not defined.
  - `array.to_object(lamba)` converts the array to an object, in which the key is the first
    value of the array returned by the lambda. The value is the second value of the array
    returned by the lambda.
  - `array.to_map(lamba)` creates a key - array of values object. The lambda returns [key,
    sort, value].

## User defined functions

During evaluation, there is also the possibility to add user defined functions. These user defined functions are only known at evaluation time, therefore there is no static type checking in place. Only runtime errors are generated if the number of arguments or type of arguments do not match.

## Easily convert custom data types to values

Using the `From` trait, custom data types can be easily converted to values, even if they are
contained in data structures like `Vec`.

use expry::*;

struct Foo {
    foo: u32,
    bar: bool,

impl<'a> From<&'a Foo> for DecodedValue<'a> {
    fn from(v: &'a Foo) -> Self {
            "foo": v.foo as i64,
            "bar": v.bar,

let foos = vec![
let encoded_value = value!({
  "foo": Foo{foo:1,bar:true},
  "foos": foos,