diff --git a/compiler/gengo/gengo.go b/compiler/gengo/gengo.go index f34c0da..8835dae 100644 --- a/compiler/gengo/gengo.go +++ b/compiler/gengo/gengo.go @@ -175,6 +175,9 @@ func (g *Generator) emitHeader() { g.writeln("var _ = hbrdd.NewWorkAreaManager") g.writeln("var _ dbf.DBFDriver") } + if g.imports["strings"] { + g.writeln("var _ = strings.TrimLeft") + } g.writeln("") } @@ -1456,7 +1459,11 @@ func (g *Generator) emitCall(e *ast.CallExpr) { } } + // Inline hot-path RTL functions — skip Frame/EndProc/VM dispatch entirely if ident, ok := e.Func.(*ast.IdentExpr); ok { + if g.tryEmitInlineRTL(ident.Name, e.Args) { + return + } g.writeln(fmt.Sprintf("t.PushSymbol(t.VM().FindSymbol(%q))", strings.ToUpper(ident.Name))) } else { g.emitExpr(e.Func) @@ -1468,6 +1475,76 @@ func (g *Generator) emitCall(e *ast.CallExpr) { g.writeln(fmt.Sprintf("t.Function(%d)", len(e.Args))) } +// tryEmitInlineRTL emits direct Go code for known RTL functions. +// Returns true if handled (no VM dispatch needed). +// This eliminates Frame/EndProc/symbol lookup for hot-path functions. +func (g *Generator) tryEmitInlineRTL(name string, args []ast.Expr) bool { + upper := strings.ToUpper(name) + switch upper { + case "LTRIM": + if len(args) == 1 { + g.emitExpr(args[0]) + g.writeln("{ _s := t.Pop2().AsString(); t.PushString(strings.TrimLeft(_s, \" \")) }") + g.imports["strings"] = true + return true + } + case "RTRIM", "TRIM": + if len(args) == 1 { + g.emitExpr(args[0]) + g.writeln("{ _s := t.Pop2().AsString(); t.PushString(strings.TrimRight(_s, \" \")) }") + g.imports["strings"] = true + return true + } + case "ALLTRIM": + if len(args) == 1 { + g.emitExpr(args[0]) + g.writeln("{ _s := t.Pop2().AsString(); t.PushString(strings.TrimSpace(_s)) }") + g.imports["strings"] = true + return true + } + case "UPPER": + if len(args) == 1 { + g.emitExpr(args[0]) + g.writeln("{ _s := t.Pop2().AsString(); t.PushString(strings.ToUpper(_s)) }") + g.imports["strings"] = true + return true + } + case "LOWER": + if len(args) == 1 { + g.emitExpr(args[0]) + g.writeln("{ _s := t.Pop2().AsString(); t.PushString(strings.ToLower(_s)) }") + g.imports["strings"] = true + return true + } + case "LEN": + if len(args) == 1 { + g.emitExpr(args[0]) + g.writeln("{ _v := t.Pop2(); if _v.IsString() { t.PushInt(len(_v.AsString())) } else if _v.IsArray() { t.PushInt(len(_v.AsArray().Items)) } else { t.PushInt(0) } }") + return true + } + case "EMPTY": + if len(args) == 1 { + g.emitExpr(args[0]) + g.writeln("{ _v := t.Pop2(); t.PushBool(_v.IsNil() || (_v.IsString() && len(strings.TrimSpace(_v.AsString())) == 0) || (_v.IsNumeric() && _v.AsNumDouble() == 0) || (_v.IsLogical() && !_v.AsBool())) }") + g.imports["strings"] = true + return true + } + case "CHR": + if len(args) == 1 { + g.emitExpr(args[0]) + g.writeln("t.PushString(string(byte(t.Pop2().AsNumInt())))") + return true + } + case "ASC": + if len(args) == 1 { + g.emitExpr(args[0]) + g.writeln("{ _s := t.Pop2().AsString(); if len(_s)>0 { t.PushInt(int(_s[0])) } else { t.PushInt(0) } }") + return true + } + } + return false +} + // isGoPackage checks if a DotExpr refers to an imported Go package. func (g *Generator) isGoPackage(dot *ast.DotExpr) bool { if ident, ok := dot.X.(*ast.IdentExpr); ok {