`LIST [<fields>] [OFF] [FOR ...] [WHILE ...] [NEXT ...] [RECORD ...]
[REST] [ALL]` and `DISPLAY [<fields>] [OFF] [FOR ...] ... [ALL]`
reach the parser as plain function calls to a new RTL primitive
__dbList (rtlDbList in hbrtl/database.go).
Implementation: walk the workarea under dbEval-style FOR/WHILE/NEXT/
RECORD/REST bounds. For each visible record, evaluate each column
block and emit the rendered values via valueToDisplay (the same
formatter QOut already uses). Empty fields list defaults to
"all fields". OFF suppresses the record-number prefix.
LIST always emits the full filtered range; DISPLAY without ALL emits
only the current record (encoded as nCount=1). TO PRINTER / TO FILE
clauses are not yet wired through — for now everything goes to
stdout.
Wiring up LIST/DISPLAY surfaced four further gaps in PP that were
silently masking bugs in any rule with multiple word-list / list /
optional clauses chained together:
* matchSegment refused MarkerWordList inside `[...]`. The LIST
rule's `[<off:OFF>]` clause therefore never set the off
capture, and `<.off.>` substituted to nothing instead of .T./.F.
matchSegment now matches WordList markers the same way the
top-level matcher does.
* `<v,...>` and `<(f)>` capture stop boundaries didn't include the
values of following MarkerWordList markers. For
`[<v,...>] [<off:OFF>] [<all:ALL>]` against `LIST id, name OFF`,
the v list would happily eat OFF. New addStopFrom helper
contributes both literal keywords and word-list values; both
matchSegment's MarkerList branch and captureExpression now use
it.
* Optional-repeat loop in matchPattern merged a no-progress
iteration's empty capture into the running multi-capture string
(with the `\x01` separator) before the no-progress break check
fired. So a successful first iteration's value got contaminated
and the substitution loop then skipped it as multi-capture
garbage. The merge now happens after the progress check.
* Unreferenced `<.name.>` markers (optional clauses that didn't
match in the input) were getting cleaned up to empty by the
generic marker scrubber instead of the .F. sentinel Harbour's
std.ch expects. New replaceUnreferencedLogify pass mirrors the
existing replaceUnreferencedBlockify and runs just before the
cleanup.
Parser cleanup: LIST and DISPLAY removed from the IDENT-statement
no-op switch in both parseIdentStmt and parseExprStmt.
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>
114 lines
5.0 KiB
Plaintext
114 lines
5.0 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. */
|
|
#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.> )
|
|
|
|
#command SUM <x> TO <v> ;
|
|
[FOR <for>] [WHILE <while>] [NEXT <next>] ;
|
|
[RECORD <rec>] [<rest:REST>] [ALL] => ;
|
|
<v> := 0 ; dbEval( {|| <v> := <v> + <x> }, ;
|
|
<{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.> )
|
|
|
|
/* --- 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 --- */
|
|
#command MENU TO <var> => <var> := __MenuTo(<var>)
|
|
#command CLEAR GETS => GetList := {}
|