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>
379 lines
9.8 KiB
Plaintext
379 lines
9.8 KiB
Plaintext
/*
|
|
* compat_harbour.prg — Harbour Compatibility Test Suite for Five
|
|
*
|
|
* Tests language features that differ between Harbour and Go semantics.
|
|
* Run: five build tests/compat_harbour.prg -o test_compat && ./test_compat
|
|
*
|
|
* Copyright (c) 2026 Charles KWON OhJun
|
|
*/
|
|
|
|
STATIC s_nPass := 0
|
|
STATIC s_nFail := 0
|
|
|
|
PROCEDURE Main()
|
|
|
|
? "================================================================"
|
|
? " Five — Harbour Compatibility Test Suite"
|
|
? "================================================================"
|
|
?
|
|
|
|
TestByref()
|
|
TestShortCircuit()
|
|
TestForLoop()
|
|
TestSequence()
|
|
TestClosure()
|
|
TestTypes()
|
|
TestStatic()
|
|
TestLocalScope()
|
|
TestArrayHash()
|
|
|
|
?
|
|
? "================================================================"
|
|
? " Results:", hb_ntos(s_nPass), "/", hb_ntos(s_nPass + s_nFail), "passed"
|
|
? "================================================================"
|
|
|
|
IF s_nFail > 0
|
|
ERRORLEVEL(1)
|
|
ENDIF
|
|
|
|
RETURN
|
|
|
|
|
|
/* ====================================================================== */
|
|
/* 1. @byref pass-by-reference */
|
|
/* ====================================================================== */
|
|
STATIC PROCEDURE TestByref()
|
|
|
|
LOCAL n := 10, cStr := "hello"
|
|
|
|
? "--- 1. @byref ---"
|
|
|
|
// Basic
|
|
ByrefModify(@n)
|
|
Assert("1a @byref basic: n changed to 42", n == 42)
|
|
|
|
// Chained
|
|
n := 100
|
|
ByrefMiddle(@n)
|
|
Assert("1b @byref chained: n changed to 999", n == 999)
|
|
|
|
// Loop accumulation
|
|
LOCAL nSum := 0, i
|
|
FOR i := 1 TO 5
|
|
ByrefAdd(@nSum, i)
|
|
NEXT
|
|
Assert("1c @byref loop: sum 1..5 = 15", nSum == 15)
|
|
|
|
// String
|
|
ByrefAppend(@cStr, " world")
|
|
Assert("1d @byref string: 'hello world'", cStr == "hello world")
|
|
|
|
RETURN
|
|
|
|
STATIC FUNCTION ByrefModify(x)
|
|
x := 42
|
|
RETURN NIL
|
|
|
|
STATIC FUNCTION ByrefMiddle(x)
|
|
ByrefInner(@x)
|
|
RETURN NIL
|
|
|
|
STATIC FUNCTION ByrefInner(y)
|
|
y := 999
|
|
RETURN NIL
|
|
|
|
STATIC FUNCTION ByrefAdd(nAcc, nVal)
|
|
nAcc := nAcc + nVal
|
|
RETURN NIL
|
|
|
|
STATIC FUNCTION ByrefAppend(cP, cS)
|
|
cP := cP + cS
|
|
RETURN NIL
|
|
|
|
|
|
/* ====================================================================== */
|
|
/* 2. Short-circuit AND/OR */
|
|
/* ====================================================================== */
|
|
STATIC PROCEDURE TestShortCircuit()
|
|
|
|
LOCAL lCalled
|
|
|
|
? "--- 2. Short-circuit AND/OR ---"
|
|
|
|
// .AND. short-circuits on false left
|
|
lCalled := .F.
|
|
IF .F. .AND. SideEffect(@lCalled)
|
|
ENDIF
|
|
Assert("2a AND short-circuit: right not called", ! lCalled)
|
|
|
|
// .OR. short-circuits on true left
|
|
lCalled := .F.
|
|
IF .T. .OR. SideEffect(@lCalled)
|
|
ENDIF
|
|
Assert("2b OR short-circuit: right not called", ! lCalled)
|
|
|
|
// .AND. evaluates right when left is true
|
|
lCalled := .F.
|
|
IF .T. .AND. SideEffect(@lCalled)
|
|
ENDIF
|
|
Assert("2c AND evaluates right when left=.T.", lCalled)
|
|
|
|
// NIL in condition → .F.
|
|
Assert("2d NIL .AND. .T. = .F.", ! (NIL .AND. .T.))
|
|
|
|
RETURN
|
|
|
|
STATIC FUNCTION SideEffect(lFlag)
|
|
lFlag := .T.
|
|
RETURN .T.
|
|
|
|
|
|
/* ====================================================================== */
|
|
/* 3. FOR..NEXT LOOP/EXIT */
|
|
/* ====================================================================== */
|
|
STATIC PROCEDURE TestForLoop()
|
|
|
|
LOCAL i, n, nLoopCount
|
|
|
|
? "--- 3. FOR..NEXT LOOP ---"
|
|
|
|
// Basic FOR
|
|
n := 0
|
|
FOR i := 1 TO 5
|
|
n += i
|
|
NEXT
|
|
Assert("3a FOR sum 1..5 = 15", n == 15)
|
|
|
|
// FOR with EXIT
|
|
n := 0
|
|
FOR i := 1 TO 100
|
|
IF i > 5
|
|
EXIT
|
|
ENDIF
|
|
n += i
|
|
NEXT
|
|
Assert("3b FOR EXIT: sum 1..5 = 15", n == 15)
|
|
|
|
// FOR with LOOP (LOOP goes to NEXT, increments counter)
|
|
nLoopCount := 0
|
|
FOR i := 1 TO 5
|
|
nLoopCount++
|
|
IF i == 3
|
|
LOOP // should skip to NEXT (i becomes 4)
|
|
ENDIF
|
|
NEXT
|
|
Assert("3c FOR LOOP: counter = 5 (not infinite)", nLoopCount == 5)
|
|
|
|
// FOR STEP -1
|
|
n := 0
|
|
FOR i := 5 TO 1 STEP -1
|
|
n += i
|
|
NEXT
|
|
Assert("3d FOR STEP -1: sum 5..1 = 15", n == 15)
|
|
|
|
RETURN
|
|
|
|
|
|
/* ====================================================================== */
|
|
/* 4. BEGIN SEQUENCE / RECOVER */
|
|
/* ====================================================================== */
|
|
STATIC PROCEDURE TestSequence()
|
|
|
|
LOCAL lRecovered, nResult
|
|
|
|
? "--- 4. BEGIN SEQUENCE ---"
|
|
|
|
// Basic recover
|
|
lRecovered := .F.
|
|
BEGIN SEQUENCE
|
|
nResult := 1 / 0 // division by zero
|
|
RECOVER
|
|
lRecovered := .T.
|
|
END SEQUENCE
|
|
Assert("4a RECOVER catches error", lRecovered)
|
|
|
|
// Normal flow (no error)
|
|
lRecovered := .F.
|
|
nResult := 0
|
|
BEGIN SEQUENCE
|
|
nResult := 42
|
|
RECOVER
|
|
lRecovered := .T.
|
|
END SEQUENCE
|
|
Assert("4b No error: result = 42", nResult == 42 .AND. ! lRecovered)
|
|
|
|
// Nested SEQUENCE
|
|
LOCAL lOuter := .F., lInner := .F.
|
|
BEGIN SEQUENCE
|
|
BEGIN SEQUENCE
|
|
nResult := 1 / 0
|
|
RECOVER
|
|
lInner := .T.
|
|
END SEQUENCE
|
|
RECOVER
|
|
lOuter := .T.
|
|
END SEQUENCE
|
|
Assert("4c Nested: inner caught, outer not", lInner .AND. ! lOuter)
|
|
|
|
RETURN
|
|
|
|
|
|
/* ====================================================================== */
|
|
/* 5. Code block closure capture */
|
|
/* ====================================================================== */
|
|
STATIC PROCEDURE TestClosure()
|
|
|
|
LOCAL bBlock, nOuter := 10
|
|
|
|
? "--- 5. Closure ---"
|
|
|
|
// Basic capture
|
|
bBlock := {|| nOuter * 2}
|
|
Assert("5a Closure captures outer: 10*2=20", Eval(bBlock) == 20)
|
|
|
|
// Capture with parameter
|
|
bBlock := {|x| nOuter + x}
|
|
Assert("5b Closure with param: 10+5=15", Eval(bBlock, 5) == 15)
|
|
|
|
// Closure returning value
|
|
bBlock := {|a,b| a + b}
|
|
Assert("5c Closure 2 params: 3+7=10", Eval(bBlock, 3, 7) == 10)
|
|
|
|
RETURN
|
|
|
|
|
|
/* ====================================================================== */
|
|
/* 6. Type system */
|
|
/* ====================================================================== */
|
|
STATIC PROCEDURE TestTypes()
|
|
|
|
? "--- 6. Types ---"
|
|
|
|
Assert("6a ValType(NIL) = 'U'", ValType(NIL) == "U")
|
|
Assert("6b ValType(1) = 'N'", ValType(1) == "N")
|
|
Assert("6c ValType('a') = 'C'", ValType("a") == "C")
|
|
Assert("6d ValType(.T.) = 'L'", ValType(.T.) == "L")
|
|
Assert("6e ValType({}) = 'A'", ValType({}) == "A")
|
|
Assert("6f ValType({=>}) = 'H'", ValType({=>}) == "H")
|
|
Assert("6g ValType({||}) = 'B'", ValType({|| NIL}) == "B")
|
|
Assert("6h NIL == NIL", NIL == NIL)
|
|
Assert("6i NIL != 0", !( NIL == 0 ))
|
|
Assert("6j Empty('')", Empty(""))
|
|
Assert("6k Empty(0)", Empty(0))
|
|
Assert("6l ! Empty(1)", ! Empty(1))
|
|
|
|
RETURN
|
|
|
|
|
|
/* ====================================================================== */
|
|
/* 7. STATIC variables */
|
|
/* ====================================================================== */
|
|
STATIC PROCEDURE TestStatic()
|
|
|
|
? "--- 7. STATIC ---"
|
|
|
|
Assert("7a STATIC counter 1st call = 1", StaticCounter() == 1)
|
|
Assert("7b STATIC counter 2nd call = 2", StaticCounter() == 2)
|
|
Assert("7c STATIC counter 3rd call = 3", StaticCounter() == 3)
|
|
|
|
RETURN
|
|
|
|
STATIC s_nCounter := 0
|
|
|
|
STATIC FUNCTION StaticCounter()
|
|
s_nCounter++
|
|
RETURN s_nCounter
|
|
|
|
|
|
/* ====================================================================== */
|
|
/* 8. LOCAL scope */
|
|
/* ====================================================================== */
|
|
STATIC PROCEDURE TestLocalScope()
|
|
|
|
LOCAL x := "outer"
|
|
|
|
? "--- 8. LOCAL scope ---"
|
|
|
|
Assert("8a LOCAL before IF: 'outer'", x == "outer")
|
|
Assert("8b LOCAL after assignment: unchanged", x == "outer")
|
|
|
|
// Multiple LOCALs in same function
|
|
LOCAL y := 99
|
|
Assert("8b Multiple LOCALs: y=99", y == 99)
|
|
Assert("8c x still 'outer'", x == "outer")
|
|
|
|
RETURN
|
|
|
|
|
|
/* ====================================================================== */
|
|
/* 9. Array + Hash operations */
|
|
/* ====================================================================== */
|
|
STATIC PROCEDURE TestArrayHash()
|
|
|
|
LOCAL a, h, i, nSum
|
|
|
|
? "--- 9. Array + Hash ---"
|
|
|
|
// Array basics
|
|
a := {1, 2, 3}
|
|
Assert("9a Array literal: Len=3", Len(a) == 3)
|
|
AAdd(a, 4)
|
|
Assert("9b AAdd: Len=4", Len(a) == 4)
|
|
|
|
// ASort
|
|
a := {3, 1, 2}
|
|
ASort(a)
|
|
Assert("9c ASort: {1,2,3}", a[1] == 1 .AND. a[2] == 2 .AND. a[3] == 3)
|
|
|
|
// ASort with block
|
|
a := {3, 1, 2}
|
|
ASort(a,,, {|x,y| x > y})
|
|
Assert("9d ASort desc: {3,2,1}", a[1] == 3 .AND. a[2] == 2 .AND. a[3] == 1)
|
|
|
|
// AScan
|
|
a := {"alice", "bob", "charlie"}
|
|
Assert("9e AScan: found 'bob' at 2", AScan(a, "bob") == 2)
|
|
Assert("9f AScan: 'dave' not found", AScan(a, "dave") == 0)
|
|
|
|
// AEval with mutable closure capture (Harbour: closures share outer locals)
|
|
nSum := 0
|
|
AEval({10, 20, 30}, {|x| nSum += x})
|
|
Assert("9g AEval closure sum: 60", nSum == 60)
|
|
|
|
// Hash basics
|
|
h := {=>}
|
|
h["name"] := "Alice"
|
|
h["age"] := 30
|
|
Assert("9h Hash set/get: 'Alice'", h["name"] == "Alice")
|
|
Assert("9i Hash Len: 2", Len(h) == 2)
|
|
Assert("9j hb_HHasKey: .T.", hb_HHasKey(h, "name"))
|
|
Assert("9k hb_HHasKey missing: .F.", ! hb_HHasKey(h, "xyz"))
|
|
|
|
// Hash iteration
|
|
LOCAL aKeys := hb_HKeys(h)
|
|
Assert("9l hb_HKeys Len: 2", Len(aKeys) == 2)
|
|
|
|
// hb_HClone
|
|
LOCAL h2 := hb_HClone(h)
|
|
h2["name"] := "Bob"
|
|
Assert("9m hb_HClone: orig unchanged", h["name"] == "Alice")
|
|
Assert("9n hb_HClone: clone changed", h2["name"] == "Bob")
|
|
|
|
RETURN
|
|
|
|
|
|
/* ====================================================================== */
|
|
/* Assert helper */
|
|
/* ====================================================================== */
|
|
STATIC FUNCTION Assert(cLabel, lOK)
|
|
|
|
IF lOK
|
|
s_nPass++
|
|
? " PASS:", cLabel
|
|
ELSE
|
|
s_nFail++
|
|
? " FAIL:", cLabel
|
|
ENDIF
|
|
|
|
RETURN NIL
|