perf(gengo): reassociate left-leaning string-concat literal runs
`"a" + x + "b" + "c" + "d"` used to emit 4 Plus() calls because the parser builds a left-leaning chain and no pair was literal+literal. Add a reassociation step inside foldLiteralTree: when the outer shape is `(Y + strlit1) + strlit2`, rewrite as `Y + (strlit1+strlit2)` so the tail literals collapse. Also run foldLiteralTree on the root BinaryExpr in emitExpr so the outermost reassoc fires (was only running on children). Verified: the 4-Plus case now emits 2 Plus calls (`"a" + x + "bcd"`). FiveSql2 43/43, Harbour compat 56/56. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -275,6 +275,13 @@ func negateLiteral(lit *ast.LiteralExpr) (*ast.LiteralExpr, bool) {
|
|||||||
// where both operands eventually collapse to literals. Non-foldable
|
// where both operands eventually collapse to literals. Non-foldable
|
||||||
// subtrees come back unchanged. Used as a preorder pre-pass so the
|
// subtrees come back unchanged. Used as a preorder pre-pass so the
|
||||||
// caller can look at a flat LITERAL + LITERAL pair.
|
// caller can look at a flat LITERAL + LITERAL pair.
|
||||||
|
//
|
||||||
|
// For left-associative string-concat chains like "a" + x + "b" + "c",
|
||||||
|
// the parser builds (((("a" + x) + "b") + "c")) and no pair is
|
||||||
|
// literal+literal. We reassociate: if the LHS is `Y + strlit` and the
|
||||||
|
// RHS is a string literal, rewrite as `Y + (strlit+rhslit)` so the
|
||||||
|
// tail literals collapse. Only safe for STRING+STRING (numeric `+`
|
||||||
|
// cares about types / overflow).
|
||||||
func foldLiteralTree(e ast.Expr) ast.Expr {
|
func foldLiteralTree(e ast.Expr) ast.Expr {
|
||||||
be, ok := e.(*ast.BinaryExpr)
|
be, ok := e.(*ast.BinaryExpr)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -285,6 +292,26 @@ func foldLiteralTree(e ast.Expr) ast.Expr {
|
|||||||
if folded, ok := tryFoldBinary(be); ok {
|
if folded, ok := tryFoldBinary(be); ok {
|
||||||
return folded
|
return folded
|
||||||
}
|
}
|
||||||
|
// String-concat reassociation for left-leaning chains.
|
||||||
|
if be.Op == token.PLUS {
|
||||||
|
if rLit, ok := be.Right.(*ast.LiteralExpr); ok && rLit.Kind == token.STRING {
|
||||||
|
if lBin, ok := be.Left.(*ast.BinaryExpr); ok && lBin.Op == token.PLUS {
|
||||||
|
if mLit, ok := lBin.Right.(*ast.LiteralExpr); ok && mLit.Kind == token.STRING {
|
||||||
|
fused := &ast.LiteralExpr{
|
||||||
|
ValuePos: mLit.ValuePos,
|
||||||
|
Kind: token.STRING,
|
||||||
|
Value: mLit.Value + rLit.Value,
|
||||||
|
}
|
||||||
|
return &ast.BinaryExpr{
|
||||||
|
OpPos: be.OpPos,
|
||||||
|
Op: token.PLUS,
|
||||||
|
Left: lBin.Left,
|
||||||
|
Right: fused,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return be
|
return be
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1945,15 +1972,17 @@ func (g *Generator) emitExpr(expr ast.Expr) {
|
|||||||
case *ast.IdentExpr:
|
case *ast.IdentExpr:
|
||||||
g.emitIdent(e)
|
g.emitIdent(e)
|
||||||
case *ast.BinaryExpr:
|
case *ast.BinaryExpr:
|
||||||
// Compile-time constant folding. Fold children first so
|
// Compile-time constant folding. Fold the whole subtree so
|
||||||
// `(2*3) + 5` collapses all the way to a single PushInt(11)
|
// `(2*3) + 5` collapses to PushInt(11) and so string-concat
|
||||||
// instead of stopping at the inner `6`. Mutates the AST in
|
// tails like `x + "b" + "c"` reassociate into `x + "bc"`.
|
||||||
// place — safe because the generator owns the tree post-parse.
|
// Mutates the AST in place — safe because the generator owns
|
||||||
e.Left = foldLiteralTree(e.Left)
|
// the tree post-parse.
|
||||||
e.Right = foldLiteralTree(e.Right)
|
switch folded := foldLiteralTree(e).(type) {
|
||||||
if folded, ok := tryFoldBinary(e); ok {
|
case *ast.LiteralExpr:
|
||||||
g.emitLiteral(folded)
|
g.emitLiteral(folded)
|
||||||
break
|
return
|
||||||
|
case *ast.BinaryExpr:
|
||||||
|
e = folded
|
||||||
}
|
}
|
||||||
// Short-circuit AND/OR: Harbour evaluates right operand only if needed.
|
// Short-circuit AND/OR: Harbour evaluates right operand only if needed.
|
||||||
// With a literal LHS we can skip the PushBool/PopLogical roundtrip
|
// With a literal LHS we can skip the PushBool/PopLogical roundtrip
|
||||||
|
|||||||
Reference in New Issue
Block a user