evalexpr icon indicating copy to clipboard operation
evalexpr copied to clipboard

Added feature: dot operator support

Open sweihub opened this issue 2 years ago • 7 comments

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))
);
  1. 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))
);
  1. 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))
    );
}

sweihub avatar Sep 01 '23 02:09 sweihub

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.

ISibboI avatar Oct 02 '23 12:10 ISibboI

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

  • tuple: (1, 2, 3, ,4).0
  • json: w = { x:1, y:2, z:3}; a = w.x;

sweihub avatar Oct 14 '23 11:10 sweihub

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

lovasoa avatar Dec 06 '23 20:12 lovasoa

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

ISibboI avatar Dec 07 '23 16:12 ISibboI

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)

sweihub avatar Dec 09 '23 14:12 sweihub

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.

bwsw avatar Jan 04 '24 18:01 bwsw

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

pm100 avatar Jan 05 '24 00:01 pm100