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

Make get and set arguments take identifiers instead of strings #203

Merged
merged 1 commit into from
Mar 27, 2023

Conversation

ttencate
Copy link
Contributor

@ttencate ttencate commented Mar 26, 2023

Refactors KvParser to accept some expressions, as long as they don't
contain a comma at the top level outside any pair of [], () or {}.

Also tightens up spans in error messages quite a bit.

Prelude for #199.

@ttencate ttencate force-pushed the feature/get_set_without_quotes branch 2 times, most recently from aac0465 to 3fa7b16 Compare March 26, 2023 12:33
@Bromeon Bromeon added feature Adds functionality to the library c: tooling CI, automation, tools labels Mar 26, 2023
Copy link
Member

@Bromeon Bromeon left a comment

Choose a reason for hiding this comment

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

Thanks a lot, that's quite the refactoring! 💪

Would it be possible to add 1-2 tests with new expressions that were previously unsupported? E.g. Vector2::new(2.5, -1.0) or so. You can also extend existing tests.

bors try

Comment on lines +139 to 142
pub fn handle_any(&mut self, key: &str) -> Option<Option<KvValue>> {
self.map.remove(&ident(key))
}
Copy link
Member

Choose a reason for hiding this comment

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

Why Option<Option>? Should rather be ParseResult, no?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There is no way for this to fail. Updated the doc comments of these handle_* funcs to clarify.

Comment on lines +213 to +227
let errors = self
.map
.keys()
.map(|ident| error(format!("unrecognized key `{ident}`"), ident));
Err(errors
.reduce(|mut a, b| {
a.combine(b);
a
})
.unwrap())
Copy link
Member

Choose a reason for hiding this comment

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

This repeats "unrecognized key" N times in the output, no? Why this change, is it not harder to read?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nicer error spans, pointing to the key in question. The case of multiple unrecognized keys should be pretty rare I think.

struct ParserState<'a> {
tokens: std::slice::Iter<'a, TokenTree>,
prev: Option<&'a TokenTree>,
curr: Option<&'a TokenTree>,
Copy link
Member

Choose a reason for hiding this comment

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

Typical abbreviation for "current" is "cur", see e.g. CurDir.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

Comment on lines 281 to 326
fn parse_opt_value(&mut self) -> ParseResult<Option<KvValue>> {
Ok(match self.curr {
// End of input directly after a key
None => None,
// Comma following key
Some(tt) if is_punct(tt, ',') => {
self.next();
None
}
// Equals sign following key
Some(tt) if is_punct(tt, '=') => {
self.next();
Some(self.parse_value()?)
}
Some(tt) => {
return bail("expected `=` or `)`", tt);
}
})
}
Copy link
Member

Choose a reason for hiding this comment

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

I repeatedly find myself confused about an all-surrounding Ok(...) and wonder why returning Result in the first place 😬

Could you avoid ? inside Ok(...)? Just split the expressions, like:

let result = match self.cur { 
    ...
};

Ok(result)

That makes clear that only if you reach the last line, Ok is being returned.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure!

Comment on lines 451 to 461
impl PartialEq for KvValue {
fn eq(&self, other: &Self) -> bool {
let to_strings = |kv: &Self| {
kv.tokens
.iter()
.map(ToString::to_string)
.collect::<Vec<String>>()
};
to_strings(self) == to_strings(other)
}
}
Copy link
Member

Choose a reason for hiding this comment

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

This is a very expensive comparison:

  • allocate N + M strings, for each token on either side
  • copy all strings into two vectors

Where is this needed? Is there no way to compare tokens directly?

Copy link
Member

Choose a reason for hiding this comment

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

Also, why PartialEq and not also Eq, is there no total equality?

Copy link
Contributor Author

@ttencate ttencate Mar 27, 2023

Choose a reason for hiding this comment

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

Neither TokenTree nor TokenStream implements even PartialEq unfortunately.

Note that this is in a #[cfg(test)] block. But I'll move it outside to make it more discoverable, annotate it with #[cfg(test)], and add a comment.

We don't need Eq, whether it's semantically correct or not.

Copy link
Member

Choose a reason for hiding this comment

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

I see. But why move it outside if it's only used inside #[cfg(test)]?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It makes sense to have all trait implementations for a type together in the file, I think. Let me know if you'd prefer it to be moved back, I don't care much either way.

Copy link
Member

Choose a reason for hiding this comment

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

Agree, but in this case it probably makes sense to have "test-only code" together -- since it's not really a trait that can be used elsewhere with the type.

Some(self.parse_value()?)
}
Some(tt) => {
return bail("expected `=` or `)`", tt);
Copy link
Member

@Bromeon Bromeon Mar 26, 2023

Choose a reason for hiding this comment

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

Error message is a bit less expressive than before:

  • maybe mention that = or ) are expected after a key
  • also, , is a possible token after a key

Copy link
Contributor Author

@ttencate ttencate Mar 27, 2023

Choose a reason for hiding this comment

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

Added some extra code to give a more helpful error in case the preceding expression was nontrivial (more than one token tree). Edit: also reworded the error message to be less about tokens.

Copy link
Member

Choose a reason for hiding this comment

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

Awesome, thanks for clarification 👍

(
$($key:expr => $value:expr),*
$(,)?
) => {
{
let mut map = std::collections::HashMap::new();
$(
map.insert($key, $value);
map.insert(ident($key), $value);
Copy link
Member

Choose a reason for hiding this comment

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

Nice change! 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We can do even better: since the keys are always identifiers, we don't even need the quotes.

bors bot added a commit that referenced this pull request Mar 26, 2023
@bors
Copy link
Contributor

bors bot commented Mar 26, 2023

try

Build succeeded:

@ttencate
Copy link
Contributor Author

Would it be possible to add 1-2 tests with new expressions that were previously unsupported? E.g. Vector2::new(2.5, -1.0) or so. You can also extend existing tests.

Sure thing, added a couple of tests and rearranged the existing ones for better coverage.

@ttencate
Copy link
Contributor Author

All done in a new commit. Let me know when it's ready to squash.

godot-macros/src/util.rs Outdated Show resolved Hide resolved
Comment on lines 451 to 461
impl PartialEq for KvValue {
fn eq(&self, other: &Self) -> bool {
let to_strings = |kv: &Self| {
kv.tokens
.iter()
.map(ToString::to_string)
.collect::<Vec<String>>()
};
to_strings(self) == to_strings(other)
}
}
Copy link
Member

Choose a reason for hiding this comment

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

Agree, but in this case it probably makes sense to have "test-only code" together -- since it's not really a trait that can be used elsewhere with the type.

Copy link
Member

@Bromeon Bromeon left a comment

Choose a reason for hiding this comment

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

Let me know when it's ready to squash.

GitHub now has a feature where it lets you compare changes even for force-pushes:

grafik

So if that simplifies your workflow, you can keep force-pushing.

@ttencate ttencate force-pushed the feature/get_set_without_quotes branch 2 times, most recently from c0a80b3 to 14682fe Compare March 27, 2023 09:14
Refactors `KvParser` to accept some expressions, as long as they don't
contain a comma at the top level outside any pair of `[]`, `()` or `{}`.

Also tightens up spans in error messages quite a bit.
@ttencate ttencate force-pushed the feature/get_set_without_quotes branch from 14682fe to 1b32155 Compare March 27, 2023 09:20
Copy link
Member

@Bromeon Bromeon left a comment

Choose a reason for hiding this comment

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

Thanks a lot!

bors r+

@bors
Copy link
Contributor

bors bot commented Mar 27, 2023

Build succeeded:

@bors bors bot merged commit c1c2d60 into godot-rust:master Mar 27, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c: tooling CI, automation, tools feature Adds functionality to the library
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants