Files
five/hbrt/ops_arith.go
Charles KWON OhJun 486e466592 feat: FiveSql2 43/43, @byref, mutable closure, RTL 479, DateTime fix
Major changes since last commit:
- FiveSql2 SQL:1999 engine (10,458 LOC) — 43/43 ALL PASS
- 21 compiler/runtime bugs fixed (short-circuit AND/OR, FOR LOOP, etc.)
- @byref pass-by-reference via RefCell pattern
- Mutable closure capture (EnsureLocalRef + RefCell sharing)
- RTL: 400 → 479 functions (+79: file, string, datetime, hash, UTF-8)
- DateTime/Timestamp fully working (hb_DateTime, hb_Hour/Min/Sec, display)
- Reserved word guard (39 keywords blocked from function calls)
- AEval arg order fix (element before index)
- Closure capture redecl fix (unique _cap_ names per block)
- Hash/string indexing in ArrayPush/ArrayPop
- Harbour compat test suite: 51/51
- 4 docs: Porting Report, Implementation Plan, Optimization Plan, Commercialization

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 11:35:37 +09:00

390 lines
8.7 KiB
Go

// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com)
// All rights reserved.
// Arithmetic operations for the Five runtime.
// Implements Harbour-compatible type promotion, overflow detection,
// and decimal precision propagation rules.
//
// See docs/harbour-type-system-analysis.md Section 5 for details.
package hbrt
import "math"
// Plus pops two values, pushes their sum.
// Harbour: hb_vmPlus (hvm.c:3285)
//
// Type rules:
// NumInt + NumInt -> NumInt (overflow -> Double)
// Numeric + Numeric -> Double
// String + String -> String (concatenation)
// Date + Numeric -> Date
// Timestamp + Numeric -> Timestamp
func (t *Thread) Plus() {
b := t.pop()
a := t.pop()
// Fast path: Int + Int
if a.IsNumInt() && b.IsNumInt() {
an, bn := a.AsNumInt(), b.AsNumInt()
r := an + bn
// Overflow detection (Harbour pattern)
if (bn >= 0 && r >= an) || (bn < 0 && r < an) {
t.push(MakeNumInt(r))
} else {
t.push(MakeDoubleAuto(float64(an) + float64(bn)))
}
return
}
// Numeric + Numeric -> Double
if a.IsNumeric() && b.IsNumeric() {
ad, bd := a.AsNumDouble(), b.AsNumDouble()
dec := maxDec(a.Decimal(), b.Decimal())
t.push(MakeDouble(ad+bd, 255, dec))
return
}
// String + String -> concatenation
if a.IsString() && b.IsString() {
t.push(MakeString(a.AsString() + b.AsString()))
return
}
// Date + NumInt -> Date (add days)
if a.IsDate() && b.IsNumInt() {
t.push(MakeDate(a.AsJulian() + b.AsNumInt()))
return
}
if a.IsNumInt() && b.IsDate() {
t.push(MakeDate(a.AsNumInt() + b.AsJulian()))
return
}
// Timestamp + Numeric -> Timestamp
if a.IsTimestamp() && b.IsNumeric() {
days := int64(b.AsNumDouble())
frac := b.AsNumDouble() - float64(days)
ms := int32(frac * 86400000.0)
newJulian := a.AsJulian() + days
newTime := a.AsTimeMs() + ms
if newTime >= 86400000 {
newJulian++
newTime -= 86400000
} else if newTime < 0 {
newJulian--
newTime += 86400000
}
t.push(MakeTimestamp(newJulian, newTime))
return
}
panic(t.argError("+", a, b))
}
// Minus pops two values, pushes their difference.
// Harbour: hb_vmMinus (hvm.c:3401)
func (t *Thread) Minus() {
b := t.pop()
a := t.pop()
// Fast path: Int - Int
if a.IsNumInt() && b.IsNumInt() {
an, bn := a.AsNumInt(), b.AsNumInt()
r := an - bn
if (bn <= 0 && r >= an) || (bn > 0 && r < an) {
t.push(MakeNumInt(r))
} else {
t.push(MakeDoubleAuto(float64(an) - float64(bn)))
}
return
}
// Numeric - Numeric -> Double
if a.IsNumeric() && b.IsNumeric() {
ad, bd := a.AsNumDouble(), b.AsNumDouble()
dec := maxDec(a.Decimal(), b.Decimal())
t.push(MakeDouble(ad-bd, 255, dec))
return
}
// Date - Date -> Long (difference in days)
if a.IsDate() && b.IsDate() {
t.push(MakeLong(a.AsJulian() - b.AsJulian()))
return
}
// Date - NumInt -> Date
if a.IsDate() && b.IsNumInt() {
t.push(MakeDate(a.AsJulian() - b.AsNumInt()))
return
}
// Timestamp - Timestamp -> Double or Long
if a.IsTimestamp() && b.IsTimestamp() {
dayDiff := a.AsJulian() - b.AsJulian()
timeDiff := a.AsTimeMs() - b.AsTimeMs()
if timeDiff != 0 {
t.push(MakeDoubleAuto(float64(dayDiff) + float64(timeDiff)/86400000.0))
} else {
t.push(MakeLong(dayDiff))
}
return
}
panic(t.argError("-", a, b))
}
// Mult pops two values, pushes their product.
// Harbour: hb_vmMult (hvm.c:3510)
// Decimal rule: dec = dec1 + dec2
func (t *Thread) Mult() {
b := t.pop()
a := t.pop()
if a.IsNumInt() && b.IsNumInt() {
an, bn := a.AsNumInt(), b.AsNumInt()
if an == 0 || bn == 0 {
t.push(MakeNumInt(0))
return
}
r := an * bn
if r/an == bn {
t.push(MakeNumInt(r))
} else {
t.push(MakeDoubleAuto(float64(an) * float64(bn)))
}
return
}
if a.IsNumeric() && b.IsNumeric() {
ad, bd := a.AsNumDouble(), b.AsNumDouble()
dec := a.Decimal() + b.Decimal()
if dec > 255 {
dec = 255
}
t.push(MakeDouble(ad*bd, 255, dec))
return
}
panic(t.argError("*", a, b))
}
// Divide pops two values, pushes the quotient.
// Harbour: hb_vmDivide (hvm.c:3546)
// Always returns Double. Division by zero -> runtime error.
func (t *Thread) Divide() {
b := t.pop()
a := t.pop()
if a.IsNumeric() && b.IsNumeric() {
bd := b.AsNumDouble()
if bd == 0 {
panic(t.divisionByZero())
}
ad := a.AsNumDouble()
t.push(MakeDoubleAuto(ad / bd))
return
}
panic(t.argError("/", a, b))
}
// Modulus pops two values, pushes the remainder.
// Harbour: hb_vmModulus (hvm.c:3608)
// Always returns Double.
func (t *Thread) Modulus() {
b := t.pop()
a := t.pop()
if a.IsNumeric() && b.IsNumeric() {
bd := b.AsNumDouble()
if bd == 0 {
panic(t.divisionByZero())
}
ad := a.AsNumDouble()
t.push(MakeDoubleAuto(math.Mod(ad, bd)))
return
}
panic(t.argError("%", a, b))
}
// Power pops two values, pushes base^exponent.
// Harbour: hb_vmPower
// Always returns Double.
func (t *Thread) Power() {
b := t.pop()
a := t.pop()
if a.IsNumeric() && b.IsNumeric() {
ad, bd := a.AsNumDouble(), b.AsNumDouble()
t.push(MakeDoubleAuto(math.Pow(ad, bd)))
return
}
panic(t.argError("**", a, b))
}
// Negate negates the top of stack.
// Harbour: hb_vmNegate
func (t *Thread) Negate() {
a := t.pop()
if a.IsNumInt() {
t.push(MakeNumInt(-a.AsNumInt()))
return
}
if a.IsDouble() {
t.push(MakeDouble(-a.AsDouble(), a.Length(), a.Decimal()))
return
}
panic(t.argError("negate", a))
}
// Inc increments the top of stack by 1.
// Harbour: hb_vmInc
func (t *Thread) Inc() {
a := t.pop()
if a.IsNumInt() {
t.push(MakeNumInt(a.AsNumInt() + 1))
return
}
if a.IsDouble() {
t.push(MakeDouble(a.AsDouble()+1, a.Length(), a.Decimal()))
return
}
panic(t.argError("++", a))
}
// Dec decrements the top of stack by 1.
// Harbour: hb_vmDec
func (t *Thread) Dec() {
a := t.pop()
if a.IsNumInt() {
t.push(MakeNumInt(a.AsNumInt() - 1))
return
}
if a.IsDouble() {
t.push(MakeDouble(a.AsDouble()-1, a.Length(), a.Decimal()))
return
}
panic(t.argError("--", a))
}
// --- Optimized operations (used by generated code) ---
// AddInt adds an integer constant to the top of stack.
// Harbour: hb_xvmAddInt
func (t *Thread) AddInt(n int64) {
a := t.pop()
if a.IsNumInt() {
an := a.AsNumInt()
r := an + n
if (n >= 0 && r >= an) || (n < 0 && r < an) {
t.push(MakeNumInt(r))
} else {
t.push(MakeDoubleAuto(float64(an) + float64(n)))
}
return
}
if a.IsDouble() {
t.push(MakeDouble(a.AsDouble()+float64(n), a.Length(), a.Decimal()))
return
}
if a.IsDate() {
t.push(MakeDate(a.AsJulian() + n))
return
}
panic(t.argError("+int", a))
}
// LocalAdd adds the top of stack to a local variable, pops the value. Byref-aware.
// Harbour: hb_xvmLocalAdd
func (t *Thread) LocalAdd(n int) {
val := t.pop()
idx := t.localIndex(n)
loc := t.locals[idx]
isRef := loc.Type() == tByref
if isRef {
loc = (*HbRefCell)(loc.ptr).V
}
var result Value
if loc.IsNumInt() && val.IsNumInt() {
r := loc.AsNumInt() + val.AsNumInt()
if (val.AsNumInt() >= 0 && r >= loc.AsNumInt()) || (val.AsNumInt() < 0 && r < loc.AsNumInt()) {
result = MakeNumInt(r)
} else {
result = MakeDoubleAuto(float64(loc.AsNumInt()) + float64(val.AsNumInt()))
}
} else if loc.IsNumeric() && val.IsNumeric() {
dec := maxDec(loc.Decimal(), val.Decimal())
result = MakeDouble(loc.AsNumDouble()+val.AsNumDouble(), 255, dec)
} else if loc.IsString() && val.IsString() {
result = MakeString(loc.AsString() + val.AsString())
} else {
panic(t.argError("+=", loc, val))
}
if isRef {
(*HbRefCell)(t.locals[idx].ptr).V = result
} else {
t.locals[idx] = result
}
}
// LocalAddInt adds an integer constant directly to a local variable. Byref-aware.
// Harbour: hb_xvmLocalAddInt (fused PUSHINT + PLUS + POPLOCAL)
func (t *Thread) LocalAddInt(n int, val int64) {
idx := t.curFrame.localBase + n - 1 // inline index
loc := t.locals[idx]
isRef := loc.Type() == tByref
if isRef {
loc = (*HbRefCell)(loc.ptr).V
}
var result Value
if loc.IsNumInt() {
r := loc.AsNumInt() + val
if (val >= 0 && r >= loc.AsNumInt()) || (val < 0 && r < loc.AsNumInt()) {
result = MakeNumInt(r)
} else {
result = MakeDoubleAuto(float64(loc.AsNumInt()) + float64(val))
}
} else if loc.IsDouble() {
result = MakeDouble(loc.AsDouble()+float64(val), loc.Length(), loc.Decimal())
} else if loc.IsDate() {
result = MakeDate(loc.AsJulian() + val)
} else {
panic(t.argError("+int", loc))
}
if isRef {
(*HbRefCell)(t.locals[idx].ptr).V = result
} else {
t.locals[idx] = result
}
}
// --- Helpers ---
func maxDec(a, b uint16) uint16 {
if a == 255 || b == 255 {
return 255 // HB_DEFAULT_DECIMALS
}
if a > b {
return a
}
return b
}
func (t *Thread) divisionByZero() *HbError {
return &HbError{
Description: "division by zero",
Operation: "/",
SubSystem: "BASE",
GenCode: 1340, // EG_ZERODIV
}
}