- 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>
379 lines
8.3 KiB
Go
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
|
|
}
|
|
}
|