feat(pp): Phase A — preprocessor std.ch as single source of truth
Introduce compiler/pp/std.ch with 19 #command rules so that ERASE,
RENAME, DELETE FILE, CLOSE [<a>|ALL|DATABASES], COMMIT, UNLOCK,
LOCATE/CONTINUE, REINDEX, PACK, ZAP, KEYBOARD, RUN, MENU TO, and
CLEAR GETS reach the parser pre-rewritten as plain function calls.
Embedded into the compiler binary via //go:embed so it auto-loads
without an explicit #include in user code, exactly the way Harbour
auto-loads its std.ch.
This is a pure dispatch move, not a behavior change for the
already-working forms: the same Five RTL functions get called.
But it does fix three regressions that the parser was masking:
* ERASE / RENAME / DELETE FILE used to be silent no-ops — the
parser swallowed the entire line and returned NIL. They now
actually delete/rename files (FErase / FRename).
* CLOSE <alias> used to silently ignore the alias and close the
current area. It now switches to the named area first
(<a>->( DbCloseArea() )).
* Two latent #command matcher bugs that surfaced while wiring
std.ch up:
- bare `CLOSE` would match rule `CLOSE ALL` because the tail
of the pattern wasn't checked for unconsumed literals.
- bare `CLOSE` would match rule `CLOSE <a>` because all
unconsumed pattern markers were unconditionally treated as
optional. They are only optional when nested inside `[...]`.
Parser cleanup: parseIdentStmt + parseExprStmt no longer hardcode
ERASE / RENAME / RUN / KEYBOARD / REINDEX / LOCATE / CONTINUE /
COMMIT / CLOSE — the rewriter handles them. Other xBase verbs
(COPY / SORT / COUNT / SUM / AVERAGE / TOTAL / JOIN / LIST /
DISPLAY / LABEL / REPORT / DIR ...) still no-op in the parser
because their RTL backends aren't implemented yet — once the
backends land they move into std.ch the same way.
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>
This commit is contained in:
@@ -1148,13 +1148,18 @@ func (p *Parser) parseIdentStmt() ast.Stmt {
|
||||
return p.parseWithTimeout()
|
||||
}
|
||||
|
||||
// xBase commands that consume entire line
|
||||
// xBase commands that consume entire line. These are silent no-ops
|
||||
// for now — they have no RTL backend, so std.ch deliberately omits
|
||||
// rules for them. ERASE / RENAME / LOCATE / CONTINUE / COMMIT /
|
||||
// CLOSE / REINDEX / PACK / ZAP / UNLOCK / KEYBOARD / RUN are now
|
||||
// rewritten by compiler/pp/std.ch into function calls before the
|
||||
// parser sees them.
|
||||
switch upper {
|
||||
case "COPY", "SORT", "COUNT", "SUM", "AVERAGE", "TOTAL", "UPDATE",
|
||||
"LABEL", "REPORT", "ACCEPT", "INPUT", "LOCATE", "CONTINUE",
|
||||
"JOIN", "RELEASE", "SAVE", "RESTORE", "ERASE", "RENAME",
|
||||
"RUN", "DIR", "STORE", "NOTE", "TEXT", "ENDTEXT",
|
||||
"WITH", "KEYBOARD", "CLEAR", "DISPLAY", "LIST", "REINDEX":
|
||||
"LABEL", "REPORT", "ACCEPT", "INPUT",
|
||||
"JOIN", "RELEASE", "SAVE", "RESTORE",
|
||||
"DIR", "STORE", "NOTE", "TEXT", "ENDTEXT",
|
||||
"WITH", "CLEAR", "DISPLAY", "LIST":
|
||||
p.advance()
|
||||
for p.current.Kind != token.NEWLINE && p.current.Kind != token.EOF {
|
||||
p.advance()
|
||||
@@ -1162,13 +1167,6 @@ func (p *Parser) parseIdentStmt() ast.Stmt {
|
||||
p.expectEndOfStmt()
|
||||
return &ast.ExprStmt{X: &ast.LiteralExpr{Kind: token.NIL_LIT, Value: "NIL"}}
|
||||
|
||||
case "COMMIT":
|
||||
p.advance()
|
||||
p.expectEndOfStmt()
|
||||
return &ast.ExprStmt{X: &ast.CallExpr{
|
||||
Func: &ast.IdentExpr{Name: "DbCommit"},
|
||||
}}
|
||||
|
||||
case "FIVE_GODUMP__":
|
||||
// GoDump is a Decl, wrap as ExprStmt for statement context
|
||||
p.advance() // consume FIVE_GODUMP__
|
||||
@@ -1211,19 +1209,10 @@ func (p *Parser) parseExprStmt() ast.Stmt {
|
||||
if p.current.Kind == token.IDENT && p.currentUpper() == "TRY" {
|
||||
return p.parseTryCatch()
|
||||
}
|
||||
// CLOSE [DATABASES|ALL] — close work areas
|
||||
if p.current.Kind == token.IDENT && p.currentUpper() == "CLOSE" {
|
||||
p.advance()
|
||||
// Skip optional DATABASES/ALL keyword
|
||||
if p.current.Kind == token.IDENT {
|
||||
p.advance()
|
||||
}
|
||||
p.expectEndOfStmt()
|
||||
return &ast.ExprStmt{X: &ast.CallExpr{
|
||||
Func: &ast.IdentExpr{Name: "DbCloseArea"},
|
||||
}}
|
||||
}
|
||||
// xBase commands that consume entire line (COPY, SORT, COUNT, SUM, etc.)
|
||||
// xBase commands that consume entire line — duplicate of the switch
|
||||
// in parseIdentStmt(). The keyword set is kept in sync; std.ch covers
|
||||
// ERASE/RENAME/LOCATE/CONTINUE/COMMIT/CLOSE/REINDEX/PACK/ZAP/UNLOCK/
|
||||
// KEYBOARD/RUN, so they're absent here.
|
||||
if p.current.Kind == token.IDENT {
|
||||
// WITH TIMEOUT n / body / ENDWITH
|
||||
if p.currentUpper() == "WITH" &&
|
||||
@@ -1232,10 +1221,10 @@ func (p *Parser) parseExprStmt() ast.Stmt {
|
||||
}
|
||||
switch p.currentUpper() {
|
||||
case "COPY", "SORT", "COUNT", "SUM", "AVERAGE", "TOTAL", "UPDATE",
|
||||
"LABEL", "REPORT", "ACCEPT", "INPUT", "LOCATE", "CONTINUE",
|
||||
"JOIN", "RELEASE", "SAVE", "RESTORE", "ERASE", "RENAME",
|
||||
"RUN", "DIR", "STORE", "NOTE", "TEXT", "ENDTEXT",
|
||||
"WITH", "KEYBOARD", "CLEAR", "DISPLAY", "LIST", "REINDEX":
|
||||
"LABEL", "REPORT", "ACCEPT", "INPUT",
|
||||
"JOIN", "RELEASE", "SAVE", "RESTORE",
|
||||
"DIR", "STORE", "NOTE", "TEXT", "ENDTEXT",
|
||||
"WITH", "CLEAR", "DISPLAY", "LIST":
|
||||
// Consume entire line — these are complex multi-word commands
|
||||
p.advance()
|
||||
for p.current.Kind != token.NEWLINE && p.current.Kind != token.EOF {
|
||||
@@ -1246,14 +1235,6 @@ func (p *Parser) parseExprStmt() ast.Stmt {
|
||||
}
|
||||
}
|
||||
|
||||
// COMMIT — flush work area
|
||||
if p.current.Kind == token.IDENT && p.currentUpper() == "COMMIT" {
|
||||
p.advance()
|
||||
p.expectEndOfStmt()
|
||||
return &ast.ExprStmt{X: &ast.CallExpr{
|
||||
Func: &ast.IdentExpr{Name: "DbCommit"},
|
||||
}}
|
||||
}
|
||||
expr := p.parseExpr()
|
||||
|
||||
// ch <- value (channel send)
|
||||
|
||||
Reference in New Issue
Block a user