fix(genpc,parser,pcinterp): pcode wider regression sweep (Tier 1 #3)
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>
This commit is contained in:
@@ -123,16 +123,17 @@ func (g *generator) emitFunc(fn *ast.FuncDecl) {
|
||||
g.code = nil
|
||||
g.locals = make(map[string]int)
|
||||
|
||||
// Build local map
|
||||
// Build local map. PRG is case-insensitive so all keys are
|
||||
// uppercased here; every lookup site below must mirror this.
|
||||
idx := 1
|
||||
for _, p := range fn.Params {
|
||||
g.locals[p.Name] = idx
|
||||
g.locals[strings.ToUpper(p.Name)] = idx
|
||||
idx++
|
||||
}
|
||||
for _, d := range fn.Decls {
|
||||
if vd, ok := d.(*ast.VarDecl); ok && vd.Scope == ast.ScopeLocal {
|
||||
for _, v := range vd.Vars {
|
||||
g.locals[v.Name] = idx
|
||||
g.locals[strings.ToUpper(v.Name)] = idx
|
||||
idx++
|
||||
}
|
||||
}
|
||||
@@ -140,7 +141,7 @@ func (g *generator) emitFunc(fn *ast.FuncDecl) {
|
||||
for _, s := range fn.Body {
|
||||
if vd, ok := s.(*ast.VarDecl); ok && vd.Scope == ast.ScopeLocal {
|
||||
for _, v := range vd.Vars {
|
||||
g.locals[v.Name] = idx
|
||||
g.locals[strings.ToUpper(v.Name)] = idx
|
||||
idx++
|
||||
}
|
||||
}
|
||||
@@ -228,7 +229,7 @@ func (g *generator) emitStmt(stmt ast.Stmt) {
|
||||
for _, v := range s.Vars {
|
||||
if v.Init != nil {
|
||||
g.emitExpr(v.Init)
|
||||
if idx, ok := g.locals[v.Name]; ok {
|
||||
if idx, ok := g.locals[strings.ToUpper(v.Name)]; ok {
|
||||
g.emit(hbrt.PcOpPopLocal)
|
||||
g.emitU16(uint16(idx))
|
||||
} else {
|
||||
@@ -287,7 +288,7 @@ func (g *generator) emitDoWhile(s *ast.DoWhileStmt) {
|
||||
}
|
||||
|
||||
func (g *generator) emitFor(s *ast.ForStmt) {
|
||||
idx, ok := g.locals[s.Var]
|
||||
idx, ok := g.locals[strings.ToUpper(s.Var)]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
@@ -400,7 +401,12 @@ func (g *generator) emitExpr(expr ast.Expr) {
|
||||
g.emit(hbrt.PcOpPushSelf)
|
||||
return
|
||||
}
|
||||
if idx, ok := g.locals[e.Name]; ok {
|
||||
// Locals are keyed case-insensitively. Look up via uppercase
|
||||
// (also covers blocks: their params are stored ToUpper). The
|
||||
// previous raw `e.Name` lookup missed any caller that wrote
|
||||
// the identifier in different case from the declaration —
|
||||
// `{|x| x * x }` invoked via Eval(b, 7) silently saw x=NIL.
|
||||
if idx, ok := g.locals[upper]; ok {
|
||||
g.emit(hbrt.PcOpPushLocal)
|
||||
g.emitU16(uint16(idx))
|
||||
} else {
|
||||
@@ -463,6 +469,117 @@ func (g *generator) emitExpr(expr ast.Expr) {
|
||||
g.emit(hbrt.PcOpArrayGen)
|
||||
g.emitU16(uint16(len(e.Items)))
|
||||
|
||||
case *ast.BlockExpr:
|
||||
// `{|p| body }` — compile body to its own pcode buffer with
|
||||
// the block's params occupying locals 1..len(Params), then
|
||||
// emit PcOpPushBlock + length + body bytes + nDetached (zero
|
||||
// — closure capture isn't wired up in pcode mode yet, so
|
||||
// blocks see their declared params and any module-local
|
||||
// symbol but no caller locals).
|
||||
// Without this case, BlockExpr fell through to the generic
|
||||
// PushNil and Eval(NIL, ...) returned NIL — silently
|
||||
// breaking every higher-order function (Eval / AEval /
|
||||
// SqlScan predicate compile / etc.) inside a pcode body.
|
||||
savedCode := g.code
|
||||
savedLocals := g.locals
|
||||
g.code = nil
|
||||
g.locals = make(map[string]int, len(e.Params))
|
||||
for i, p := range e.Params {
|
||||
g.locals[strings.ToUpper(p)] = i + 1
|
||||
}
|
||||
g.emitExpr(e.Body)
|
||||
g.emit(hbrt.PcOpRetValue)
|
||||
body := g.code
|
||||
g.code = savedCode
|
||||
g.locals = savedLocals
|
||||
|
||||
g.emit(hbrt.PcOpPushBlock)
|
||||
g.emitI32(int32(len(body)))
|
||||
g.code = append(g.code, body...)
|
||||
g.emitU16(uint16(len(e.Params))) // nParams
|
||||
g.emitU16(0) // nDetached — no closure capture yet
|
||||
|
||||
case *ast.SeqExpr:
|
||||
// Comma-separated expression list inside a code block:
|
||||
// `{|| e1, e2, e3 }`. Evaluate each in order, pop intermediate
|
||||
// results so only the last value remains. Same semantics as
|
||||
// gengo's SeqExpr handler.
|
||||
for i, item := range e.Items {
|
||||
g.emitExpr(item)
|
||||
if i < len(e.Items)-1 {
|
||||
g.emit(hbrt.PcOpPop)
|
||||
}
|
||||
}
|
||||
|
||||
case *ast.HashLitExpr:
|
||||
// `{ "k" => 1, ... }` — push each key+value pair, HashGen
|
||||
// builds the hash from the top-N stack pairs. Without this
|
||||
// case, the hash literal silently fell through to PushNil
|
||||
// and any subsequent `h[key]` panicked at ArrayPush with
|
||||
// "argument error (op: [])".
|
||||
for i, k := range e.Keys {
|
||||
g.emitExpr(k)
|
||||
g.emitExpr(e.Values[i])
|
||||
}
|
||||
g.emit(hbrt.PcOpHashGen)
|
||||
g.emitU16(uint16(len(e.Keys)))
|
||||
|
||||
case *ast.IndexExpr:
|
||||
// arr[idx] — push array, push index, ArrayPush reads element.
|
||||
// (ArrayPush is the "get" op; ArrayPop is the "set" op — names
|
||||
// kept to match the Harbour stack-machine convention.)
|
||||
// Without this case, indexed reads in pcode silently emitted
|
||||
// PushNil via the default fallback, so `arr[i]` always
|
||||
// returned NIL and `n + arr[i]` panicked at the +.
|
||||
g.emitExpr(e.X)
|
||||
g.emitExpr(e.Index)
|
||||
g.emit(hbrt.PcOpArrayPush)
|
||||
|
||||
case *ast.PostfixExpr:
|
||||
// `x++` / `x--` — read current value (becomes the expression
|
||||
// result), apply Inc/Dec to the LOCAL slot, leave the
|
||||
// pre-modification value on the stack so it round-trips
|
||||
// correctly when used as an expression. As a statement the
|
||||
// caller does Pop afterward.
|
||||
// Without this case, postfix on pcode-mode silently emitted
|
||||
// PushNil → `n++` was a no-op, breaking DO WHILE / FOR
|
||||
// patterns that mutate the loop counter.
|
||||
if id, isIdent := e.X.(*ast.IdentExpr); isIdent {
|
||||
if idx, found := g.locals[strings.ToUpper(id.Name)]; found {
|
||||
g.emit(hbrt.PcOpPushLocal)
|
||||
g.emitU16(uint16(idx))
|
||||
delta := int64(1)
|
||||
if e.Op == token.DEC {
|
||||
delta = -1
|
||||
}
|
||||
g.emit(hbrt.PcOpLocalAddInt)
|
||||
g.emitU16(uint16(idx))
|
||||
g.emitI32(int32(delta))
|
||||
return
|
||||
}
|
||||
}
|
||||
// Anything else (memvar, alias->field, arr[i]) — emit the
|
||||
// expression as a no-op for now and document the gap.
|
||||
g.emitExpr(e.X)
|
||||
|
||||
case *ast.AliasExpr:
|
||||
// Pcode mode: only the M-> / MEMVAR-> namespace (memvar
|
||||
// access) is wired up. The general workarea-alias form
|
||||
// (`FOO->bar`, `(expr)->(body)`) needs new opcodes for
|
||||
// alias dispatch + workarea context save/restore — until
|
||||
// then it falls through to the generic NIL fallback so
|
||||
// callers see "missing data" rather than crash.
|
||||
if aliasIdent, ok1 := e.Alias.(*ast.IdentExpr); ok1 {
|
||||
if fieldIdent, ok2 := e.Field.(*ast.IdentExpr); ok2 {
|
||||
upper := strings.ToUpper(aliasIdent.Name)
|
||||
if upper == "M" || upper == "MEMVAR" {
|
||||
g.emitString(hbrt.PcOpPushMemvar, fieldIdent.Name)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
g.emit(hbrt.PcOpPushNil)
|
||||
|
||||
default:
|
||||
g.emit(hbrt.PcOpPushNil) // fallback
|
||||
}
|
||||
@@ -584,7 +701,7 @@ func (g *generator) emitAssign(a *ast.AssignExpr) {
|
||||
op, ok := compoundBinOp(a.Op)
|
||||
if ok {
|
||||
if ident, isIdent := a.Left.(*ast.IdentExpr); isIdent {
|
||||
if idx, found := g.locals[ident.Name]; found {
|
||||
if idx, found := g.locals[strings.ToUpper(ident.Name)]; found {
|
||||
g.emit(hbrt.PcOpPushLocal)
|
||||
g.emitU16(uint16(idx))
|
||||
g.emitExpr(a.Right)
|
||||
@@ -597,7 +714,7 @@ func (g *generator) emitAssign(a *ast.AssignExpr) {
|
||||
}
|
||||
}
|
||||
if ident, ok := a.Left.(*ast.IdentExpr); ok {
|
||||
if idx, found := g.locals[ident.Name]; found {
|
||||
if idx, found := g.locals[strings.ToUpper(ident.Name)]; found {
|
||||
g.emitExpr(a.Right)
|
||||
g.emit(hbrt.PcOpPopLocal)
|
||||
g.emitU16(uint16(idx))
|
||||
|
||||
@@ -1154,17 +1154,36 @@ func (p *Parser) parseIdentStmt() ast.Stmt {
|
||||
// CLOSE / REINDEX / PACK / ZAP / UNLOCK / KEYBOARD / RUN are now
|
||||
// rewritten by compiler/pp/std.ch into function calls before the
|
||||
// parser sees them.
|
||||
//
|
||||
// Guard against shadowing variables — the keywords here (LABEL,
|
||||
// REPORT, INPUT, NOTE, ...) are also extremely common LOCAL/PRIVATE
|
||||
// names. If the very next token is an assignment / index / paren /
|
||||
// alias-arrow operator, the user is doing a variable assignment or
|
||||
// function call, not invoking the xBase command — fall through to
|
||||
// expression parsing. This was a real silent bug: `LOCAL label` +
|
||||
// `label := "x"` had the assignment swallowed by the LABEL case
|
||||
// because the no-op consumed-to-EOL path doesn't care about :=.
|
||||
switch upper {
|
||||
case "LABEL", "REPORT", "ACCEPT", "INPUT",
|
||||
"RELEASE", "SAVE", "RESTORE",
|
||||
"DIR", "STORE", "NOTE", "TEXT", "ENDTEXT",
|
||||
"WITH", "CLEAR":
|
||||
switch p.peekAt(1) {
|
||||
case token.ASSIGN, token.PLUSEQ, token.MINUSEQ, token.STAREQ,
|
||||
token.SLASHEQ, token.PERCENTEQ, token.POWEREQ,
|
||||
token.LBRACKET, token.LPAREN, token.ARROW,
|
||||
token.INC, token.DEC,
|
||||
token.DOT:
|
||||
// Looks like a variable / call / member-access — not
|
||||
// the xBase command. Fall through.
|
||||
default:
|
||||
p.advance()
|
||||
for p.current.Kind != token.NEWLINE && p.current.Kind != token.EOF {
|
||||
p.advance()
|
||||
}
|
||||
p.expectEndOfStmt()
|
||||
return &ast.ExprStmt{X: &ast.LiteralExpr{Kind: token.NIL_LIT, Value: "NIL"}}
|
||||
}
|
||||
|
||||
case "FIVE_GODUMP__":
|
||||
// GoDump is a Decl, wrap as ExprStmt for statement context
|
||||
@@ -1229,6 +1248,17 @@ func (p *Parser) parseExprStmt() ast.Stmt {
|
||||
"RELEASE", "SAVE", "RESTORE",
|
||||
"DIR", "STORE", "NOTE", "TEXT", "ENDTEXT",
|
||||
"WITH", "CLEAR":
|
||||
// Same shadowing-guard as parseIdentStmt — see comment
|
||||
// there. Without this, `LOCAL label ; label := "x"` had
|
||||
// the assignment swallowed.
|
||||
switch p.peekAt(1) {
|
||||
case token.ASSIGN, token.PLUSEQ, token.MINUSEQ, token.STAREQ,
|
||||
token.SLASHEQ, token.PERCENTEQ, token.POWEREQ,
|
||||
token.LBRACKET, token.LPAREN, token.ARROW,
|
||||
token.INC, token.DEC,
|
||||
token.DOT:
|
||||
// Variable / call / member-access — fall through.
|
||||
default:
|
||||
// Consume entire line — these are complex multi-word commands
|
||||
p.advance()
|
||||
for p.current.Kind != token.NEWLINE && p.current.Kind != token.EOF {
|
||||
@@ -1238,6 +1268,7 @@ func (p *Parser) parseExprStmt() ast.Stmt {
|
||||
return &ast.ExprStmt{X: &ast.LiteralExpr{Kind: token.NIL_LIT, Value: "NIL"}}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
expr := p.parseExpr()
|
||||
|
||||
|
||||
@@ -277,6 +277,15 @@ func execPcodeBody(t *Thread, fn *PcodeFunc, mod *PcodeModule) {
|
||||
case PcOpArrayPop:
|
||||
t.ArrayPop()
|
||||
|
||||
// --- Hash --- (PcOpHashGen has been declared since the
|
||||
// initial pcode design but its dispatch case was missing,
|
||||
// so any pcode body that built a hash literal panicked
|
||||
// with "unknown pcode opcode: 0x51".)
|
||||
case PcOpHashGen:
|
||||
count := int(binary.LittleEndian.Uint16(code[pc:]))
|
||||
pc += 2
|
||||
t.HashGen(count)
|
||||
|
||||
// --- Block ---
|
||||
case PcOpPushBlock:
|
||||
codeLen := int(binary.LittleEndian.Uint32(code[pc:]))
|
||||
@@ -284,11 +293,17 @@ func execPcodeBody(t *Thread, fn *PcodeFunc, mod *PcodeModule) {
|
||||
blockCode := make([]byte, codeLen)
|
||||
copy(blockCode, code[pc:pc+codeLen])
|
||||
pc += codeLen
|
||||
nParams := int(binary.LittleEndian.Uint16(code[pc:]))
|
||||
pc += 2
|
||||
nDetached := int(binary.LittleEndian.Uint16(code[pc:]))
|
||||
pc += 2
|
||||
|
||||
// Create a Go function that interprets the block's pcode
|
||||
blockFn := &PcodeFunc{Code: blockCode}
|
||||
// Create a Go function that interprets the block's pcode.
|
||||
// Params count must be threaded through so ExecPcode's
|
||||
// Frame() pulls Eval()'s args off the stack into the
|
||||
// block's locals — without it, `{|x| x*x }` saw x=NIL
|
||||
// and `x * x` panicked on the multiplication.
|
||||
blockFn := &PcodeFunc{Code: blockCode, Params: nParams}
|
||||
modCopy := mod
|
||||
t.PushBlock(func(t2 *Thread) {
|
||||
ExecPcode(t2, blockFn, modCopy)
|
||||
|
||||
@@ -37,6 +37,7 @@ TESTS=(
|
||||
test_frb_loop
|
||||
test_frb_step
|
||||
test_frb_goroutine
|
||||
test_frb_pcode_sweep
|
||||
)
|
||||
|
||||
pass=0
|
||||
|
||||
231
tests/frb/test_frb_pcode_sweep.prg
Normal file
231
tests/frb/test_frb_pcode_sweep.prg
Normal file
@@ -0,0 +1,231 @@
|
||||
/* 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
|
||||
Reference in New Issue
Block a user