-
-
Notifications
You must be signed in to change notification settings - Fork 380
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
feat: graphql lexer #2271
feat: graphql lexer #2271
Conversation
✅ Deploy Preview for biomejs ready!
To edit notification comments on pull requests, go to your Netlify site configuration. |
CodSpeed Performance ReportMerging #2271 will improve performances by 11.8%Comparing Summary
Benchmarks breakdown
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still see two todo!()
, but other than that, LGTM!
Note that for the Grit lexer, I have also just left an unimplemented!()
inside the rewind()
method. That probably works here as well.
} | ||
} | ||
|
||
fn is_name_start(byte: u8) -> bool { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm wondering if we can use some functions from crates/biome_unicode_table/src/lib.rs
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think so, those function support unicode characters while graphql identity only allow ascii
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I looked at the code, and overall, it seems fine, although I found a few things we should avoid doing in production. Would you mind addressing them?
} | ||
|
||
fn rewind(&mut self, _checkpoint: LexerCheckpoint<Self::Kind>) { | ||
todo!(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of todo
, you should use unimplemented!
and provide a brief explanation
|
||
/// Lexes an ellipsis. | ||
fn consume_ellipsis(&mut self) -> GraphqlSyntaxKind { | ||
assert_eq!(self.current_byte(), Some(b'.')); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't want to ship assertion in production. You should use debug_assert_eq
} | ||
|
||
fn consume_digit(&mut self, chr: u8, state: LexNumberState) -> LexNumberState { | ||
assert!(chr.is_ascii_digit()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No assertion in production. You should use debug_assert
} | ||
} | ||
|
||
fn consume_digit(&mut self, chr: u8, state: LexNumberState) -> LexNumberState { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the difference between a digit and a number? It's worth adding a comment that briefly explains it. Also, LexNumberState
should be called LexDigitState
, to avoid confusion.
} | ||
|
||
fn consume_string(&mut self) -> GraphqlSyntaxKind { | ||
assert_eq!(self.current_byte(), Some(b'"')); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use debug_assert
start: TextSize, | ||
state: LexNumberState, | ||
) -> LexNumberState { | ||
assert!(matches!(chr, b'e' | b'E')); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use debug_assert
let diagnostic = ParseDiagnostic::new( | ||
"Invalid escape sequence", | ||
escape_start..self.text_position() + c.text_len(), | ||
).with_hint(r#"Valid escape sequences are: `\\`, `\/`, `/"`, `\b\`, `\f`, `\n`, `\r`, `\t` or any unicode escape sequence `\uXXXX` where X is hexedecimal number. "#); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider using with_alternatives
to present a list of things to the user instead.
(state, Some(diagnostic)) | ||
} | ||
} | ||
_ => panic!("String uninitialized/terminated"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't want to panic in production. Consider using a safe approach, maybe by emitting a diagnostic and/or using consume_unexpecated_character
} | ||
|
||
fn consume_comment(&mut self) -> GraphqlSyntaxKind { | ||
assert_eq!(self.current_byte(), Some(b'#')); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use debug_assert_eq
|
||
#[inline] | ||
fn parse_selection_set(p: &mut GraphqlParser) -> ParsedSyntax { | ||
if !p.at(T!['{']) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to propose removing this conditional because doing so would enable parsing of an invalid syntax without losing the set in the absence of {
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You mean something like this?
fn parse_selection_set(p: &mut GraphqlParser) -> ParsedSyntax {
if !p.eat(T!['{']) {p.error("Expected `{` here")}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes,
I believe that we have 'p.expect(T![token])'🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you check it again e86a331
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good. Just a few nits to address and we can merge it :)
crates/biome_graphql_parser/tests/graphql_test_suite/ok/operation.graphql.snap
Outdated
Show resolved
Hide resolved
5f09443
to
760512d
Compare
let m = p.start(); | ||
p.expect(T!['{']); | ||
SelectionList::new().parse_list(p); | ||
p.bump(T!['}']); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it looks good!
Sorry, I forgot to mention that we also need to use expect
here. It seems we need to check the at/at_ts
token and call bump to ensure that we don't encounter a panic in production.
p.bump(T!['}']); | |
p.expect(T!['}']); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand. I didn't notice bump panic if the token was not found
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🙏
Summary
Implement a Lexer for GraphQL
This PR also include a minimal parser to demonstrate that things work
Test Plan
All the tests for GraphQL lexer and parser pass