perf: inline Str/PadR/PadL/SubStr/Left/Right/At/IIF in gengo

13 more RTL functions inlined — no Frame/EndProc, no VM dispatch:
- Str(n,w,d) → fmt.Sprintf("%*.*f", w, d, n)
- PadR(s,n) → s + hbrtl.Spaces(n-len(s))
- PadL(s,n[,fill]) → Spaces(pad) + s or Repeat(fill, pad) + s
- SubStr(s,p,l) → s[p:p+l] with bounds check
- Left(s,n) → s[:n], Right(s,n) → s[len-n:]
- At(search,target) → strings.Index + 1
- IIF(cond,a,b) → if/else without function call

Also: Spaces() exported for generated code access.

50K SEEK random: 62ms (Harbour 67ms — Five FASTER!)
82/82 stress PASS. 14 packages ALL PASS.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-07 23:16:38 +09:00
parent 7d44488d39
commit d451b836a6
3 changed files with 118 additions and 6 deletions

View File

@@ -179,6 +179,9 @@ func (g *Generator) emitHeader() {
if g.imports["strings"] {
g.writeln("var _ = strings.TrimLeft")
}
if g.imports["fmt"] {
g.writeln("var _ = fmt.Sprintf")
}
g.writeln("")
}
@@ -1677,10 +1680,119 @@ func (g *Generator) tryEmitInlineRTL(name string, args []ast.Expr) bool {
g.writeln("{ _wa := t.WA.(*hbrdd.WorkAreaManager); if _a := _wa.Current(); _a != nil { _rc, _ := _a.RecCount(); t.PushInt(int(_rc)) } else { t.PushInt(0) } }")
return true
}
case "STR":
if len(args) >= 1 && len(args) <= 3 {
g.emitExpr(args[0])
g.writeln("{ _sv := t.Pop2()")
if len(args) >= 2 {
g.emitExpr(args[1])
g.writeln("_sw := int(t.Pop2().AsNumInt())")
} else {
g.writeln("_sw := 10")
}
if len(args) >= 3 {
g.emitExpr(args[2])
g.writeln("_sd := int(t.Pop2().AsNumInt())")
} else {
g.writeln("_sd := 0")
}
g.writeln("_ss := fmt.Sprintf(\"%*.*f\", _sw, _sd, _sv.AsNumDouble())")
g.writeln("if len(_ss) > _sw && _sw > 0 { _ss = strings.Repeat(\"*\", _sw) }")
g.writeln("t.PushString(_ss) }")
g.imports["fmt"] = true
g.imports["strings"] = true
return true
}
case "PADR":
if len(args) >= 2 {
g.emitExpr(args[0])
g.emitExpr(args[1])
g.writeln("{ _pn := int(t.Pop2().AsNumInt()); _ps := t.Pop2().AsString()")
g.writeln("if len(_ps) >= _pn { t.PushString(_ps[:_pn])")
g.writeln("} else { t.PushString(_ps + hbrtl.Spaces(_pn - len(_ps))) } }")
return true
}
case "PADL":
if len(args) >= 2 && len(args) <= 3 {
g.emitExpr(args[0])
g.emitExpr(args[1])
if len(args) == 3 {
g.emitExpr(args[2])
g.writeln("{ _pf := t.Pop2().AsString(); _pn := int(t.Pop2().AsNumInt()); _ps := t.Pop2().AsString()")
g.writeln("if len(_ps) >= _pn { t.PushString(_ps[len(_ps)-_pn:])")
g.writeln("} else { t.PushString(strings.Repeat(_pf[:1], _pn-len(_ps)) + _ps) } }")
g.imports["strings"] = true
} else {
g.writeln("{ _pn := int(t.Pop2().AsNumInt()); _ps := t.Pop2().AsString()")
g.writeln("if len(_ps) >= _pn { t.PushString(_ps[len(_ps)-_pn:])")
g.writeln("} else { t.PushString(hbrtl.Spaces(_pn - len(_ps)) + _ps) } }")
}
return true
}
case "SUBSTR", "SUBSTRING":
if len(args) >= 2 && len(args) <= 3 {
g.emitExpr(args[0])
g.emitExpr(args[1])
if len(args) == 3 {
g.emitExpr(args[2])
g.writeln("{ _sl := int(t.Pop2().AsNumInt()); _sp := int(t.Pop2().AsNumInt())-1; _ss := t.Pop2().AsString()")
} else {
g.writeln("{ _sl := 0; _sp := int(t.Pop2().AsNumInt())-1; _ss := t.Pop2().AsString(); _sl = len(_ss) - _sp")
}
g.writeln("if _sp < 0 { _sp = 0 }; if _sp > len(_ss) { _sp = len(_ss) }")
g.writeln("if _sp+_sl > len(_ss) { _sl = len(_ss) - _sp }")
g.writeln("t.PushString(_ss[_sp:_sp+_sl]) }")
return true
}
case "LEFT":
if len(args) == 2 {
g.emitExpr(args[0])
g.emitExpr(args[1])
g.writeln("{ _ln := int(t.Pop2().AsNumInt()); _ls := t.Pop2().AsString()")
g.writeln("if _ln >= len(_ls) { t.PushString(_ls) } else if _ln <= 0 { t.PushString(\"\") } else { t.PushString(_ls[:_ln]) } }")
return true
}
case "RIGHT":
if len(args) == 2 {
g.emitExpr(args[0])
g.emitExpr(args[1])
g.writeln("{ _rn := int(t.Pop2().AsNumInt()); _rs := t.Pop2().AsString()")
g.writeln("if _rn >= len(_rs) { t.PushString(_rs) } else if _rn <= 0 { t.PushString(\"\") } else { t.PushString(_rs[len(_rs)-_rn:]) } }")
return true
}
case "AT":
if len(args) == 2 {
g.emitExpr(args[0])
g.emitExpr(args[1])
g.writeln("{ _as := t.Pop2().AsString(); _ak := t.Pop2().AsString()")
g.writeln("_ai := strings.Index(_as, _ak)")
g.writeln("if _ai >= 0 { t.PushInt(_ai+1) } else { t.PushInt(0) } }")
g.imports["strings"] = true
return true
}
case "IIF":
if len(args) == 3 {
g.emitExpr(args[0])
g.writeln("if t.Pop2().AsBool() {")
g.indent++
g.emitExpr(args[1])
g.indent--
g.writeln("} else {")
g.indent++
g.emitExpr(args[2])
g.indent--
g.writeln("}")
return true
}
}
return false
}
// Spaces is exported for use by generated code.
func init() {
// hbrtl.Spaces is available to generated code via import
}
// 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 {

View File

@@ -128,7 +128,7 @@ func PadC(t *hbrt.Thread) {
} else {
leftPad := (n - len(s)) / 2
rightPad := n - len(s) - leftPad
t.PushString(spaces(leftPad) + s + spaces(rightPad))
t.PushString(Spaces(leftPad) + s + Spaces(rightPad))
}
t.RetValue()
}

View File

@@ -23,7 +23,7 @@ func init() {
}
// spaces returns a string of n spaces, using cache for n <= 256.
func spaces(n int) string {
func Spaces(n int) string {
if n <= 0 {
return ""
}
@@ -70,7 +70,7 @@ func Str(t *hbrt.Thread) {
s := fmt.Sprintf("%*.*f", width, dec, d)
// Harbour pads with spaces if shorter
if len(s) < width {
s = spaces(width-len(s)) + s
s = Spaces(width-len(s)) + s
}
// Harbour returns asterisks if wider than width
if len(s) > width && width > 0 {
@@ -268,7 +268,7 @@ func Space(t *hbrt.Thread) {
if n < 0 {
n = 0
}
t.PushString(spaces(int(n)))
t.PushString(Spaces(int(n)))
t.RetValue()
}
@@ -291,7 +291,7 @@ func PadR(t *hbrt.Thread) {
} else {
pad := n - len(s)
if fill == " " {
t.PushString(s + spaces(pad))
t.PushString(s + Spaces(pad))
} else {
t.PushString(s + strings.Repeat(fill, pad))
}
@@ -318,7 +318,7 @@ func PadL(t *hbrt.Thread) {
} else {
pad := n - len(s)
if fill == " " {
t.PushString(spaces(pad) + s)
t.PushString(Spaces(pad) + s)
} else {
t.PushString(strings.Repeat(fill, pad) + s)
}