// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com) // All rights reserved. // Integration tests: simulates what generated Go code would look like. // Each test is the equivalent of a PRG file compiled by Five. package tests import ( "five/hbrt" "five/hbrtl" "testing" ) func newTestVM() *hbrt.VM { vm := hbrt.NewVM() hbrtl.RegisterRTL(vm) return vm } // TestHelloWorld simulates: // // FUNCTION Main() // ? "Hello, World!" // ? 1 + 2 // RETURN NIL func TestHelloWorld(t *testing.T) { vm := newTestVM() mod := hbrt.NewModule("HELLO", hbrt.Sym("MAIN", hbrt.FsPublic|hbrt.FsLocal|hbrt.FsFirst, func(th *hbrt.Thread) { th.Frame(0, 0) defer th.EndProc() th.PushSymbol(vm.FindSymbol("QOUT")) th.PushNil() th.PushString("Hello, World!") th.Function(1) th.PushSymbol(vm.FindSymbol("QOUT")) th.PushNil() th.PushInt(1) th.PushInt(2) th.Plus() th.Function(1) th.RetNil() }), ) vm.RegisterModule(mod) result := vm.Run("MAIN") if !result.IsNil() { t.Errorf("Main should return NIL, got %v", result) } } // TestSumLoop simulates: // // FUNCTION Main() // LOCAL nSum := 0, i := 1 // DO WHILE i <= 10 // nSum += i // i++ // ENDDO // ? "Sum 1..10 =", nSum // RETURN nSum func TestSumLoop(t *testing.T) { vm := newTestVM() mod := hbrt.NewModule("SUMTEST", hbrt.Sym("MAIN", hbrt.FsPublic|hbrt.FsLocal|hbrt.FsFirst, func(th *hbrt.Thread) { th.Frame(0, 2) defer th.EndProc() th.LocalSetInt(1, 0) th.LocalSetInt(2, 1) lab_for: th.PushLocal(2) th.PushInt(10) th.LessEqual() if !th.PopLogical() { goto lab_endfor } th.PushLocal(2) th.LocalAdd(1) th.LocalAddInt(2, 1) goto lab_for lab_endfor: th.PushSymbol(vm.FindSymbol("QOUT")) th.PushNil() th.PushString("Sum 1..10 =") th.PushLocal(1) th.Function(2) th.PushLocal(1) th.RetValue() }), ) vm.RegisterModule(mod) result := vm.Run("MAIN") if result.AsNumInt() != 55 { t.Errorf("Sum 1..10 = %d, want 55", result.AsNumInt()) } } // TestStringConcat simulates: // // FUNCTION Main() // LOCAL cName := "World" // LOCAL cGreeting := "Hello, " + cName + "!" // ? cGreeting // RETURN cGreeting func TestStringConcat(t *testing.T) { vm := newTestVM() mod := hbrt.NewModule("STRTEST", hbrt.Sym("MAIN", hbrt.FsPublic|hbrt.FsLocal|hbrt.FsFirst, func(th *hbrt.Thread) { th.Frame(0, 2) defer th.EndProc() th.PushString("World") th.PopLocal(1) th.PushString("Hello, ") th.PushLocal(1) th.Plus() th.PushString("!") th.Plus() th.PopLocal(2) th.PushSymbol(vm.FindSymbol("QOUT")) th.PushNil() th.PushLocal(2) th.Function(1) th.PushLocal(2) th.RetValue() }), ) vm.RegisterModule(mod) result := vm.Run("MAIN") if result.AsString() != "Hello, World!" { t.Errorf("greeting = %q, want %q", result.AsString(), "Hello, World!") } } // TestFunctionCallWithSTR simulates: // // FUNCTION Main() // LOCAL n := 42 // ? "Value: " + Str(n) // RETURN n func TestFunctionCallWithSTR(t *testing.T) { vm := newTestVM() mod := hbrt.NewModule("STRFUNC", hbrt.Sym("MAIN", hbrt.FsPublic|hbrt.FsLocal|hbrt.FsFirst, func(th *hbrt.Thread) { th.Frame(0, 1) defer th.EndProc() th.LocalSetInt(1, 42) // Str(n) th.PushSymbol(vm.FindSymbol("STR")) th.PushNil() th.PushLocal(1) th.Function(1) // "Value: " + Str(n) strResult := th.GetRetValue() th.Pop() // pop Function result from stack th.PushString("Value: ") th.PushValue(strResult) th.Plus() // ? result concatResult := th.Pop2() th.PushSymbol(vm.FindSymbol("QOUT")) th.PushNil() th.PushValue(concatResult) th.Function(1) th.PushLocal(1) th.RetValue() }), ) vm.RegisterModule(mod) result := vm.Run("MAIN") if result.AsNumInt() != 42 { t.Errorf("result = %d, want 42", result.AsNumInt()) } } // TestNestedFunctionCalls simulates: // // FUNCTION Double(n) → n * 2 // FUNCTION Main() → ? Double(Double(5)) func TestNestedFunctionCalls(t *testing.T) { vm := newTestVM() mod := hbrt.NewModule("NESTED", hbrt.Sym("DOUBLE", hbrt.FsPublic|hbrt.FsLocal, func(th *hbrt.Thread) { th.Frame(1, 0) defer th.EndProc() th.RetInt(th.Local(1).AsNumInt() * 2) }), hbrt.Sym("MAIN", hbrt.FsPublic|hbrt.FsLocal|hbrt.FsFirst, func(th *hbrt.Thread) { th.Frame(0, 0) defer th.EndProc() // Double(5) → 10 th.PushSymbol(vm.FindSymbol("DOUBLE")) th.PushNil() th.PushInt(5) th.Function(1) // Double(10) → 20 inner := th.Pop2() th.PushSymbol(vm.FindSymbol("DOUBLE")) th.PushNil() th.PushValue(inner) th.Function(1) // ? and return outer := th.Pop2() th.PushSymbol(vm.FindSymbol("QOUT")) th.PushNil() th.PushValue(outer) th.Function(1) th.PushValue(outer) th.RetValue() }), ) vm.RegisterModule(mod) result := vm.Run("MAIN") if result.AsNumInt() != 20 { t.Errorf("Double(Double(5)) = %d, want 20", result.AsNumInt()) } } // TestIfElse simulates: // // FUNCTION Main() // LOCAL n := 55 // IF n > 50 // ? "Greater" // RETURN .T. // ELSE // ? "Not greater" // RETURN .F. // ENDIF func TestIfElse(t *testing.T) { vm := newTestVM() mod := hbrt.NewModule("IFTEST", hbrt.Sym("MAIN", hbrt.FsPublic|hbrt.FsLocal|hbrt.FsFirst, func(th *hbrt.Thread) { th.Frame(0, 1) defer th.EndProc() th.LocalSetInt(1, 55) th.PushLocal(1) th.PushInt(50) th.Greater() if !th.PopLogical() { goto lab_else } th.PushSymbol(vm.FindSymbol("QOUT")) th.PushNil() th.PushString("Greater") th.Function(1) th.PushBool(true) th.RetValue() return lab_else: th.PushSymbol(vm.FindSymbol("QOUT")) th.PushNil() th.PushString("Not greater") th.Function(1) th.PushBool(false) th.RetValue() }), ) vm.RegisterModule(mod) result := vm.Run("MAIN") if !result.AsBool() { t.Error("55 > 50 should return .T.") } }