Skip to content

Added feature: dot operator support - #153

Open
snailwei wants to merge 3 commits into
ISibboI:mainfrom
snailwei:main
Open

Added feature: dot operator support#153
snailwei wants to merge 3 commits into
ISibboI:mainfrom
snailwei:main

Conversation

@snailwei

@snailwei snailwei commented Sep 1, 2023

Copy link
Copy Markdown

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

ISibboI commented Oct 2, 2023

Copy link
Copy Markdown
Owner

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.

@snailwei

snailwei commented Oct 14, 2023

Copy link
Copy Markdown
Author

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

lovasoa commented Dec 6, 2023

Copy link
Copy Markdown
Contributor

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

@ISibboI

ISibboI commented Dec 7, 2023

Copy link
Copy Markdown
Owner

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

@snailwei

snailwei commented Dec 9, 2023

Copy link
Copy Markdown
Author

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

bwsw commented Jan 4, 2024

Copy link
Copy Markdown

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

pm100 commented Jan 5, 2024

Copy link
Copy Markdown

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

@ISibboI
ISibboI force-pushed the main branch 6 times, most recently from e4a8571 to 6608b16 Compare October 11, 2024 15:01
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