- Compiler: PP → Lexer → Parser → Analyzer → Gengo pipeline - Parser: 232/236 (98%) Harbour compatibility, registry-based dispatch - RTL: 351 Harbour-compatible functions - RDD: DBF/NTX/CDX engines with Rushmore bitmap optimization - Go Interop: IMPORT + pkg.Func() + obj:Method() with FastPath (15M calls/sec) - HB_FUNC API: Full Harbour C API compatible Go bridge - Concurrency: SPAWN/LAUNCH/GOROUTINE, <-, WATCH, PARALLEL FOR, ASYNC/AWAIT - Extensions: Multi-return, DEFER, Slice, f-string, Nil-safe ?:, CONST - Macro Compiler: Runtime AST parsing and evaluation - Debugger: TUI debugger with source display, breakpoints, stepping - FRB: Native + Pcode dual mode runtime binary - Tests: 13 packages ALL PASS Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
360 lines
6.0 KiB
Go
360 lines
6.0 KiB
Go
// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com)
|
|
// All rights reserved.
|
|
|
|
package hbrt
|
|
|
|
import (
|
|
"math"
|
|
"testing"
|
|
)
|
|
|
|
// --- Plus ---
|
|
|
|
func TestPlusIntInt(t *testing.T) {
|
|
th := newTestThread()
|
|
th.Frame(0, 0)
|
|
|
|
th.PushInt(10)
|
|
th.PushInt(20)
|
|
th.Plus()
|
|
|
|
r := th.pop()
|
|
if !r.IsNumInt() || r.AsNumInt() != 30 {
|
|
t.Errorf("10 + 20 = %v, want 30", r)
|
|
}
|
|
th.EndProc()
|
|
}
|
|
|
|
func TestPlusIntOverflow(t *testing.T) {
|
|
th := newTestThread()
|
|
th.Frame(0, 0)
|
|
|
|
th.PushLong(math.MaxInt64)
|
|
th.PushLong(1)
|
|
th.Plus()
|
|
|
|
r := th.pop()
|
|
if !r.IsDouble() {
|
|
t.Errorf("MaxInt64 + 1 should overflow to Double, got type %d", r.Type())
|
|
}
|
|
th.EndProc()
|
|
}
|
|
|
|
func TestPlusDoubleDouble(t *testing.T) {
|
|
th := newTestThread()
|
|
th.Frame(0, 0)
|
|
|
|
th.PushDouble(1.5, 3, 1)
|
|
th.PushDouble(2.3, 3, 1)
|
|
th.Plus()
|
|
|
|
r := th.pop()
|
|
if !r.IsDouble() {
|
|
t.Fatal("expected Double")
|
|
}
|
|
if math.Abs(r.AsDouble()-3.8) > 1e-10 {
|
|
t.Errorf("1.5 + 2.3 = %g, want 3.8", r.AsDouble())
|
|
}
|
|
th.EndProc()
|
|
}
|
|
|
|
func TestPlusStringConcat(t *testing.T) {
|
|
th := newTestThread()
|
|
th.Frame(0, 0)
|
|
|
|
th.PushString("Hello, ")
|
|
th.PushString("World!")
|
|
th.Plus()
|
|
|
|
r := th.pop()
|
|
if r.AsString() != "Hello, World!" {
|
|
t.Errorf("string concat = %q, want %q", r.AsString(), "Hello, World!")
|
|
}
|
|
th.EndProc()
|
|
}
|
|
|
|
func TestPlusDateInt(t *testing.T) {
|
|
th := newTestThread()
|
|
th.Frame(0, 0)
|
|
|
|
th.push(MakeDate(2461033)) // 2026-03-27
|
|
th.PushInt(10)
|
|
th.Plus()
|
|
|
|
r := th.pop()
|
|
if !r.IsDate() || r.AsJulian() != 2461043 {
|
|
t.Errorf("Date + 10 = %v, want Date(2461043)", r)
|
|
}
|
|
th.EndProc()
|
|
}
|
|
|
|
func TestPlusDecimalPropagation(t *testing.T) {
|
|
th := newTestThread()
|
|
th.Frame(0, 0)
|
|
|
|
th.PushDouble(1.2, 4, 1) // 1 decimal
|
|
th.PushDouble(3.456, 5, 3) // 3 decimals
|
|
th.Plus()
|
|
|
|
r := th.pop()
|
|
// Result should have max(1, 3) = 3 decimals
|
|
if r.Decimal() != 3 {
|
|
t.Errorf("decimal = %d, want 3", r.Decimal())
|
|
}
|
|
th.EndProc()
|
|
}
|
|
|
|
// --- Minus ---
|
|
|
|
func TestMinusIntInt(t *testing.T) {
|
|
th := newTestThread()
|
|
th.Frame(0, 0)
|
|
|
|
th.PushInt(30)
|
|
th.PushInt(20)
|
|
th.Minus()
|
|
|
|
r := th.pop()
|
|
if r.AsNumInt() != 10 {
|
|
t.Errorf("30 - 20 = %d, want 10", r.AsNumInt())
|
|
}
|
|
th.EndProc()
|
|
}
|
|
|
|
func TestMinusDateDate(t *testing.T) {
|
|
th := newTestThread()
|
|
th.Frame(0, 0)
|
|
|
|
th.push(MakeDate(2461043))
|
|
th.push(MakeDate(2461033))
|
|
th.Minus()
|
|
|
|
r := th.pop()
|
|
if !r.IsLong() || r.AsLong() != 10 {
|
|
t.Errorf("Date - Date = %v, want 10", r)
|
|
}
|
|
th.EndProc()
|
|
}
|
|
|
|
// --- Mult ---
|
|
|
|
func TestMultIntInt(t *testing.T) {
|
|
th := newTestThread()
|
|
th.Frame(0, 0)
|
|
|
|
th.PushInt(6)
|
|
th.PushInt(7)
|
|
th.Mult()
|
|
|
|
r := th.pop()
|
|
if r.AsNumInt() != 42 {
|
|
t.Errorf("6 * 7 = %d, want 42", r.AsNumInt())
|
|
}
|
|
th.EndProc()
|
|
}
|
|
|
|
func TestMultDecimalRule(t *testing.T) {
|
|
th := newTestThread()
|
|
th.Frame(0, 0)
|
|
|
|
th.PushDouble(1.5, 3, 1) // 1 decimal
|
|
th.PushDouble(2.5, 3, 1) // 1 decimal
|
|
th.Mult()
|
|
|
|
r := th.pop()
|
|
// Mult decimal = dec1 + dec2 = 2
|
|
if r.Decimal() != 2 {
|
|
t.Errorf("decimal = %d, want 2", r.Decimal())
|
|
}
|
|
if math.Abs(r.AsDouble()-3.75) > 1e-10 {
|
|
t.Errorf("1.5 * 2.5 = %g, want 3.75", r.AsDouble())
|
|
}
|
|
th.EndProc()
|
|
}
|
|
|
|
func TestMultIntOverflow(t *testing.T) {
|
|
th := newTestThread()
|
|
th.Frame(0, 0)
|
|
|
|
th.PushLong(math.MaxInt64)
|
|
th.PushLong(2)
|
|
th.Mult()
|
|
|
|
r := th.pop()
|
|
if !r.IsDouble() {
|
|
t.Errorf("MaxInt64 * 2 should overflow to Double")
|
|
}
|
|
th.EndProc()
|
|
}
|
|
|
|
// --- Divide ---
|
|
|
|
func TestDivide(t *testing.T) {
|
|
th := newTestThread()
|
|
th.Frame(0, 0)
|
|
|
|
th.PushInt(10)
|
|
th.PushInt(3)
|
|
th.Divide()
|
|
|
|
r := th.pop()
|
|
if !r.IsDouble() {
|
|
t.Error("division should always return Double")
|
|
}
|
|
if math.Abs(r.AsDouble()-3.333333) > 0.001 {
|
|
t.Errorf("10 / 3 = %g", r.AsDouble())
|
|
}
|
|
th.EndProc()
|
|
}
|
|
|
|
func TestDivideByZero(t *testing.T) {
|
|
th := newTestThread()
|
|
th.Frame(0, 0)
|
|
|
|
defer func() {
|
|
r := recover()
|
|
if r == nil {
|
|
t.Error("expected panic on division by zero")
|
|
}
|
|
hbErr, ok := r.(*HbError)
|
|
if !ok {
|
|
t.Errorf("expected HbError, got %T", r)
|
|
}
|
|
if hbErr.GenCode != 1340 {
|
|
t.Errorf("GenCode = %d, want 1340 (EG_ZERODIV)", hbErr.GenCode)
|
|
}
|
|
}()
|
|
|
|
th.PushInt(10)
|
|
th.PushInt(0)
|
|
th.Divide()
|
|
}
|
|
|
|
// --- Modulus ---
|
|
|
|
func TestModulus(t *testing.T) {
|
|
th := newTestThread()
|
|
th.Frame(0, 0)
|
|
|
|
th.PushInt(10)
|
|
th.PushInt(3)
|
|
th.Modulus()
|
|
|
|
r := th.pop()
|
|
if !r.IsDouble() || r.AsDouble() != 1.0 {
|
|
t.Errorf("10 %% 3 = %v, want 1.0", r)
|
|
}
|
|
th.EndProc()
|
|
}
|
|
|
|
// --- Power ---
|
|
|
|
func TestPower(t *testing.T) {
|
|
th := newTestThread()
|
|
th.Frame(0, 0)
|
|
|
|
th.PushInt(2)
|
|
th.PushInt(10)
|
|
th.Power()
|
|
|
|
r := th.pop()
|
|
if r.AsDouble() != 1024.0 {
|
|
t.Errorf("2 ** 10 = %g, want 1024", r.AsDouble())
|
|
}
|
|
th.EndProc()
|
|
}
|
|
|
|
// --- Negate ---
|
|
|
|
func TestNegate(t *testing.T) {
|
|
th := newTestThread()
|
|
th.Frame(0, 0)
|
|
|
|
th.PushInt(42)
|
|
th.Negate()
|
|
if th.pop().AsNumInt() != -42 {
|
|
t.Error("negate 42 should be -42")
|
|
}
|
|
|
|
th.PushDouble(3.14, 4, 2)
|
|
th.Negate()
|
|
r := th.pop()
|
|
if r.AsDouble() != -3.14 {
|
|
t.Errorf("negate 3.14 = %g", r.AsDouble())
|
|
}
|
|
th.EndProc()
|
|
}
|
|
|
|
// --- Inc / Dec ---
|
|
|
|
func TestIncDec(t *testing.T) {
|
|
th := newTestThread()
|
|
th.Frame(0, 0)
|
|
|
|
th.PushInt(10)
|
|
th.Inc()
|
|
if th.peek().AsNumInt() != 11 {
|
|
t.Error("Inc(10) should be 11")
|
|
}
|
|
th.Dec()
|
|
th.Dec()
|
|
if th.pop().AsNumInt() != 9 {
|
|
t.Error("Dec(Dec(11)) should be 9")
|
|
}
|
|
th.EndProc()
|
|
}
|
|
|
|
// --- AddInt optimization ---
|
|
|
|
func TestAddInt(t *testing.T) {
|
|
th := newTestThread()
|
|
th.Frame(0, 0)
|
|
|
|
th.PushInt(100)
|
|
th.AddInt(50)
|
|
if th.pop().AsNumInt() != 150 {
|
|
t.Error("100 + 50 should be 150")
|
|
}
|
|
|
|
th.push(MakeDate(2461033))
|
|
th.AddInt(7)
|
|
r := th.pop()
|
|
if !r.IsDate() || r.AsJulian() != 2461040 {
|
|
t.Errorf("Date + 7 = %v", r)
|
|
}
|
|
th.EndProc()
|
|
}
|
|
|
|
// --- LocalAdd optimization ---
|
|
|
|
func TestLocalAdd(t *testing.T) {
|
|
th := newTestThread()
|
|
th.Frame(0, 2)
|
|
|
|
th.LocalSetInt(1, 100)
|
|
th.PushInt(50)
|
|
th.LocalAdd(1)
|
|
|
|
if th.Local(1).AsNumInt() != 150 {
|
|
t.Errorf("local += 50: got %d, want 150", th.Local(1).AsNumInt())
|
|
}
|
|
th.EndProc()
|
|
}
|
|
|
|
// --- LocalAddInt optimization ---
|
|
|
|
func TestLocalAddInt(t *testing.T) {
|
|
th := newTestThread()
|
|
th.Frame(0, 2)
|
|
|
|
th.LocalSetInt(1, 0)
|
|
for i := int64(1); i <= 10; i++ {
|
|
th.LocalAddInt(1, i)
|
|
}
|
|
|
|
if th.Local(1).AsNumInt() != 55 {
|
|
t.Errorf("sum 1..10 = %d, want 55", th.Local(1).AsNumInt())
|
|
}
|
|
th.EndProc()
|
|
}
|