feat(parser): keyword-as-identifier at stmt-block boundaries
Harbour permits keywords (CASE, DO, WHILE, etc.) to be used as variable/array names. In most expression contexts Five already handles this via expr.go:362 which whitelists keywords when used as bare identifiers. But parseStmtBlock was stopping on any stop token unconditionally, so a line like case[ n ] := x -- 'case' is a LOCAL array terminated the enclosing stmt block at `case` and left `[ n ] := x` unparsable. Add isIdentSuffix(): peeks one ahead and reports whether the next token is something that can only follow an identifier ([, :=, +=, -=, *=, /=, %=, ^=, ++, --, :, .). parseStmtBlock now treats the stop token as a statement-start when its suffix matches, so the block keeps going. Verified with /tmp/test_kwident.prg (`case[...]` outside DO CASE, `arr[...]` inside DO CASE body), /tmp/test_kwident2.prg (both the `case case[n] == "two"` arm and `case[1] := "updated"` assignment after ENDCASE). Pathological harbour-core/tests/keywords.prg still fails — it places `case[...]` in the arm-expected position of a DO CASE block with no leading arm, which no sane parser can disambiguate. FiveSql2 43/43, Harbour compat 56/56, Go test ALL PASS. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1069,6 +1069,21 @@ func (p *Parser) parseMethodDecl() *ast.MethodDecl {
|
||||
|
||||
// --- Statement parsing ---
|
||||
|
||||
// isIdentSuffix reports whether the given token could follow an
|
||||
// identifier, signalling that a keyword on the prior slot is being
|
||||
// used as a variable name (array index, method call, assignment,
|
||||
// etc.). Used by parseStmtBlock to avoid prematurely ending a
|
||||
// statement block when a keyword identifier appears.
|
||||
func isIdentSuffix(k token.Kind) bool {
|
||||
switch k {
|
||||
case token.LBRACKET, token.ASSIGN, token.PLUSEQ, token.MINUSEQ,
|
||||
token.STAREQ, token.SLASHEQ, token.PERCENTEQ, token.POWEREQ,
|
||||
token.INC, token.DEC, token.COLON, token.DOT:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// parseStmtBlock parses statements until one of the stop tokens.
|
||||
func (p *Parser) parseStmtBlock(stopTokens ...token.Kind) []ast.Stmt {
|
||||
var stmts []ast.Stmt
|
||||
@@ -1084,6 +1099,14 @@ func (p *Parser) parseStmtBlock(stopTokens ...token.Kind) []ast.Stmt {
|
||||
p.peekAt(1) == token.ASSIGN {
|
||||
break // continue parsing as statement
|
||||
}
|
||||
// Don't stop at a keyword-used-as-identifier. Harbour
|
||||
// allows keywords like CASE/DO as variable names, so
|
||||
// `case[idx]`, `case := 1`, `case:method()` are
|
||||
// expression statements, not new block arms. Peek the
|
||||
// next token for identifier-ish suffixes.
|
||||
if isIdentSuffix(p.peekAt(1)) {
|
||||
break // treat current token as identifier, parse as stmt
|
||||
}
|
||||
return stmts
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user