Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added feature: dot operator support #153

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open

Conversation

sweihub
Copy link

@sweihub sweihub commented Sep 1, 2023

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: Support get value from tuple #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))
    );
}

@ISibboI
Copy link
Owner

ISibboI commented Oct 2, 2023

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
Copy link
Author

sweihub commented Oct 14, 2023

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;

@lovasoa
Copy link
Contributor

lovasoa commented Dec 6, 2023

I would have an usecase for this in dezoomify-rs, to solve lovasoa/dezoomify-rs#222

@ISibboI
Copy link
Owner

ISibboI commented Dec 7, 2023

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

@sweihub
Copy link
Author

sweihub commented Dec 9, 2023

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
Copy link

bwsw commented Jan 4, 2024

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
Copy link

pm100 commented Jan 5, 2024

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants