Files
five/compiler/gengo/gengo_test.go
CharlesKWON f4ed42556b checkpoint: season-wide bug fix campaign + infra
Cumulative season's silent-bug hunting (~62 fixes) across the FiveSql2
SQL engine, the Five compiler/runtime, and the hbrdd RDD layer. Saved
as a single checkpoint before refactoring the parser to delegate xBase
command translation to the preprocessor.

Highlights:

FiveSql2 engine (_FiveSql2/src/)
- prefix-glob index attach -> explicit convention (<table>_pk.ntx,
  <table>_uq.ntx, <table>.cdx) — fixes silent multi-row INSERT row-drop
- DROP/CREATE TABLE FErase chain extended (.cdx, .fsc, .fsv, .dbt, .fpt)
- COUNT(DISTINCT col) parsed + aggregated via hSeen hash
- UNION column-count mismatch returns SQL_ERR_GRAMMAR (was silent)
- DISTINCT + ORDER BY hidden-col leak fixed (trim before DISTINCT)
- Derived table FROM (SELECT...) + JOIN right-side derived
- Self-FK CASCADE depth 2+ via SqlGetSingleColPK pre-collect
- LAG/LEAD default arg uses SqlEvalRowExpr (handles -N const exprs)
- DATE literal round-trip validation (Feb 29 non-leap rejected)
- CREATE OR REPLACE VIEW; CREATE VIEW errors on already-exists
- AlterTable type dispatcher comma-wrapped (1-char type "A" no longer
  matches CHARACTER)

Compiler / runtime
- gengo: HB_ -> FV_ prefix on emitted Go function names (Five identity)
- gengo split: emit_block.go, emit_stmt.go, folding.go extracted
- parser/stmtreg.go nudges
- hbrt: debug TUI/CLI restructure (debugcmd, debugkey, termios_*),
  windows debug stubs collapsed
- thread/vm/value/class/pcinterp tightening from panic traces

RDD layer (hbrdd/)
- dbf: null bitmap support (null.go + null_test.go), mmap split
  (mmap_posix.go / mmap_windows.go), byte-level numeric parse
- ntx/cdx: windows mmap parity
- workarea + mem RDD: cross-area state-bleed fixes

RTL (hbrtl/)
- errorlog rewrite with platform-specific FD (errorlog_fd_unix /
  errorlog_fd_other)
- sqlscan, sqlhelpers, indexrtl, datetime extensions

Gates green at checkpoint:
- go test ./...        : PASS
- FiveSql2 SQL:1999    : 43/43
- Harbour compat       : 56/56

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 09:26:25 +09:00

164 lines
4.3 KiB
Go

// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com)
// All rights reserved.
package gengo
import (
"five/compiler/parser"
"strings"
"testing"
)
func generate(t *testing.T, source string) string {
t.Helper()
file, errs := parser.Parse("test.prg", source)
if len(errs) > 0 {
for _, e := range errs {
t.Errorf("parse error: %s", e)
}
t.FailNow()
}
return Generate(file)
}
func assertContains(t *testing.T, code, want string) {
t.Helper()
if !strings.Contains(code, want) {
t.Errorf("generated code missing %q\n--- code ---\n%s", want, code)
}
}
func TestGenerateHelloWorld(t *testing.T) {
code := generate(t, `FUNCTION Main()
? "Hello, World!"
RETURN NIL
`)
assertContains(t, code, "package main")
assertContains(t, code, `import (`)
assertContains(t, code, `"five/hbrt"`)
assertContains(t, code, "func FV_MAIN(t *hbrt.Thread)")
assertContains(t, code, "t.Frame(0, 0)")
assertContains(t, code, "defer t.EndProc()")
assertContains(t, code, `t.PushString("Hello, World!")`)
assertContains(t, code, "t.Function(1)")
assertContains(t, code, "t.PushNil()")
assertContains(t, code, "t.RetValue()")
assertContains(t, code, "func main()")
assertContains(t, code, `vm.Run("MAIN")`)
}
func TestGenerateArithmetic(t *testing.T) {
// Const prop (b829ed4) inlines `n` as 10 at its read site. The
// literal fold pass runs before the ident substitution so the
// outer `10 + 5` doesn't collapse to `15` — leaves two PushInt +
// Plus. Dead store for `n` is elided (6974ff9).
code := generate(t, `FUNCTION Main()
LOCAL n := 10
RETURN n + 5
`)
assertContains(t, code, "t.Frame(0, 1)")
assertContains(t, code, "t.PushInt(10)")
assertContains(t, code, "t.PushInt(5)")
assertContains(t, code, "t.Plus()")
assertContains(t, code, "t.RetValue()")
}
func TestGenerateIfElse(t *testing.T) {
code := generate(t, `FUNCTION Main()
LOCAL n := 10
IF n > 5
? "Big"
ELSE
? "Small"
ENDIF
RETURN NIL
`)
assertContains(t, code, "t.Greater()")
assertContains(t, code, "if t.PopLogical()")
assertContains(t, code, `t.PushString("Big")`)
assertContains(t, code, "} else {")
assertContains(t, code, `t.PushString("Small")`)
}
func TestGenerateDoWhile(t *testing.T) {
code := generate(t, `FUNCTION Main()
LOCAL i := 0
DO WHILE i < 10
i++
ENDDO
RETURN i
`)
assertContains(t, code, "for {")
assertContains(t, code, "t.Less()")
assertContains(t, code, "if !t.PopLogical() { break }")
assertContains(t, code, "t.LocalAddInt(1, 1)") // i++
}
func TestGenerateForNext(t *testing.T) {
code := generate(t, `FUNCTION Main()
LOCAL i, nSum := 0
FOR i := 1 TO 10
nSum += i
NEXT
RETURN nSum
`)
assertContains(t, code, "t.Frame(0, 2)")
assertContains(t, code, "for {")
assertContains(t, code, "LocalLessEqualInt(")
assertContains(t, code, "t.LocalAdd(") // nSum += i
assertContains(t, code, "t.LocalAddInt(") // i += 1
}
func TestGenerateMultipleFunctions(t *testing.T) {
// Symbol hoist (1f63c7f) replaced `t.VM().FindSymbol(...)` with a
// per-file package-level pointer populated lazily via GetSym.
code := generate(t, `FUNCTION Double(n)
RETURN n * 2
FUNCTION Main()
? Double(21)
RETURN NIL
`)
assertContains(t, code, "func FV_DOUBLE(t *hbrt.Thread)")
assertContains(t, code, "func FV_MAIN(t *hbrt.Thread)")
assertContains(t, code, "t.Frame(1, 0)") // Double has 1 param
assertContains(t, code, "t.Mult()")
assertContains(t, code, `t.GetSym(&_sym_test_DOUBLE, "DOUBLE")`)
}
func TestGenerateStringConcat(t *testing.T) {
// cName propagates to "World" (b829ed4). The string-concat fold
// (7e4079f) works on literal+literal pairs, which is what the
// three PushStrings + Plus calls produce.
code := generate(t, `FUNCTION Main()
LOCAL cName := "World"
? "Hello, " + cName + "!"
RETURN NIL
`)
assertContains(t, code, `t.PushString("Hello, ")`)
assertContains(t, code, `t.PushString("World")`)
assertContains(t, code, `t.PushString("!")`)
assertContains(t, code, "t.Plus()")
}
func TestGenerateSymbolTable(t *testing.T) {
code := generate(t, `FUNCTION Main()
RETURN NIL
FUNCTION Helper()
RETURN NIL
`)
assertContains(t, code, `hbrt.Sym("MAIN"`)
assertContains(t, code, `hbrt.Sym("HELPER"`)
assertContains(t, code, "hbrt.FsFirst")
}
func TestGenerateImport(t *testing.T) {
code := generate(t, `IMPORT "net/http"
FUNCTION Main()
RETURN NIL
`)
assertContains(t, code, `"net/http"`)
}