// 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 HB_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) { 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.PopLocal(1)") assertContains(t, code, "t.PushLocal(1)") // n 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, "t.LessEqual()") assertContains(t, code, "t.LocalAdd(") // nSum += i assertContains(t, code, "t.LocalAddInt(") // i += 1 } func TestGenerateMultipleFunctions(t *testing.T) { code := generate(t, `FUNCTION Double(n) RETURN n * 2 FUNCTION Main() ? Double(21) RETURN NIL `) assertContains(t, code, "func HB_DOUBLE(t *hbrt.Thread)") assertContains(t, code, "func HB_MAIN(t *hbrt.Thread)") assertContains(t, code, "t.Frame(1, 0)") // Double has 1 param assertContains(t, code, "t.Mult()") assertContains(t, code, `t.PushSymbol(t.VM().FindSymbol("DOUBLE"))`) } func TestGenerateStringConcat(t *testing.T) { code := generate(t, `FUNCTION Main() LOCAL cName := "World" ? "Hello, " + cName + "!" RETURN NIL `) assertContains(t, code, `t.PushString("Hello, ")`) assertContains(t, code, "t.PushLocal(1)") assertContains(t, code, "t.Plus()") assertContains(t, code, `t.PushString("!")`) } 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"`) }