- 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>
303 lines
6.7 KiB
Go
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
|
|
}
|