diff --git a/compiler/parser/parser.go b/compiler/parser/parser.go index b35f205..1c30cb5 100644 --- a/compiler/parser/parser.go +++ b/compiler/parser/parser.go @@ -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.)