fix(parser): DATA x, y, z registers every name — not just the first
Harbour's `DATA name1, name2, name3` (and `VAR`, `CLASSDATA`) should declare every listed field. Five's parseDataDecl instead returned a single DataDecl for the first name and silently dropped the rest — the comma branch just consumed the identifier without producing a new decl. Surfaced by the OPERATOR overloading test (/tmp/test_operator.prg originally had `DATA x, y` for a Vec2 class) where later `::y` access panicked with "unknown method y". Change the signature to `[]*ast.DataDecl` and rewrite the loop so each comma closes the current decl and starts a fresh one. AS / INIT / qualifier runs still attach to the most recent name, so: DATA x, y, z → three decls, no init DATA x INIT 10, y, z INIT 0 → init attaches to preceding name DATA cName AS CHARACTER → typed single decl All seven class-body call sites flatten the slice into `members`. Verified with /tmp/test_multidata.prg (`DATA x, y, z` + mixed `DATA label INIT "origin", count INIT 0`) and the OPERATOR test which now passes with the original `DATA x, y` form restored. 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:
@@ -543,7 +543,7 @@ func (p *Parser) parseClassDecl() *ast.ClassDecl {
|
||||
for !p.atAny(token.ENDCLASS, token.END, token.EOF) {
|
||||
switch p.current.Kind {
|
||||
case token.DATA:
|
||||
members = append(members, p.parseDataDecl())
|
||||
for _, dd := range p.parseDataDecl() { members = append(members, dd) }
|
||||
case token.METHOD:
|
||||
members = append(members, p.parseClassMethodDecl())
|
||||
case token.ACCESS:
|
||||
@@ -554,13 +554,13 @@ func (p *Parser) parseClassDecl() *ast.ClassDecl {
|
||||
// CLASS VAR / CLASS METHOD / CLASS DATA inside class body
|
||||
p.advance() // skip CLASS
|
||||
if p.current.Kind == token.DATA {
|
||||
members = append(members, p.parseDataDecl())
|
||||
for _, dd := range p.parseDataDecl() { members = append(members, dd) }
|
||||
} else if p.current.Kind == token.METHOD {
|
||||
members = append(members, p.parseClassMethodDecl())
|
||||
} else if p.current.Kind == token.IDENT && p.currentUpper() == "VAR" {
|
||||
p.tokens[p.pos].Kind = token.DATA
|
||||
p.current = p.tokens[p.pos]
|
||||
members = append(members, p.parseDataDecl())
|
||||
for _, dd := range p.parseDataDecl() { members = append(members, dd) }
|
||||
} else {
|
||||
p.skipToEndOfLine()
|
||||
}
|
||||
@@ -588,7 +588,7 @@ func (p *Parser) parseClassDecl() *ast.ClassDecl {
|
||||
// Rewrite as DATA token
|
||||
p.tokens[p.pos].Kind = token.DATA
|
||||
p.current = p.tokens[p.pos]
|
||||
members = append(members, p.parseDataDecl())
|
||||
for _, dd := range p.parseDataDecl() { members = append(members, dd) }
|
||||
} else if (upper == "PROTECTED" || upper == "EXPORTED" || upper == "HIDDEN" ||
|
||||
upper == "VISIBLE" || upper == "EXPORT" || upper == "SYNC") &&
|
||||
p.peekAt(1) == token.COLON {
|
||||
@@ -600,16 +600,16 @@ func (p *Parser) parseClassDecl() *ast.ClassDecl {
|
||||
// CLASSDATA / CLASSVAR — class-level variable (treat as DATA)
|
||||
p.tokens[p.pos].Kind = token.DATA
|
||||
p.current = p.tokens[p.pos]
|
||||
members = append(members, p.parseDataDecl())
|
||||
for _, dd := range p.parseDataDecl() { members = append(members, dd) }
|
||||
} else if upper == "CLASS" {
|
||||
// CLASS VAR — class-level variable
|
||||
p.advance() // skip CLASS
|
||||
if p.current.Kind == token.IDENT && p.currentUpper() == "VAR" {
|
||||
p.tokens[p.pos].Kind = token.DATA
|
||||
p.current = p.tokens[p.pos]
|
||||
members = append(members, p.parseDataDecl())
|
||||
for _, dd := range p.parseDataDecl() { members = append(members, dd) }
|
||||
} else if p.current.Kind == token.DATA {
|
||||
members = append(members, p.parseDataDecl())
|
||||
for _, dd := range p.parseDataDecl() { members = append(members, dd) }
|
||||
} else if p.current.Kind == token.METHOD {
|
||||
members = append(members, p.parseClassMethodDecl())
|
||||
} else {
|
||||
@@ -662,25 +662,46 @@ func (p *Parser) parseClassDecl() *ast.ClassDecl {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Parser) parseDataDecl() *ast.DataDecl {
|
||||
// parseDataDecl parses `DATA name [AS type] [INIT expr] [qualifiers]`,
|
||||
// with optional comma-separated additional names each carrying their
|
||||
// own AS / INIT / qualifier run — Harbour syntax:
|
||||
//
|
||||
// DATA x, y, z -- three decls, no init
|
||||
// DATA x INIT 10, y, z INIT 0 -- init attaches to preceding name
|
||||
// DATA cName AS CHARACTER INIT "" -- typed single decl
|
||||
//
|
||||
// Returns the full list; callers flatten it into the class member
|
||||
// slice. Previously only the first name survived — the loop skipped
|
||||
// extra names after a comma without creating decls for them.
|
||||
func (p *Parser) parseDataDecl() []*ast.DataDecl {
|
||||
dataPos := p.expect(token.DATA).Pos
|
||||
name := p.expectMethodName().Literal // allow keywords as data names
|
||||
|
||||
var init ast.Expr
|
||||
var asType string
|
||||
current := &ast.DataDecl{
|
||||
DataPos: dataPos,
|
||||
Name: p.expectMethodName().Literal,
|
||||
}
|
||||
out := []*ast.DataDecl{}
|
||||
|
||||
// Parse AS, INIT, commas, and qualifiers in any order
|
||||
for p.current.Kind != token.NEWLINE && p.current.Kind != token.EOF {
|
||||
if p.match(token.AS) {
|
||||
if p.current.Kind == token.IDENT || p.current.Literal != "" {
|
||||
p.advance() // type name
|
||||
current.AsType = p.current.Literal
|
||||
p.advance()
|
||||
}
|
||||
continue
|
||||
}
|
||||
if p.match(token.COMMA) {
|
||||
// VAR One, Two, Three — skip additional names
|
||||
out = append(out, current)
|
||||
if p.current.Kind == token.IDENT || p.current.Literal != "" {
|
||||
p.advance() // skip additional name
|
||||
current = &ast.DataDecl{
|
||||
DataPos: p.current.Pos,
|
||||
Name: p.current.Literal,
|
||||
}
|
||||
p.advance()
|
||||
} else {
|
||||
// Trailing comma or missing name — bail cleanly.
|
||||
current = nil
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -688,7 +709,7 @@ func (p *Parser) parseDataDecl() *ast.DataDecl {
|
||||
upper := p.currentUpper()
|
||||
if upper == "INIT" {
|
||||
p.advance()
|
||||
init = p.parseExpr()
|
||||
current.Init = p.parseExpr()
|
||||
continue
|
||||
}
|
||||
// Skip visibility/attribute qualifiers
|
||||
@@ -705,9 +726,12 @@ func (p *Parser) parseDataDecl() *ast.DataDecl {
|
||||
}
|
||||
break
|
||||
}
|
||||
if current != nil {
|
||||
out = append(out, current)
|
||||
}
|
||||
p.expectEndOfStmt()
|
||||
|
||||
return &ast.DataDecl{DataPos: dataPos, Name: name, Init: init, AsType: asType}
|
||||
return out
|
||||
}
|
||||
|
||||
// expectMethodName: method names can be keywords (end, home, left, right, etc.)
|
||||
|
||||
Reference in New Issue
Block a user