Six audit-driven blockers landed together because they're tangled:
* MENU TO removed from std.ch — the rule expanded to a call to a
nonexistent __MenuTo() RTL symbol, so any user code with `MENU
TO choice` compiled clean and panicked at runtime. Behavior
pre-this-round was a parser silent no-op, which is at least
consistent. Restore that until @ PROMPT (the companion command)
actually lands.
* COUNT now requires `TO <var>`. The earlier `[TO <v>]` optional
bracket was a Harbour-pattern transcription error: the result
template references `<v>` unconditionally, so a bare `COUNT`
expanded to ungrammatical ` := 0 ; dbEval(...)` and the
PRG parser rejected it. Match Harbour's std.ch which makes TO
mandatory.
* UPDATE FROM ... REPLACE now requires `FROM`/`ON`/`REPLACE` all
three. Same root cause as COUNT: the result template uses
`<key>`, `<f1>`, `<x1>` unconditionally; missing any of them
produced broken syntax. Tightened to fail loudly rather than
silently mis-expand.
* CLOSE <unknown_alias> no longer closes the *current* workarea.
SelectByAlias was a silent no-op when the alias was missing,
leaving WASaveAndSelectAlias to evaluate the inner DbCloseArea()
against the originally-selected WA — a real data-loss footgun.
SelectByAlias now returns bool; WASaveAndSelectAlias switches to
the no-area sentinel (0) on miss so the inner expression's
Current() returns nil and short-circuits.
* SUM <x1>, <xN> TO <v1>, <vN> — multi-pair form supported.
Required two pieces:
1. matchSegment's regular-marker stop-boundary now combines
outerTail literals AND the segment's repeat boundary so
`[, <xN>]` doesn't let `<xN>` swallow past the next ','.
2. **Five parser miscompiled comma-separated expressions in
code blocks.** `{|| e1, e2, e3 }` kept only the last expr
and threw away earlier ones at *AST level*, so all their
side effects vanished. New SeqExpr AST node + emitter
(emit each, pop intermediate results) + folding/walk
updates fix the underlying bug, which also unbreaks any
other block that relied on comma sequencing.
* pp.go's `;` continuation joiner now strips exactly one trailing
`;` per iteration, preserving Harbour's `;;` convention (literal
`;` followed by a continuation marker). Without this the SUM
rule's chained `<v1> :=[ <vN> :=] 0 ; ; dbEval(...)` collapsed
to a missing statement separator.
* parseExprStmt's xBase fallback switch is back in sync with
parseIdentStmt — COPY/SORT/COUNT/SUM/AVERAGE/TOTAL/UPDATE/JOIN/
DISPLAY/LIST removed (std.ch handles all of them now). Leaving
them in the fallback masked typos as silent no-ops.
Gates green:
go test ./... : PASS
FiveSql2 SQL:1999 : 43/43
Harbour compat : 56/56
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
168 lines
7.9 KiB
Plaintext
168 lines
7.9 KiB
Plaintext
/*
|
|
* std.ch — Five standard preprocessor rules
|
|
*
|
|
* Equivalent to harbour-core/include/std.ch. Translates xBase legacy
|
|
* commands into function calls so the parser does not have to know
|
|
* about them. Auto-loaded by compiler/pp at startup.
|
|
*
|
|
* Phase A: only rules whose backend RTL function already exists in
|
|
* Five. Rules whose backend is not yet implemented (COPY, SORT,
|
|
* COUNT, SUM, AVERAGE, TOTAL, JOIN, LIST, DISPLAY, LABEL, REPORT,
|
|
* DIR) are deliberately NOT included here — the parser still handles
|
|
* them as silent no-ops until their RTL backend lands.
|
|
*
|
|
* Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com)
|
|
* All rights reserved.
|
|
*/
|
|
|
|
/* --- file system --- */
|
|
#command ERASE <(f)> => FErase(<(f)>)
|
|
#command DELETE FILE <(f)> => FErase(<(f)>)
|
|
#command RENAME <(s)> TO <(d)> => FRename(<(s)>, <(d)>)
|
|
|
|
/* --- workarea lifecycle ---
|
|
Order matters: literal-keyword forms first, then bare CLOSE,
|
|
then the alias-form last so it doesn't shadow the others. */
|
|
#command CLOSE ALL => DbCloseAll()
|
|
#command CLOSE DATABASES => DbCloseAll()
|
|
#command CLOSE => DbCloseArea()
|
|
#command CLOSE <a> => <a>->( DbCloseArea() )
|
|
|
|
/* --- record state --- */
|
|
#command COMMIT => DbCommit()
|
|
#command UNLOCK ALL => DbUnlock()
|
|
#command UNLOCK => DbRUnlock()
|
|
|
|
/* --- record search --- */
|
|
#command LOCATE [FOR <for>] [WHILE <while>] ;
|
|
[NEXT <next>] [RECORD <rec>] [<rest:REST>] [ALL] => ;
|
|
__dbLocate(<{for}>, <{while}>, <next>, <rec>, <.rest.>)
|
|
|
|
#command CONTINUE => __dbContinue()
|
|
|
|
/* --- analytical (no extra RTL — just dbEval) ---
|
|
These mirror Harbour's std.ch but use single-value forms. Multi-
|
|
expression SUM/AVERAGE (`SUM x, y TO sx, sy`) use optional-repeat
|
|
syntax in Harbour and can be added here once a real test exercises
|
|
the more elaborate form. */
|
|
/* COUNT/SUM/AVERAGE require TO <var> — without it the rewrite
|
|
would produce naked assignment with no LHS. Match Harbour
|
|
std.ch which also makes TO non-optional. */
|
|
#command COUNT TO <v> [FOR <for>] [WHILE <while>] ;
|
|
[NEXT <next>] [RECORD <rec>] [<rest:REST>] [ALL] => ;
|
|
<v> := 0 ; dbEval( {|| <v> := <v> + 1 }, ;
|
|
<{for}>, <{while}>, <next>, <rec>, <.rest.> )
|
|
|
|
/* SUM and AVERAGE accept multiple paired expressions/destinations:
|
|
`SUM x, y, z TO sx, sy, sz`. The optional `[, <xN>]` and
|
|
`[, <vN>]` repeats are matched pairwise; the result template's
|
|
chained `<v1> :=[ <vN> :=] 0` and comma-list inside the dbEval
|
|
block expand once per extra pair. Single-pair usage is unchanged. */
|
|
#command SUM <x1> [, <xN>] TO <v1> [, <vN>] ;
|
|
[FOR <for>] [WHILE <while>] [NEXT <next>] ;
|
|
[RECORD <rec>] [<rest:REST>] [ALL] => ;
|
|
<v1> :=[ <vN> :=] 0 ; ;
|
|
dbEval( {|| <v1> := <v1> + <x1>[, <vN> := <vN> + <xN>] }, ;
|
|
<{for}>, <{while}>, <next>, <rec>, <.rest.> )
|
|
|
|
#command AVERAGE <x> TO <v> ;
|
|
[FOR <for>] [WHILE <while>] [NEXT <next>] ;
|
|
[RECORD <rec>] [<rest:REST>] [ALL] => ;
|
|
<v> := __dbAverage( <{x}>, ;
|
|
<{for}>, <{while}>, <next>, <rec>, <.rest.> )
|
|
|
|
/* --- bulk record export ---
|
|
COPY TO copies visible records of the current workarea into a fresh
|
|
DBF. FIELDS/FOR/WHILE/NEXT/RECORD/REST work as in Harbour. SDF and
|
|
DELIMITED variants stay as silent no-ops in the parser until their
|
|
backends land. */
|
|
#command COPY [TO <(f)>] [FIELDS <fields,...>] ;
|
|
[FOR <for>] [WHILE <while>] [NEXT <next>] ;
|
|
[RECORD <rec>] [<rest:REST>] [ALL] => ;
|
|
__dbCopy( <(f)>, { <(fields)> }, ;
|
|
<{for}>, <{while}>, <next>, <rec>, <.rest.> )
|
|
|
|
/* SORT TO copies the visible records into a fresh DBF in key order.
|
|
Each key in `<fields>` may carry `/D` for descending; default is
|
|
ascending. */
|
|
#command SORT [TO <(f)>] [ON <fields,...>] ;
|
|
[FOR <for>] [WHILE <while>] [NEXT <next>] ;
|
|
[RECORD <rec>] [<rest:REST>] [ALL] => ;
|
|
__dbSort( <(f)>, { <(fields)> }, ;
|
|
<{for}>, <{while}>, <next>, <rec>, <.rest.> )
|
|
|
|
/* --- console output ---
|
|
LIST emits every record matching the filter; DISPLAY without ALL
|
|
shows just the current record. Both share __dbList — lAll
|
|
distinguishes them. TO PRINTER / TO FILE accepted but unused;
|
|
stdout is the only sink for now. */
|
|
#command LIST [<v,...>] [<off:OFF>] ;
|
|
[FOR <for>] [WHILE <while>] [NEXT <next>] ;
|
|
[RECORD <rec>] [<rest:REST>] [ALL] => ;
|
|
__dbList( <.off.>, { <{v}> }, .T., ;
|
|
<{for}>, <{while}>, <next>, <rec>, <.rest.> )
|
|
|
|
#command DISPLAY [<v,...>] [<off:OFF>] ;
|
|
[FOR <for>] [WHILE <while>] [NEXT <next>] ;
|
|
[RECORD <rec>] [<rest:REST>] [<all:ALL>] => ;
|
|
__dbList( <.off.>, { <{v}> }, <.all.>, ;
|
|
<{for}>, <{while}>, <next>, <rec>, <.rest.> )
|
|
|
|
/* TOTAL TO writes one record per consecutive run of equal key values
|
|
from the source. Numeric fields named in FIELDS are summed; every
|
|
other (non-memo) field takes the first record's value. The source
|
|
must already be sorted/indexed on the key for the grouping to
|
|
produce one row per distinct value.
|
|
|
|
Note for callers: Five doesn't auto-resolve a bare identifier
|
|
inside a code block to the current workarea's field. Write the
|
|
key alias-qualified — `ON src->dept` rather than `ON dept`. */
|
|
#command TOTAL [TO <(f)>] [ON <key>] [FIELDS <fields,...>] ;
|
|
[FOR <for>] [WHILE <while>] [NEXT <next>] ;
|
|
[RECORD <rec>] [<rest:REST>] [ALL] => ;
|
|
__dbTotal( <(f)>, <{key}>, { <(fields)> }, ;
|
|
<{for}>, <{while}>, <next>, <rec>, <.rest.> )
|
|
|
|
/* JOIN merges the current ("master") workarea with the named
|
|
detail alias into a fresh DBF, emitting one output row per
|
|
master/detail pair where FOR evaluates true. */
|
|
#command JOIN [WITH <(alias)>] [TO <(f)>] [FIELDS <fields,...>] ;
|
|
[FOR <for>] => ;
|
|
__dbJoin( <(alias)>, <(f)>, { <(fields)> }, <{for}> )
|
|
|
|
/* UPDATE FROM walks the named detail alias and applies the
|
|
REPLACE ... WITH ... clauses to the matching master record.
|
|
Both areas should be sorted on the key for the default forward-
|
|
walk; pass RANDOM to scan master from top for each detail key.
|
|
|
|
Note 1: ON <key> is wrapped as `_FIELD-><key>` rather than the bare
|
|
`<{key}>` Harbour uses, because the same block must evaluate
|
|
against both master and detail. Bare identifiers don't auto-bind
|
|
to fields under Five — `_FIELD->` makes the dispatch explicit.
|
|
|
|
Note 2: FROM/ON/REPLACE are all required (Harbour technically allows
|
|
them in any order but every real call site provides all three). The
|
|
former optional brackets allowed compile-clean garbage like a bare
|
|
`UPDATE` to expand to a broken-syntax call. Keep them mandatory. */
|
|
#command UPDATE FROM <(alias)> ON <key> [<rand:RANDOM>] ;
|
|
REPLACE <f1> WITH <x1> [, <fN> WITH <xN>] => ;
|
|
__dbUpdate( <(alias)>, {|| _FIELD-><key> }, <.rand.>, ;
|
|
{|| _FIELD-><f1> := <x1>[, _FIELD-><fN> := <xN>] } )
|
|
|
|
/* --- bulk maintenance --- */
|
|
#command REINDEX => DbReindex()
|
|
#command PACK => DbPack()
|
|
#command ZAP => DbZap()
|
|
|
|
/* --- input / shell --- */
|
|
#command KEYBOARD <text> => Keyboard(<text>)
|
|
#command RUN <*cmd*> => hb_Run(<(cmd)>)
|
|
|
|
/* --- legacy GET system ---
|
|
MENU TO is intentionally absent: it requires the @ PROMPT statement
|
|
companion which Five doesn't implement. Adding the rule would let
|
|
user code compile and then panic at runtime on the missing
|
|
__MenuTo() symbol. Keep the parser's silent no-op for MENU TO until
|
|
@ PROMPT lands. */
|
|
#command CLEAR GETS => GetList := {}
|