From 0a5482b6aae7164bd46185b7efe64c05681decba Mon Sep 17 00:00:00 2001 From: CharlesKWON Date: Sat, 18 Apr 2026 16:52:23 +0900 Subject: [PATCH] feat(parser): implicit class binding for standalone METHOD bodies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Classic Clipper/Harbour form writes method implementations as bare `METHOD Name(params)` statements following a `CLASS X ... ENDCLASS` declaration, with the binding inferred from the most recent class: CREATE CLASS Shape METHOD Area ENDCLASS METHOD Area -- binds to Shape RETURN 0 Five was requiring `METHOD Area CLASS Shape` explicitly. Without it, parseMethodDecl left MethodDecl.ClassName empty, gengo skipped the body emission, and the link step failed with `undefined: HB_SHAPE_AREA`. The class registration had AddMethod("AREA", HB_SHAPE_AREA) pointing at the missing symbol. Parser tracks p.lastClassName at parseClassDecl, and parseMethodDecl falls back to that value when no CLASS clause is supplied. Each new CLASS declaration updates the tracker, so multi-class files still dispatch correctly — verified with /tmp/test_implicit_class.prg (Shape + Box both resolve their own Name/Area methods). Unblocks harbour-core/tests/clsscope.prg and other OOP compat tests that use this form. FiveSql2 43/43, Harbour compat 56/56, Go test ALL PASS. Co-Authored-By: Claude Opus 4.7 (1M context) --- compiler/parser/parser.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/compiler/parser/parser.go b/compiler/parser/parser.go index 1c30cb5..144bb2c 100644 --- a/compiler/parser/parser.go +++ b/compiler/parser/parser.go @@ -32,6 +32,12 @@ type Parser struct { errors []Error file string GoDumps []string // inline Go code blocks from PP + // lastClassName tracks the most recent `CLASS X` / `CREATE CLASS X` + // declaration at file scope. Standalone `METHOD foo(...)` decls + // without an explicit `CLASS Y` clause default to this name — + // matches classic Clipper/Harbour where method bodies follow the + // class declaration and bind implicitly. + lastClassName string } // Error represents a parse error with position. @@ -522,6 +528,7 @@ func (p *Parser) parseVarDecl() *ast.VarDecl { func (p *Parser) parseClassDecl() *ast.ClassDecl { classPos := p.expect(token.CLASS).Pos name := p.expect(token.IDENT).Literal + p.lastClassName = name var parent string if p.match(token.INHERIT) { @@ -1020,6 +1027,12 @@ func (p *Parser) parseMethodDecl() *ast.MethodDecl { var className string if p.match(token.CLASS) { className = p.expect(token.IDENT).Literal + } else if p.lastClassName != "" { + // Implicit binding to the most recent `CREATE CLASS` / `CLASS` + // declaration — classic Clipper/Harbour form. Real code uses + // `CREATE CLASS CLSX ... ENDCLASS` followed by a bare + // `METHOD TestX() ... RETURN Self` without restating the class. + className = p.lastClassName } p.expectEndOfStmt()