feat(oop): METHOD ... INLINE <expr> and MESSAGE handlers
Harbour's inline-method sugar was parsed but the body was skipped, leaving any `METHOD X() INLINE expr` declaration registered in the class vtable with no matching HB_<CLASS>_X function — link error at build time. Parser: MethodDecl gains an InlineBody Expr field. parseClassMethodDecl captures the expression after INLINE instead of skipping to EOL. New parseMessageDecl handles `MESSAGE <name> [(params)] INLINE expr` and returns the same MethodDecl shape. Codegen: emitClassDecl walks members a second time after the class registration init block and emits emitInlineMethodBody for each IsInline method — a Frame(nParams, 0) + emitExpr(InlineBody) + RetValue function. curMethodClass is bound so ::super: inside an inline body still resolves. Tested (/tmp/test_inline.prg): all four patterns — bare INLINE, MESSAGE INLINE, INLINE with params, INLINE reading ::field — produce expected values. 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:
@@ -74,6 +74,18 @@ func (g *Generator) emitClassDecl(cls *ast.ClassDecl) {
|
||||
g.writeln("}")
|
||||
g.writeln("")
|
||||
|
||||
// Emit function bodies for INLINE methods — the class-body form
|
||||
// `METHOD X() INLINE expr` / `MESSAGE X INLINE expr` carries its
|
||||
// own body, unlike plain METHOD declarations which expect a
|
||||
// standalone `METHOD X() CLASS Foo` implementation elsewhere.
|
||||
for _, m := range cls.Members {
|
||||
md, ok := m.(*ast.MethodDecl)
|
||||
if !ok || !md.IsInline || md.InlineBody == nil {
|
||||
continue
|
||||
}
|
||||
g.emitInlineMethodBody(className, md)
|
||||
}
|
||||
|
||||
// Also need a constructor function: Person() returns new object
|
||||
// This is called as Person():New(...)
|
||||
g.writeln(fmt.Sprintf("func HB_%s_CTOR(t *hbrt.Thread) {", className))
|
||||
@@ -89,6 +101,40 @@ func (g *Generator) emitClassDecl(cls *ast.ClassDecl) {
|
||||
// Constructor symbol already added in Generate() symbol collection phase
|
||||
}
|
||||
|
||||
// emitInlineMethodBody generates the HB_<CLASS>_<METHOD> function for
|
||||
// an INLINE-declared method: the body is the single expression parsed
|
||||
// after the INLINE keyword, evaluated and returned. Params bind to
|
||||
// locals 1..N so the inline expression can reference them.
|
||||
func (g *Generator) emitInlineMethodBody(className string, md *ast.MethodDecl) {
|
||||
methodName := strings.ToUpper(md.Name)
|
||||
goFuncName := fmt.Sprintf("HB_%s_%s", className, methodName)
|
||||
nParams := len(md.Params)
|
||||
|
||||
g.writeln(fmt.Sprintf("func %s(t *hbrt.Thread) {", goFuncName))
|
||||
g.indent++
|
||||
g.writeln(fmt.Sprintf("t.Frame(%d, 0)", nParams))
|
||||
g.writeln("defer t.EndProc()")
|
||||
|
||||
// Param name → local index map so the inline expr can reference them.
|
||||
localMap := make(localMap)
|
||||
for i, p := range md.Params {
|
||||
localMap[strings.ToUpper(p.Name)] = i + 1
|
||||
}
|
||||
prevLocals := g.curLocals
|
||||
prevCls := g.curMethodClass
|
||||
g.curLocals = localMap
|
||||
g.curMethodClass = className
|
||||
|
||||
g.emitExpr(md.InlineBody)
|
||||
g.writeln("t.RetValue()")
|
||||
|
||||
g.curLocals = prevLocals
|
||||
g.curMethodClass = prevCls
|
||||
g.indent--
|
||||
g.writeln("}")
|
||||
g.writeln("")
|
||||
}
|
||||
|
||||
// emitMethodDeclStandalone generates a standalone METHOD ... CLASS ... implementation.
|
||||
func (g *Generator) emitMethodDeclStandalone(md *ast.MethodDecl) {
|
||||
if md.ClassName == "" {
|
||||
|
||||
Reference in New Issue
Block a user