Skip to content

Commit 09cb20c

Browse files
git-hulkclaude
andcommitted
Rename parseIdentAnyKeyword to parseAnyKeyword, add kindClass bit set
TokenKind stays a string (its values are woven into the public AST and error messages), so multi-kind membership checks get an internal bit set instead: kindClass with classIdent/classKeyword bits and a matchTokenKindIn(classIdent | classKeyword) helper, replacing the repeated two-comparison ident-or-keyword checks. Unlike matchTokenKind, matchTokenKindIn is a raw kind check with no keyword-to-identifier coercion. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 5b71ae5 commit 09cb20c

7 files changed

Lines changed: 54 additions & 22 deletions

File tree

parser/keyword.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ const (
266266
// Every other keyword is non-reserved and is accepted anywhere an identifier
267267
// is expected (see Parser.matchTokenKind). Positions where even a reserved
268268
// keyword is provably used as a name — after AS, after a dot in a qualified
269-
// name, or a lookahead-disambiguated select item — use parseIdentAnyKeyword.
269+
// name, or a lookahead-disambiguated select item — use parseAnyKeyword.
270270
//
271271
// Keywords that double as ClickHouse function or engine names (IF, LEFT,
272272
// RIGHT, ANY, MIN, MAX, TRIM, SET, JOIN...) must stay non-reserved.

parser/lexer.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,31 @@ const (
5757
type Pos int
5858
type TokenKind string
5959

60+
// kindClass is a bit set over token kinds, enabling multi-kind membership
61+
// checks in a single expression, e.g. matchTokenKindIn(classIdent | classKeyword).
62+
// TokenKind itself stays a string (its values are woven into the public AST
63+
// and error messages), so kinds that participate in such checks get a class
64+
// bit here instead.
65+
type kindClass uint8
66+
67+
const (
68+
classIdent kindClass = 1 << iota
69+
classKeyword
70+
)
71+
72+
// class maps a TokenKind to its kindClass bit; kinds without a class
73+
// (operators, punctuation, EOF) map to 0.
74+
func (k TokenKind) class() kindClass {
75+
switch k {
76+
case TokenKindIdent:
77+
return classIdent
78+
case TokenKindKeyword:
79+
return classKeyword
80+
default:
81+
return 0
82+
}
83+
}
84+
6085
type Token struct {
6186
Pos Pos
6287
End Pos

parser/parse_system.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1280,7 +1280,7 @@ func (p *Parser) parseGrantOption(_ Pos) (string, error) {
12801280
}
12811281
// Between WITH and OPTION the token can only be the option name, which may
12821282
// be a reserved keyword (e.g. `WITH GRANT OPTION`).
1283-
ident, err := p.parseIdentAnyKeyword()
1283+
ident, err := p.parseAnyKeyword()
12841284
if err != nil {
12851285
return "", err
12861286
}

parser/parser_column.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -152,10 +152,10 @@ func (p *Parser) parseInfix(expr Expr, precedence int) (Expr, error) {
152152
// access column with dot notation
153153
var rightExpr Expr
154154
var err error
155-
if p.lastTokenKind() == TokenKindIdent || p.lastTokenKind() == TokenKindKeyword {
155+
if p.matchTokenKindIn(classIdent | classKeyword) {
156156
// After a dot the token can only be a member name, so even
157157
// reserved keywords are accepted (e.g. `t.from`).
158-
rightExpr, err = p.parseIdentAnyKeyword()
158+
rightExpr, err = p.parseAnyKeyword()
159159
} else {
160160
rightExpr, err = p.parseDecimal(p.Pos())
161161
}
@@ -474,7 +474,7 @@ func (p *Parser) parseColumnExpr(pos Pos) (Expr, error) { //nolint:funlen
474474
// terminator/alias check).
475475
if p.keywordIsSelectItemIdentifier() ||
476476
(p.matchTokenKind(TokenKindKeyword) && p.peekIsEndOfStatement()) {
477-
return p.parseIdentAnyKeyword()
477+
return p.parseAnyKeyword()
478478
}
479479
switch {
480480
case p.matchKeyword(KeywordInterval):
@@ -681,7 +681,7 @@ func (p *Parser) parseFunctionExpr(_ Pos) (*FunctionExpr, error) {
681681
// parse function name; callers gate entry (select-item modifiers match
682682
// EXCEPT/APPLY/REPLACE first, INSERT INTO FUNCTION follows the FUNCTION
683683
// keyword), so even reserved keywords are valid names here.
684-
name, err := p.parseIdentAnyKeyword()
684+
name, err := p.parseAnyKeyword()
685685
if err != nil {
686686
return nil, err
687687
}
@@ -800,7 +800,7 @@ func (p *Parser) parseQueryParam(pos Pos) (*QueryParam, error) {
800800

801801
// Inside `{name:Type}` the token can only be the parameter name, so even
802802
// reserved keywords are accepted (e.g. `{end:UInt32}`).
803-
ident, err := p.parseIdentAnyKeyword()
803+
ident, err := p.parseAnyKeyword()
804804
if err != nil {
805805
return nil, err
806806
}
@@ -885,7 +885,7 @@ func (p *Parser) parseSelectItem() (*SelectItem, error) {
885885
case p.tryConsumeKeywords(KeywordAs):
886886
// `SELECT 1 AS <keyword>` works for any keyword, reserved or not:
887887
// after AS the token can only be an alias name.
888-
alias, err = p.parseIdentAnyKeyword()
888+
alias, err = p.parseAnyKeyword()
889889
if err != nil {
890890
return nil, err
891891
}

parser/parser_common.go

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,13 @@ func (p *Parser) matchTokenKind(kind TokenKind) bool {
7777
!reservedKeywords.Contains(strings.ToUpper(p.last().String))
7878
}
7979

80+
// matchTokenKindIn reports whether the current token's kind is in the given
81+
// class set, e.g. matchTokenKindIn(classIdent | classKeyword). Unlike
82+
// matchTokenKind it is a raw kind check: no keyword-to-identifier coercion.
83+
func (p *Parser) matchTokenKindIn(classes kindClass) bool {
84+
return p.lastTokenKind().class()&classes != 0
85+
}
86+
8087
// expectTokenKind consumes the last token if it is the given kind.
8188
func (p *Parser) expectTokenKind(kind TokenKind) error {
8289
if lastToken := p.tryConsumeTokenKind(kind); lastToken != nil {
@@ -149,14 +156,14 @@ func (p *Parser) tryParseIdent() *Ident {
149156
}
150157
}
151158

152-
// parseIdentAnyKeyword parses the current token as an identifier, accepting
159+
// parseAnyKeyword parses the current token as an identifier, accepting
153160
// any keyword token — reserved or not — as the name. Use it only in positions
154161
// where context has already proven the token is a name and not the start of a
155162
// clause or expression: after AS, after a dot in a qualified name, or a select
156163
// item the lookahead disambiguated.
157-
func (p *Parser) parseIdentAnyKeyword() (*Ident, error) {
164+
func (p *Parser) parseAnyKeyword() (*Ident, error) {
158165
last := p.last()
159-
if last == nil || (last.Kind != TokenKindIdent && last.Kind != TokenKindKeyword) {
166+
if !p.matchTokenKindIn(classIdent | classKeyword) {
160167
return nil, &ParseError{
161168
Pos: p.Pos(),
162169
Got: last,
@@ -227,7 +234,7 @@ func (p *Parser) tryParseDotIdent(_ Pos) (*Ident, error) {
227234
}
228235
// After a dot the token can only be a member name, so even reserved
229236
// keywords are accepted (e.g. `db.from`, `t.limit`).
230-
return p.parseIdentAnyKeyword()
237+
return p.parseAnyKeyword()
231238
}
232239

233240
func (p *Parser) tryParseDotIdentOrString(_ Pos) (*Ident, error) {
@@ -237,7 +244,7 @@ func (p *Parser) tryParseDotIdentOrString(_ Pos) (*Ident, error) {
237244
// After a dot the token can only be a member name, so even reserved
238245
// keywords are accepted (e.g. `db.from`).
239246
if p.lastTokenKind() == TokenKindKeyword {
240-
return p.parseIdentAnyKeyword()
247+
return p.parseAnyKeyword()
241248
}
242249
return p.parseIdentOrString()
243250
}

parser/parser_query.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -401,7 +401,7 @@ func (p *Parser) parseTableExpr(pos Pos) (*TableExpr, error) {
401401
if p.tryConsumeKeywords(KeywordAs) {
402402
// After AS the token can only be an alias name, so even reserved
403403
// keywords are accepted (e.g. `FROM t AS from`).
404-
alias, err := p.parseIdentAnyKeyword()
404+
alias, err := p.parseAnyKeyword()
405405
if err != nil {
406406
return nil, err
407407
}
@@ -831,7 +831,7 @@ func (p *Parser) parseWindowCondition(pos Pos) (*WindowExpr, error) {
831831
// canParseWindowNameInParens already disambiguated keyword tokens
832832
// (e.g. `OVER (order)` vs `OVER (ORDER BY ...)`).
833833
var err error
834-
windowName, err = p.parseIdentAnyKeyword()
834+
windowName, err = p.parseAnyKeyword()
835835
if err != nil {
836836
return nil, err
837837
}
@@ -863,10 +863,10 @@ func (p *Parser) parseWindowCondition(pos Pos) (*WindowExpr, error) {
863863
}
864864

865865
func (p *Parser) canParseWindowNameInParens() bool {
866-
if p.lastTokenKind() != TokenKindIdent && p.lastTokenKind() != TokenKindKeyword {
866+
if !p.matchTokenKindIn(classIdent | classKeyword) {
867867
return false
868868
}
869-
if p.lastTokenKind() != TokenKindKeyword {
869+
if !p.matchTokenKindIn(classKeyword) {
870870
return true
871871
}
872872

@@ -899,7 +899,7 @@ func (p *Parser) parseWindowClause(pos Pos) (*WindowClause, error) {
899899
for {
900900
// After WINDOW (or a comma) the token can only be a window name, so
901901
// even reserved keywords are accepted (e.g. `WINDOW order AS (...)`).
902-
windowName, err := p.parseIdentAnyKeyword()
902+
windowName, err := p.parseAnyKeyword()
903903
if err != nil {
904904
return nil, err
905905
}

parser/parser_table.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -428,11 +428,11 @@ func (p *Parser) parseIdentOrFunction(_ Pos) (Expr, error) {
428428
if p.tryConsumeKeywords(KeywordOver) {
429429
var overExpr Expr
430430
switch {
431-
case p.matchTokenKind(TokenKindIdent), p.lastTokenKind() == TokenKindKeyword:
431+
case p.matchTokenKindIn(classIdent | classKeyword):
432432
// After OVER a bare token can only be a window name, so even
433433
// reserved keywords are accepted (e.g. `OVER order`),
434434
// mirroring the WINDOW clause definition side.
435-
overExpr, err = p.parseIdentAnyKeyword()
435+
overExpr, err = p.parseAnyKeyword()
436436
case p.matchTokenKind(TokenKindLParen):
437437
overExpr, err = p.parseWindowCondition(p.Pos())
438438
if err != nil {
@@ -454,12 +454,12 @@ func (p *Parser) parseIdentOrFunction(_ Pos) (Expr, error) {
454454
return funcExpr, nil
455455
case p.tryConsumeTokenKind(TokenKindDot) != nil:
456456
switch {
457-
case p.matchTokenKind(TokenKindIdent), p.lastTokenKind() == TokenKindKeyword:
457+
case p.matchTokenKindIn(classIdent | classKeyword):
458458
fields := []*Ident{ident}
459459
for {
460460
// After a dot the token can only be a member name, so even
461461
// reserved keywords are accepted (e.g. `t.from`).
462-
child, err := p.parseIdentAnyKeyword()
462+
child, err := p.parseAnyKeyword()
463463
if err != nil {
464464
return nil, err
465465
}

0 commit comments

Comments
 (0)