ISibboI / evalexpr

A powerful expression evaluation crate 🦀.
GNU Affero General Public License v3.0
325 stars 54 forks source link

Added feature: dot operator support #153

Open sweihub opened 1 year ago

sweihub commented 1 year ago

Hi @ISibboI

Would you review and merge this? Once the dot operator supported, we can do object like expressions, a quick glance:

  1. Demo 1: array
    assert_eq!(
    eval_with_context_mut(
        "v = array(1,2,3,4,5); 
         v = v.push(6); 
         v.length == v.get(5)",
        &mut context
    ),
    Ok(Value::Boolean(true))
    );
  2. Demo 2: tuple accessor, partly solved: https://github.com/ISibboI/evalexpr/issues/99
    assert_eq!(
    eval_with_context_mut("x = (1,2,3,4,5); x.get(4)", &mut context),
    Ok(Value::Int(5))
    );
  3. Demo 3: attributes of a future instrument
    eval_with_context_mut("
    f = future("IC2312");
    // return the prices
    (f.bid, f.ask, f.mid, f.last)
    ",
    &mut context);

The full test


#[test]
fn test_dot_attribute() {
    let mut context = HashMapContext::new();

    context
        .set_function(
            "array".to_string(),
            Function::new(|argument| Ok(Value::Tuple(argument.as_tuple()?))),
        )
        .unwrap();

    context
        .set_function(
            "dot".to_string(),
            Function::new(move |argument| {
                let x = argument.as_fixed_len_tuple(3)?;
                if let (Value::Tuple(id), Value::String(method)) = (&x[0], &x[1]) {
                    match method.as_str() {
                        "push" => {
                            // array.push(x)
                            let mut array = id.clone();
                            array.push(x[2].clone());
                            return Ok(Value::Tuple(array));
                        },
                        "get" => {
                            // array.get(i)
                            let index = x[2].as_int()?;
                            let value = &id[index as usize];
                            return Ok(value.clone());
                        },
                        "length" => {
                            // array.length
                            return Ok(Value::Int(id.len() as i64));
                        },
                        _ => {},
                    }
                }
                Err(EvalexprError::CustomMessage("unexpected dot call".into()))
            }),
        )
        .unwrap();

    assert_eq!(
        eval_with_context_mut(
            "v = array(1,2,3,4,5); 
             v = v.push(6); 
             v.length == v.get(5)",
            &mut context
        ),
        Ok(Value::Boolean(true))
    );

    assert_eq!(
        eval_with_context_mut("x = (1,2,3,4,5); x.get(4)", &mut context),
        Ok(Value::Int(5))
    );
}
ISibboI commented 1 year ago

Thank you for the pull request.

I think it may make more sense to support a dot operator as an actual operator. So I would not merge this for now.

sweihub commented 1 year ago

I thought over this issue, if we implement a dot operator, how do you expose the interface to programmer?

In object orienting world, we define a class, and a method, the compiler basically translates the method into a function: object_method(instance, args, ...), the object is strongly typed, and the compiler knows the associated method. But I think evalexpr should not go that far, otherwise we can use Lua or JavaScript for embedding script.

For example, how do you call method get() for instance x, the get() method was not a defined function inside evalexpr, we do not have a strongly typed associated method, this is a user defined method, we should have a way to let the user to implement.

data = (1, 2, 3);
x = data;
y = x.get();

So the most simple way is let the user to implement a dot(x, "get", args), you can name the function, but we must have a way. dot operator to get field is another story we can implement later, like

lovasoa commented 11 months ago

I would have an usecase for this in dezoomify-rs, to solve https://github.com/lovasoa/dezoomify-rs/issues/222

ISibboI commented 11 months ago

I think the simplest way would be to copy Rust: the expression a.f(b) is equivalent to calling f(a, b).

sweihub commented 11 months ago

How do you disambiguate from a.f(b) + f(x, b) ? if translate a.f(b) to f(a, b)?

You already have some built-in functions, so just let user to implement a dot function dot(self, args, ...) is the simplest way.

I already forked the evalexpr with the dot operator support, and it helps me a lot to implement simple object oriented. Who ever needs this feature, please use my fork.

https://github.com/sweihub/evalexpr

example

x = market("HSI2408");
a = x.ask + 0.01;
(a, x.bid, x.mid, x.last)
bwsw commented 10 months ago

Currently, evalexpr allows var.names.ok for vars and I have a ton of them. The introduced feature breaks the functionality. I suppose, according to SemVer, you should not merge it in the "next" release.

pm100 commented 10 months ago

in my case I have variables starting with '.', they are debugger symbols like '._main', please dont break that