// 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() }