Six more silent miscompiles in the pcode path, all uncovered by a
new pcode regression sweep that exercises the full PRG surface a
dynamic FrbCompile body could legitimately use.
* **xBase-keyword shadowing of variable names.** parseIdentStmt
and parseExprStmt's fallback switches consumed an entire line
when the leading IDENT matched LABEL / REPORT / ACCEPT / INPUT
/ NOTE / etc. Those words are also extremely common LOCAL /
PRIVATE names — `LOCAL label ; label := "x"` had the
assignment swallowed because the switch didn't peek at the
next token. Both switches now look at peek(1): an assignment
operator, [], (, -, ++, --, or `.` means it's a variable /
call / member access, not the xBase command, and we fall
through to expression parsing. Real silent bug — bit
test_frb_pcode_sweep's `LOCAL label` declaration.
* **`arr[i]` indexing not implemented in genpc.** ast.IndexExpr
fell through to the default PushNil path, so any indexed read
in a pcode-mode body returned NIL. New case emits the array,
the index, and PcOpArrayPush (the get-op; PcOpArrayPop is the
set-op — naming follows Harbour convention). Hashes go
through the same opcode, which already special-cases
IsHash() in ops_collection.go.
* **Hash literals not implemented in genpc + dispatch missing
in pcinterp.** `{ "k" => v, ... }` fell to PushNil. Added
HashLitExpr emit (Push key, Push value pairs, then PcOpHashGen
with count). Also wired up the PcOpHashGen dispatch in
execPcodeBody — it had been declared in pcode.go since the
initial design but the case statement was never added, so
even hand-written modules couldn't use hashes.
* **`x++` / `x--` postfix were silent no-ops.** PostfixExpr fell
to PushNil and the surrounding ExprStmt then popped the NIL.
DO WHILE loops with `n--` couldn't terminate; FOR loops with
`i++` in the body were broken too. New case: PushLocal +
LocalAddInt(±1).
* **BlockExpr (`{|p| body }`) wasn't compiled.** Eval(b, n)
inside a pcode body returned NIL. Added: build the body in a
sub-codebuffer with the block's params occupying its locals,
emit PcOpRetValue at the end, then PushBlock with the
serialized bytes. Format extended with a uint16 nParams field
so the runtime's PcOpPushBlock dispatch can set
PcodeFunc.Params correctly — without it, ExecPcode's
Frame(0, 0) pulled none of Eval's args and the block saw
every parameter as NIL.
* **All g.locals accesses were case-sensitive.** PRG is case-
insensitive, but the pcode generator stored block params via
strings.ToUpper while every other lookup site (function decl,
mid-decl, ForStmt, IdentExpr read, AssignExpr write,
PostfixExpr) used the raw .Name. So `{|x| x*x }` stored "X"
but read "x" and missed. Normalized: all insertions and all
lookups now go through strings.ToUpper.
* **SeqExpr in pcode** — added the matching emit for comma-
separated expression lists in code blocks (`{|| a, b, c }`).
Same shape as the gengo SeqExpr case from Wave 1.
Test fixture: tests/frb/test_frb_pcode_sweep.prg covers 14 shapes
(string ops, arithmetic, comparison chains, array indexing, DO
WHILE with postfix, nested IF, IIf, hash literal + indexing,
block + Eval, character iteration). All 14 pass. Wired into the
FRB runner — suite now stands at 7/7.
Other gates green:
go test ./... : PASS
FiveSql2 SQL:1999 : 43/43
Harbour compat : 56/56
std.ch suite : 15/15
FRB suite : 7/7
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
232 lines
6.1 KiB
Plaintext
232 lines
6.1 KiB
Plaintext
/* Pcode-mode wider-coverage sweep. Every function below is compiled
|
|
to pcode via FrbCompile (in-process) and called via FrbDo, so any
|
|
pcode interpreter regression that escaped the focused fixtures
|
|
surfaces here. Failures are accumulated and printed at the end. */
|
|
|
|
PROCEDURE Main()
|
|
LOCAL pMod, src, pass := 0, fail := 0, label, expect, got
|
|
|
|
src := ;
|
|
'FUNCTION StringOps(s)' + Chr(10) + ;
|
|
' RETURN Upper(s) + "_" + Lower(s)' + Chr(10) + ;
|
|
'FUNCTION ArithMix(a, b)' + Chr(10) + ;
|
|
' RETURN (a + b) * 2 - a / 2' + Chr(10) + ;
|
|
'FUNCTION CmpChain(n)' + Chr(10) + ;
|
|
' IF n > 0 .AND. n < 100' + Chr(10) + ;
|
|
' RETURN "in"' + Chr(10) + ;
|
|
' ELSEIF n == 0' + Chr(10) + ;
|
|
' RETURN "zero"' + Chr(10) + ;
|
|
' ENDIF' + Chr(10) + ;
|
|
' RETURN "out"' + Chr(10) + ;
|
|
'FUNCTION ArrSum(arr)' + Chr(10) + ;
|
|
' LOCAL n := 0, i' + Chr(10) + ;
|
|
' FOR i := 1 TO Len(arr)' + Chr(10) + ;
|
|
' n += arr[i]' + Chr(10) + ;
|
|
' NEXT' + Chr(10) + ;
|
|
' RETURN n' + Chr(10) + ;
|
|
'FUNCTION DoWhileTest(n)' + Chr(10) + ;
|
|
' LOCAL r := 0' + Chr(10) + ;
|
|
' DO WHILE n > 0' + Chr(10) + ;
|
|
' r += n' + Chr(10) + ;
|
|
' n--' + Chr(10) + ;
|
|
' ENDDO' + Chr(10) + ;
|
|
' RETURN r' + Chr(10) + ;
|
|
'FUNCTION NestedIf(a, b)' + Chr(10) + ;
|
|
' IF a > 0' + Chr(10) + ;
|
|
' IF b > 0' + Chr(10) + ;
|
|
' RETURN "++"' + Chr(10) + ;
|
|
' ENDIF' + Chr(10) + ;
|
|
' RETURN "+0"' + Chr(10) + ;
|
|
' ENDIF' + Chr(10) + ;
|
|
' RETURN "00"' + Chr(10) + ;
|
|
'FUNCTION IIfTest(n)' + Chr(10) + ;
|
|
' RETURN iif(n > 5, "big", "small")' + Chr(10) + ;
|
|
'FUNCTION HashLit()' + Chr(10) + ;
|
|
' LOCAL h := { "a" => 1, "b" => 2 }' + Chr(10) + ;
|
|
' RETURN h["a"] + h["b"]' + Chr(10) + ;
|
|
'FUNCTION BlockEval(n)' + Chr(10) + ;
|
|
' LOCAL b := {|x| x * x }' + Chr(10) + ;
|
|
' RETURN Eval(b, n)' + Chr(10) + ;
|
|
'FUNCTION CountChars(s, c)' + Chr(10) + ;
|
|
' LOCAL i, n := 0' + Chr(10) + ;
|
|
' FOR i := 1 TO Len(s)' + Chr(10) + ;
|
|
' IF SubStr(s, i, 1) == c' + Chr(10) + ;
|
|
' n++' + Chr(10) + ;
|
|
' ENDIF' + Chr(10) + ;
|
|
' NEXT' + Chr(10) + ;
|
|
' RETURN n' + Chr(10)
|
|
|
|
pMod := FrbCompile(src)
|
|
IF pMod == NIL
|
|
? "FAIL: compile returned NIL"
|
|
RETURN
|
|
ENDIF
|
|
|
|
/* Each row: label, expected, FrbDo call (deferred via codeblock) */
|
|
? "--- pcode sweep ---"
|
|
|
|
label := "1. StringOps('Hi')"
|
|
expect := "HI_hi"
|
|
got := FrbDo(pMod, "STRINGOPS", "Hi")
|
|
IF got == expect
|
|
? "PASS", label, "=", got
|
|
pass++
|
|
ELSE
|
|
? "FAIL", label, "expect", expect, "got", got
|
|
fail++
|
|
ENDIF
|
|
|
|
label := "2. ArithMix(10, 4)"
|
|
expect := 23 /* (10+4)*2 - 10/2 = 28 - 5 = 23 */
|
|
got := FrbDo(pMod, "ARITHMIX", 10, 4)
|
|
IF got == expect
|
|
? "PASS", label, "=", got
|
|
pass++
|
|
ELSE
|
|
? "FAIL", label, "expect", expect, "got", got
|
|
fail++
|
|
ENDIF
|
|
|
|
label := "3a. CmpChain(50)"
|
|
expect := "in"
|
|
got := FrbDo(pMod, "CMPCHAIN", 50)
|
|
IF got == expect
|
|
? "PASS", label, "=", got
|
|
pass++
|
|
ELSE
|
|
? "FAIL", label, "expect", expect, "got", got
|
|
fail++
|
|
ENDIF
|
|
|
|
label := "3b. CmpChain(0)"
|
|
expect := "zero"
|
|
got := FrbDo(pMod, "CMPCHAIN", 0)
|
|
IF got == expect
|
|
? "PASS", label, "=", got
|
|
pass++
|
|
ELSE
|
|
? "FAIL", label, "expect", expect, "got", got
|
|
fail++
|
|
ENDIF
|
|
|
|
label := "3c. CmpChain(200)"
|
|
expect := "out"
|
|
got := FrbDo(pMod, "CMPCHAIN", 200)
|
|
IF got == expect
|
|
? "PASS", label, "=", got
|
|
pass++
|
|
ELSE
|
|
? "FAIL", label, "expect", expect, "got", got
|
|
fail++
|
|
ENDIF
|
|
|
|
label := "4. ArrSum({1,2,3,4,5})"
|
|
expect := 15
|
|
got := FrbDo(pMod, "ARRSUM", { 1, 2, 3, 4, 5 })
|
|
IF got == expect
|
|
? "PASS", label, "=", got
|
|
pass++
|
|
ELSE
|
|
? "FAIL", label, "expect", expect, "got", got
|
|
fail++
|
|
ENDIF
|
|
|
|
label := "5. DoWhileTest(5)"
|
|
expect := 15 /* 5+4+3+2+1 */
|
|
got := FrbDo(pMod, "DOWHILETEST", 5)
|
|
IF got == expect
|
|
? "PASS", label, "=", got
|
|
pass++
|
|
ELSE
|
|
? "FAIL", label, "expect", expect, "got", got
|
|
fail++
|
|
ENDIF
|
|
|
|
label := "6a. NestedIf(1,1)"
|
|
expect := "++"
|
|
got := FrbDo(pMod, "NESTEDIF", 1, 1)
|
|
IF got == expect
|
|
? "PASS", label, "=", got
|
|
pass++
|
|
ELSE
|
|
? "FAIL", label, "expect", expect, "got", got
|
|
fail++
|
|
ENDIF
|
|
|
|
label := "6b. NestedIf(1,0)"
|
|
expect := "+0"
|
|
got := FrbDo(pMod, "NESTEDIF", 1, 0)
|
|
IF got == expect
|
|
? "PASS", label, "=", got
|
|
pass++
|
|
ELSE
|
|
? "FAIL", label, "expect", expect, "got", got
|
|
fail++
|
|
ENDIF
|
|
|
|
label := "6c. NestedIf(-1,0)"
|
|
expect := "00"
|
|
got := FrbDo(pMod, "NESTEDIF", -1, 0)
|
|
IF got == expect
|
|
? "PASS", label, "=", got
|
|
pass++
|
|
ELSE
|
|
? "FAIL", label, "expect", expect, "got", got
|
|
fail++
|
|
ENDIF
|
|
|
|
label := "7. IIfTest(10)"
|
|
expect := "big"
|
|
got := FrbDo(pMod, "IIFTEST", 10)
|
|
IF got == expect
|
|
? "PASS", label, "=", got
|
|
pass++
|
|
ELSE
|
|
? "FAIL", label, "expect", expect, "got", got
|
|
fail++
|
|
ENDIF
|
|
|
|
label := "8. HashLit()"
|
|
expect := 3
|
|
got := FrbDo(pMod, "HASHLIT")
|
|
IF got == expect
|
|
? "PASS", label, "=", got
|
|
pass++
|
|
ELSE
|
|
? "FAIL", label, "expect", expect, "got", got
|
|
fail++
|
|
ENDIF
|
|
|
|
label := "9. BlockEval(7)"
|
|
expect := 49
|
|
got := FrbDo(pMod, "BLOCKEVAL", 7)
|
|
IF got == expect
|
|
? "PASS", label, "=", got
|
|
pass++
|
|
ELSE
|
|
? "FAIL", label, "expect", expect, "got", got
|
|
fail++
|
|
ENDIF
|
|
|
|
label := "10. CountChars('mississippi', 's')"
|
|
expect := 4
|
|
got := FrbDo(pMod, "COUNTCHARS", "mississippi", "s")
|
|
IF got == expect
|
|
? "PASS", label, "=", got
|
|
pass++
|
|
ELSE
|
|
? "FAIL", label, "expect", expect, "got", got
|
|
fail++
|
|
ENDIF
|
|
|
|
FrbUnload(pMod)
|
|
|
|
? ""
|
|
? "================================================================"
|
|
? " pcode sweep:", pass, "passed,", fail, "failed"
|
|
? "================================================================"
|
|
IF fail > 0
|
|
? "FAIL summary"
|
|
ENDIF
|
|
RETURN
|