Files
five/hbrt/ops_compare.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

303 lines
6.7 KiB
Go

// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com)
// All rights reserved.
// Comparison and logical operations for the Five runtime.
// Implements Harbour-compatible comparison semantics including:
// - NIL == NIL → true, NIL == anything → false
// - Numeric cross-type comparison (Int/Long/Double auto-promotion)
// - String comparison respecting SET EXACT
// - Date/Timestamp comparison
// - Logical XOR equality (Clipper quirk)
//
// Operator hierarchy inspired by tsgo's isEqualityOperator/isRelationalOperatorOrHigher
// pattern (ref/typescript-go/internal/checker/utilities.go:772).
//
// See docs/harbour-type-system-analysis.md Section 6 for full rules.
package hbrt
import "strings"
// --- Equality operators ---
// Equal pops two values, pushes boolean result.
// Harbour: hb_vmEqual (hvm.c:3974)
func (t *Thread) Equal() {
b := t.pop()
a := t.pop()
t.push(MakeBool(valueEqual(a, b)))
}
// ExactEqual pops two values, pushes boolean result.
// Harbour: hb_vmExactlyEqual — arrays cannot override.
func (t *Thread) ExactEqual() {
b := t.pop()
a := t.pop()
t.push(MakeBool(valueExactEqual(a, b)))
}
// NotEqual pops two values, pushes boolean result.
func (t *Thread) NotEqual() {
b := t.pop()
a := t.pop()
t.push(MakeBool(!valueEqual(a, b)))
}
// --- Relational operators ---
// Less pops two values, pushes boolean result.
// Harbour: hb_vmLess (hvm.c:4176)
func (t *Thread) Less() {
b := t.pop()
a := t.pop()
cmp, ok := valueCompare(a, b)
if !ok {
panic(t.argError("<", a, b))
}
t.push(MakeBool(cmp < 0))
}
// LessEqual pops two values, pushes boolean result.
func (t *Thread) LessEqual() {
b := t.pop()
a := t.pop()
cmp, ok := valueCompare(a, b)
if !ok {
panic(t.argError("<=", a, b))
}
t.push(MakeBool(cmp <= 0))
}
// Greater pops two values, pushes boolean result.
func (t *Thread) Greater() {
b := t.pop()
a := t.pop()
cmp, ok := valueCompare(a, b)
if !ok {
panic(t.argError(">", a, b))
}
t.push(MakeBool(cmp > 0))
}
// GreaterEqual pops two values, pushes boolean result.
func (t *Thread) GreaterEqual() {
b := t.pop()
a := t.pop()
cmp, ok := valueCompare(a, b)
if !ok {
panic(t.argError(">=", a, b))
}
t.push(MakeBool(cmp >= 0))
}
// --- Logical operators ---
// Not negates the boolean value on top of stack.
func (t *Thread) Not() {
a := t.pop()
if !a.IsLogical() {
panic(t.argError(".NOT.", a))
}
t.push(MakeBool(!a.AsBool()))
}
// And pops two values, pushes logical AND.
// Harbour evaluates both sides (no short-circuit in VM ops).
func (t *Thread) And() {
b := t.pop()
a := t.pop()
if !a.IsLogical() || !b.IsLogical() {
panic(t.argError(".AND.", a, b))
}
t.push(MakeBool(a.AsBool() && b.AsBool()))
}
// Or pops two values, pushes logical OR.
func (t *Thread) Or() {
b := t.pop()
a := t.pop()
if !a.IsLogical() || !b.IsLogical() {
panic(t.argError(".OR.", a, b))
}
t.push(MakeBool(a.AsBool() || b.AsBool()))
}
// InString implements the $ operator: "bc" $ "abcde" → .T.
func (t *Thread) InString() {
b := t.pop()
a := t.pop()
if a.IsString() && b.IsString() {
t.push(MakeBool(strings.Contains(b.AsString(), a.AsString())))
} else {
panic(t.argError("$", a, b))
}
}
// PopLogical pops the top of stack and returns it as bool.
// Used by generated code for IF/WHILE conditions.
// Harbour: hb_xvmPopLogical
func (t *Thread) PopLogical() bool {
v := t.pop()
if !v.IsLogical() {
panic(t.argError("logical", v))
}
return v.AsBool()
}
// --- Optimized comparison (used by generated code) ---
// EqualIntIs compares stack top with an integer constant, returns bool.
// Harbour: hb_xvmEqualIntIs (fused PUSHINT + EQUAL)
func (t *Thread) EqualIntIs(n int64) bool {
a := t.pop()
if a.IsNumInt() {
return a.AsNumInt() == n
}
if a.IsDouble() {
return a.AsDouble() == float64(n)
}
return false
}
// --- Internal comparison functions ---
// valueEqual implements Harbour's equality semantics.
// NIL == NIL → true; NIL == anything → false
// Numeric: cross-type double comparison
// String: case-sensitive (SET EXACT ON assumed for now)
// Logical: XOR-like (Clipper quirk)
// Array/Hash/Block/Pointer: pointer identity
func valueEqual(a, b Value) bool {
at, bt := a.Type(), b.Type()
// NIL handling
if at == tNil && bt == tNil {
return true
}
if at == tNil || bt == tNil {
return false
}
// Numeric cross-type comparison
if a.IsNumeric() && b.IsNumeric() {
if a.IsNumInt() && b.IsNumInt() {
return a.AsNumInt() == b.AsNumInt()
}
return a.AsNumDouble() == b.AsNumDouble()
}
// Same type required from here
if at != bt {
return false
}
switch at {
case tString:
return a.AsString() == b.AsString()
case tDate:
return a.AsJulian() == b.AsJulian()
case tTimestamp:
return a.AsJulian() == b.AsJulian() && a.AsTimeMs() == b.AsTimeMs()
case tLogical:
// Harbour/Clipper quirk: XOR-like behavior
// .T. = .T. → .T. .F. = .F. → .T.
// .T. = .F. → .F. .F. = .T. → .F.
return a.AsBool() == b.AsBool()
case tArray, tObject:
// Pointer identity
return a.ptr == b.ptr
case tHash:
return a.ptr == b.ptr
case tBlock:
return a.ptr == b.ptr
case tPointer:
return a.scalar == b.scalar
}
return false
}
// valueExactEqual is like valueEqual but arrays/objects cannot override.
// Harbour: hb_vmExactlyEqual
func valueExactEqual(a, b Value) bool {
// For strings, exact equality checks full length (ignoring SET EXACT)
if a.IsString() && b.IsString() {
return a.AsString() == b.AsString()
}
return valueEqual(a, b)
}
// valueCompare returns comparison result (-1, 0, +1) and whether comparison is valid.
// Only String, Numeric, Date, Timestamp support ordering.
// Harbour: hb_vmLess, hb_vmGreater, etc. (hvm.c:4176+)
func valueCompare(a, b Value) (int, bool) {
// Numeric comparison
if a.IsNumeric() && b.IsNumeric() {
if a.IsNumInt() && b.IsNumInt() {
return compareInt64(a.AsNumInt(), b.AsNumInt()), true
}
return compareFloat64(a.AsNumDouble(), b.AsNumDouble()), true
}
at, bt := a.Type(), b.Type()
if at != bt {
return 0, false // type mismatch → error
}
switch at {
case tString:
return strings.Compare(a.AsString(), b.AsString()), true
case tDate:
return compareInt64(a.AsJulian(), b.AsJulian()), true
case tTimestamp:
cmp := compareInt64(a.AsJulian(), b.AsJulian())
if cmp != 0 {
return cmp, true
}
return compareInt32(a.AsTimeMs(), b.AsTimeMs()), true
}
return 0, false // unsupported type for ordering
}
// --- Primitive comparison helpers ---
// Following tsgo pattern of small, inlineable helper functions.
func compareInt64(a, b int64) int {
if a < b {
return -1
}
if a > b {
return 1
}
return 0
}
func compareInt32(a, b int32) int {
if a < b {
return -1
}
if a > b {
return 1
}
return 0
}
func compareFloat64(a, b float64) int {
if a < b {
return -1
}
if a > b {
return 1
}
return 0
}