`UPDATE [FROM <alias>] [ON <key>] [RANDOM] REPLACE <f1> WITH <x1>
[, <fN> WITH <xN>]` becomes a preprocessor rewrite to a new RTL
primitive __dbUpdate. For each detail record, find the master
record with matching key (forward-walk if both sorted, full scan
when RANDOM) and apply the REPLACE clauses in master's context.
Same shape as harbour-core/src/rdd/dbupdat.prg. The REPLACE clauses
expand to comma-separated assignments inside one block —
`{|| _FIELD->total := del->amt, _FIELD->status := "OK" }` — using
the multi-pair `[, <fN> WITH <xN>]` optional-repeat that std.ch
already establishes for SUM and DEFAULT.
Five-specific tweak: ON <key> wraps as `{|| _FIELD-><key> }` rather
than Harbour's bare `<{key}>`. Five doesn't auto-resolve a bare
identifier in a code block to the current workarea's field, and the
UPDATE block must evaluate against both detail and master so an
explicit alias prefix won't do — _FIELD-> dispatches to whichever
area is selected at eval time, which is what's needed.
Wiring up UPDATE surfaced one further matchSegment gap that fell
out of the multi-pair `[REPLACE ... [, ...]]` shape:
* matchSegment didn't handle nested `[...]` inside its body.
`[REPLACE <f1> WITH <x1> [, <fN> WITH <xN>]]` gave the inner
`[` as a literal token to match against the line, so even the
single-pair `REPLACE total WITH del->amt` form failed and f1/x1
came back empty. Now matchSegment runs the same repeat-loop on
inner `[...]` blocks that the top-level matcher uses, with its
own outer-tail computed from the segment tail past the inner
`]`.
Parser cleanup: UPDATE removed from the IDENT-statement no-op switch.
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>
151 lines
6.9 KiB
Plaintext
151 lines
6.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. */
|
|
#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.> )
|
|
|
|
/* 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: 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. */
|
|
#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 --- */
|
|
#command MENU TO <var> => <var> := __MenuTo(<var>)
|
|
#command CLEAR GETS => GetList := {}
|