Macro rules for overriding logical operators

Hi,

I'm looking at feasibility for using declarative macros (or procedure macros if that's what it takes) to convert some Excel-like expressions into polars expressions. As a simplified example,

(a || c) && d

should be transcribed to:

col("a").or(col("c")).and(col("d"))

The macro is going used in a small part of the program and usually the expressions are short.

I have sth like this:

macro_rules! expression {
    ($name:ident) => {
        col(stringify!($name))
    };
    ($lit:literal) => {
        lit($lit)
    };
    (($tt:tt)) => {
        expression!($tt)
    };
    ($left:tt || $($right:tt)*) => {
        expression!($left).or(expression!($($right)*))
    };
}

It can handle very things like expression!(a || c || d).but the grammar does not generalize.

Does someone has any directions or perhaps there is an existing crate I can use / refer to?

Hey,
A few small changes make your macro expand as expected:

macro_rules! expression {
	($name:ident) => {
		col(stringify!($name))
	};
	($lit:literal) => {
		lit($lit)
	};
	(($($tt:tt)*)) => { // this changed
		expression!($($tt)*)
	};
	($left:tt || $($right:tt)*) => {
		expression!($left).or(expression!($($right)*))
	};
	($left:tt && $($right:tt)*) => { // added this
		expression!($left).and(expression!($($right)*))
	};
}

expression!((a || c) && d); // expands as expected

However, there are a few issues with your approach.
Firstly, these expressions are not equivalent whereas in most other programming languages they are, due to operator precedence:

expression!(a || c && d); // a || (c && d)
expression!(c && d || a); // c && (d || a) but e.g. in javascript (c && d) || a

I'm not sure how it would work out, but if you want to avoid this, you could consider avoiding macros and just implementing the operator traits for your types. However, it might be a pain since they seem to be from an external crate so you would have to use newtype shenanigans and you would have to give up on using && and || since you can't implement them due to lazy evaluation.

In addition, due to the way that macro_rules! recursion works, this code would probably have a worse time complexity compared to a proc macro. They are a pain to set up, but proc macros are a lot more flexible since you can run arbitrary code on the tokens making it easier to set up e.g. operator precedence and making the use of more performant algorithms possible. If your syntax is a subset of a syntatically valid rust expression, you might want to look into parsing it into a syn::Expr which will also (maybe actually im not sure) fix the operator precedence thing. You can then DFS through it to generate the macro output. Since proc macros are more flexible, using them would also lead to more room for future growth.

Finally, you might want to look into macro hygiene if it affects your use case.

I'm also fairly new to this so if I messed anything up kindly forget I said anything.

&& has higher precedence than || in Rust, so c && d || a is (c && d) || a.

Yes, in normal code, but due to how the macro is written it will not respect that (this can be verified using cargo-expand). This is why I suggested using syn::Expr in a proc macro which I'm under the impression will respect operator precedence (speaking of which I have just now realized I am completely unsure of this, it might just not).

1 Like