Files
five/hbrt/ops_arith.go
Charles KWON OhJun 59568f3301 Five v0.9 — Harbour + Go fusion language
- 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>
2026-03-31 09:41:50 +09:00

379 lines
8.3 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.
// Harbour: hb_xvmLocalAdd
func (t *Thread) LocalAdd(n int) {
val := t.pop()
idx := t.localIndex(n)
loc := t.locals[idx]
if loc.IsNumInt() && val.IsNumInt() {
r := loc.AsNumInt() + val.AsNumInt()
if (val.AsNumInt() >= 0 && r >= loc.AsNumInt()) || (val.AsNumInt() < 0 && r < loc.AsNumInt()) {
t.locals[idx] = MakeNumInt(r)
} else {
t.locals[idx] = MakeDoubleAuto(float64(loc.AsNumInt()) + float64(val.AsNumInt()))
}
return
}
if loc.IsNumeric() && val.IsNumeric() {
dec := maxDec(loc.Decimal(), val.Decimal())
t.locals[idx] = MakeDouble(loc.AsNumDouble()+val.AsNumDouble(), 255, dec)
return
}
if loc.IsString() && val.IsString() {
t.locals[idx] = MakeString(loc.AsString() + val.AsString())
return
}
panic(t.argError("+=", loc, val))
}
// LocalAddInt adds an integer constant directly to a local variable.
// Harbour: hb_xvmLocalAddInt (fused PUSHINT + PLUS + POPLOCAL)
func (t *Thread) LocalAddInt(n int, val int64) {
idx := t.localIndex(n)
loc := t.locals[idx]
if loc.IsNumInt() {
r := loc.AsNumInt() + val
if (val >= 0 && r >= loc.AsNumInt()) || (val < 0 && r < loc.AsNumInt()) {
t.locals[idx] = MakeNumInt(r)
} else {
t.locals[idx] = MakeDoubleAuto(float64(loc.AsNumInt()) + float64(val))
}
return
}
if loc.IsDouble() {
t.locals[idx] = MakeDouble(loc.AsDouble()+float64(val), loc.Length(), loc.Decimal())
return
}
if loc.IsDate() {
t.locals[idx] = MakeDate(loc.AsJulian() + val)
return
}
panic(t.argError("+int", loc))
}
// --- 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
}
}