Visitar URL original
tinyexpr improvements by Nahor · Pull Request #13061 · fish-shell/fish-shell · GitHub
Skip to content

tinyexpr improvements - #13061

Open
Nahor wants to merge 13 commits into
fish-shell:masterfrom
Nahor:push-wnrumoprplol
Open

Nahor wants to merge 13 commits into
fish-shell:masterfrom
Nahor:push-wnrumoprplol

Conversation

@Nahor

@Nahor Nahor commented Oct 21, 2026

Copy link
Copy Markdown
Contributor

It started with a fix for the error caret location but evolved in a major update to make the code more idiomatic, and hopefully more readable and easier to reason with.

Nahor added 13 commits October 21, 2026 10:32
The alias was using a lowerCamelCase while conventionally Rust types
use UpperCamelCase.
Some syntax highlighters rely on the convention for styling, leading to
the incorrect style being used.

The chosen alias was also not ideal since the error is technically
not limited to the `wcstodXXX` functions, even if that's the only way
to currently get it in `tinyexpr`.

The qualified name is barely longer than the alias, so go with that rather
than a better alias.
The reason `get_token` returns an `Option` is for whitespaces and eof.
- Move the whitespaces handling in `next_token` since they do not
  generate tokens. This is also marginally more efficient.
- Move the eof handling in `get_token`. This also makes `get_token` the
  sole token generator outside of the special `Token::Error`
The token is not just for infix operator but also as a sign for
subsequent number or function
This makes the code more readable and intuitive by removing uncommon
`if let <constant> = ...` and verbose `matches!(...)` (and the easily
misread `if !matches!()`)
`get_token()` should only be called if there are no errors, and nothing
sets `self.current` earlier in the function.
The old API is taken from C and is not idiomatic Rust.
Instead of putting the caret at the end of the input, which is
unhelpful, put it on the opening parenthesis
A part of the change is how "too many args" error is reported because
it is more logical and allow for significant code simplification.
The error carret is now on the first extra comma rather than
the following value, since that's where a closing parenthesis is
expected (or an end-of-string).
And do not report a length since the parser could stop at some arbitrary
location when there are more errors afterwards.
Rename `State::current` to `State::token`. The old name doesn't tell
you "what" the variable is about (the state? the position? some
settings? ...). Moreover, "current" is uselessly specific when there
isn't some "previous" or "next" variable to go along with it.
This improves error reporting by allowing to put the caret at the start
of the current token instead of the end, or avoids hardcoding the token
width when we already put the caret at the correct location.
Comment thread src/tinyexpr.rs
}
}

impl PartialEq for Function {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did a relatively quick scan over all commits; everything I saw and the test behavior changes look good to me.
I didn't review super closely but as long as there are no unintended behavior changes this is fine.
LGTM. One optional comment.

Comment thread src/tinyexpr.rs
(Self::Constant(l0), Self::Constant(r0)) => l0 == r0,
(Self::Fn1(l0), Self::Fn1(r0)) => std::ptr::fn_addr_eq(*l0, *r0),
(Self::Fn2(l0), Self::Fn2(r0)) => std::ptr::fn_addr_eq(*l0, *r0),
(Self::FnN(l0), Self::FnN(r0)) => std::ptr::fn_addr_eq(*l0, *r0),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that "if let" that doesn't bind anything is weird;

I'm okay with either but if we stick to "matches!(...)", we wouldn't need to add this PartialEq impl.
If "!matches!()" is too subtle, we could maybe write "does_not_match!()"?
That's something we could use elsewhere too. Sounds a bit weird though.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought of using matches! everywhere instead of PartialEq, but it is not as nice as ==, it's harder to read/write.

I'm not keen on does_not_match! since it's weird as you say. I'd rather keep !matches!.

An alternative is to manually implement PartialEq for Token and only compare the discriminant (i.e. the enum itself, not the values in each enum entry) since we only use == for entries with not values and still use matches! for the other (mostly Token::Error).
At some point I had that, to avoid the PartialEq on Function because I don't like the use of fn_addr_eq. But that make PartialEq on Token a bit odd/non-standard, so I reverted that change.

A variant of the alternative is to use our function instead of the standard PartialEq::eq(), for instance a is(<token>) to avoid the subversion of PartialEq but it's not as discoverable.
Or a family if is_<token>() (similar to is_some()/is_ok()) but that a lot of functions barely used if at all.

Best option: the is operator, but we'll have to wait a while for that 🥲

This branch has not been deployed

No deployments
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.

2 participants