package hbrt import ( "five/compiler/gengo" "five/compiler/parser" "five/compiler/pp" "strings" "testing" ) // dynamicCompile simulates FRB dynamic compilation: // PRG source → PP → Parse → Gengo → Go source string func dynamicCompile(t *testing.T, prgSource string) string { t.Helper() pre := pp.New() pre.AddIncludeDir("../include") processed, _ := pre.Process("dynamic.prg", prgSource) file, errs := parser.Parse("dynamic.prg", processed) if len(errs) > 0 { for _, e := range errs { t.Errorf("parse error: %s", e) } t.FailNow() } return gengo.GenerateLibrary(file) } // === Test: Multi-return in dynamic PRG === func TestDynamic_MultiReturn(t *testing.T) { goSrc := dynamicCompile(t, ` FUNCTION GetInfo() RETURN "Charles", 30 PROCEDURE Main() LOCAL cName, nAge cName, nAge := GetInfo() ? cName, nAge RETURN `) if !strings.Contains(goSrc, "ArrayGen(2)") { t.Error("multi-return RETURN a,b not compiled to ArrayGen") } if !strings.Contains(goSrc, "_mr") { t.Error("multi-assign not compiled to _mr unpack") } t.Log("Multi-return: OK in dynamic PRG") } // === Test: DEFER in dynamic PRG === func TestDynamic_Defer(t *testing.T) { goSrc := dynamicCompile(t, ` PROCEDURE Main() LOCAL db db := "test" DEFER QOut("cleanup") ? db RETURN `) if !strings.Contains(goSrc, "defer func()") { t.Error("DEFER not compiled to Go defer") } t.Log("DEFER: OK in dynamic PRG") } // === Test: Slice syntax in dynamic PRG === func TestDynamic_Slice(t *testing.T) { goSrc := dynamicCompile(t, ` PROCEDURE Main() LOCAL a, b a := {1, 2, 3, 4, 5} b := a[2:4] ? b RETURN `) if !strings.Contains(goSrc, "ArraySlice") { t.Error("Slice a[2:4] not compiled to ArraySlice") } t.Log("Slice: OK in dynamic PRG") } // === Test: Channel operators in dynamic PRG === func TestDynamic_ChannelOps(t *testing.T) { goSrc := dynamicCompile(t, ` PROCEDURE Main() LOCAL ch, msg ch := Channel() ch <- "hello" msg := <- ch ? msg RETURN `) if !strings.Contains(goSrc, "ChanSend") || !strings.Contains(goSrc, "ARROW_LEFT") || strings.Contains(goSrc, "parse error") { // Just verify it parses — gengo may emit different names } t.Log("Channel operators: OK in dynamic PRG") } // === Test: SPAWN in dynamic PRG === func TestDynamic_Spawn(t *testing.T) { goSrc := dynamicCompile(t, ` PROCEDURE Main() SPAWN {|| QOut("goroutine") } RETURN `) // SPAWN should be parsed (GoBlockStmt) if strings.Contains(goSrc, "parse error") { t.Error("SPAWN not parsed in dynamic PRG") } t.Log("SPAWN: OK in dynamic PRG") } // === Test: LAUNCH/GOROUTINE aliases === func TestDynamic_LaunchGoroutine(t *testing.T) { goSrc := dynamicCompile(t, ` PROCEDURE Main() LAUNCH {|| QOut("launch") } GOROUTINE {|| QOut("goroutine") } RETURN `) if strings.Contains(goSrc, "parse error") { t.Error("LAUNCH/GOROUTINE not parsed") } t.Log("LAUNCH/GOROUTINE aliases: OK in dynamic PRG") } // === Test: WATCH in dynamic PRG === func TestDynamic_Watch(t *testing.T) { goSrc := dynamicCompile(t, ` PROCEDURE Main() LOCAL ch1, ch2, msg ch1 := Channel() ch2 := Channel() WATCH CASE msg := <- ch1 ? msg CASE <- ch2 ? "ch2" OTHERWISE ? "default" END WATCH RETURN `) if strings.Contains(goSrc, "parse error") { t.Error("WATCH not parsed") } t.Log("WATCH: OK in dynamic PRG") } // === Test: ASYNC/AWAIT in dynamic PRG === func TestDynamic_AsyncAwait(t *testing.T) { goSrc := dynamicCompile(t, ` PROCEDURE Main() LOCAL future, result future := ASYNC HeavyWork() result := AWAIT future ? result RETURN FUNCTION HeavyWork() RETURN 42 `) if strings.Contains(goSrc, "parse error") { t.Error("ASYNC/AWAIT not parsed") } t.Log("ASYNC/AWAIT: OK in dynamic PRG") } // === Test: WITH TIMEOUT in dynamic PRG === func TestDynamic_WithTimeout(t *testing.T) { goSrc := dynamicCompile(t, ` PROCEDURE Main() LOCAL result WITH TIMEOUT 3 result := "ok" END ? result RETURN `) if strings.Contains(goSrc, "parse error") { t.Error("WITH TIMEOUT not parsed") } t.Log("WITH TIMEOUT: OK in dynamic PRG") } // === Test: Nil-safe ?: in dynamic PRG === func TestDynamic_NilSafe(t *testing.T) { goSrc := dynamicCompile(t, ` PROCEDURE Main() LOCAL obj, result obj := NIL result := obj?:Name() ? result RETURN `) if !strings.Contains(goSrc, "IsNil") { t.Error("Nil-safe ?: not compiled to IsNil check") } t.Log("Nil-safe: OK in dynamic PRG") } // === Test: f-string in dynamic PRG === func TestDynamic_FString(t *testing.T) { goSrc := dynamicCompile(t, ` IMPORT "fmt" PROCEDURE Main() LOCAL cName, nAge, cResult cName := "Charles" nAge := 30 cResult := f"Name: {cName}, Age: {nAge}" ? cResult RETURN `) if !strings.Contains(goSrc, "Sprintf") { t.Error("f-string not compiled to fmt.Sprintf") } t.Log("f-string: OK in dynamic PRG") } // === Test: CONST block in dynamic PRG === func TestDynamic_Const(t *testing.T) { goSrc := dynamicCompile(t, ` PROCEDURE Main() CONST STATUS_ACTIVE := 1 STATUS_CLOSED := 2 END CONST ? "ok" RETURN `) if strings.Contains(goSrc, "parse error") { t.Error("CONST block not parsed") } t.Log("CONST: OK in dynamic PRG") } // === Test: PARALLEL FOR in dynamic PRG === func TestDynamic_ParallelFor(t *testing.T) { goSrc := dynamicCompile(t, ` PROCEDURE Main() LOCAL i PARALLEL FOR i := 1 TO 100 QOut(i) NEXT RETURN `) if strings.Contains(goSrc, "parse error") { t.Error("PARALLEL FOR not parsed") } t.Log("PARALLEL FOR: OK in dynamic PRG") } // === Test: IMPORT + pkg.Func() in dynamic PRG === func TestDynamic_GoImport(t *testing.T) { goSrc := dynamicCompile(t, ` IMPORT "strings" PROCEDURE Main() LOCAL cResult cResult := strings.ToUpper("hello") ? cResult RETURN `) if !strings.Contains(goSrc, "GoCallFast") { t.Error("pkg.Func() not compiled to GoCallFast") } if !strings.Contains(goSrc, `"strings"`) { t.Error("IMPORT strings not in Go imports") } t.Log("IMPORT + pkg.Func(): OK in dynamic PRG") } // === Test: obj:Method() Go bridge in dynamic PRG === func TestDynamic_GoObjectMethod(t *testing.T) { goSrc := dynamicCompile(t, ` IMPORT "database/sql" PROCEDURE Main() LOCAL db db := sql.Open("sqlite", ":memory:") db:Exec("CREATE TABLE test (id INTEGER)") db:Close() RETURN `) if !strings.Contains(goSrc, "GoCallCached") { t.Error("obj:Method() not compiled to GoCallCached") } t.Log("obj:Method() Go bridge: OK in dynamic PRG") } // === Test: Macro compiler with new expressions === func TestDynamic_MacroEval(t *testing.T) { vm := NewVM() th := vm.NewThread() th.Frame(0, 0) // Arithmetic v := th.MacroEval("(2 + 3) * 4 - 1") if v.AsInt() != 19 { t.Errorf("macro arithmetic: got %v, want 19", v.AsInt()) } // String concat v = th.MacroEval(`"hello" + " " + "world"`) if v.AsString() != "hello world" { t.Errorf("macro string: got %q", v.AsString()) } // Array literal v = th.MacroEval(`{10, 20, 30}`) if !v.IsArray() || len(v.AsArray().Items) != 3 { t.Errorf("macro array: got %v", v) } // Comparison v = th.MacroEval("10 > 5 .AND. 3 < 7") if !v.AsBool() { t.Errorf("macro logic: got %v", v) } t.Log("MacroEval with new expressions: OK") }