fix(pp,parser,gengo): pre-release blocker round (Wave 1)
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>
This commit is contained in:
@@ -426,13 +426,24 @@ func (p *Parser) parseArrayOrBlock() ast.Expr {
|
||||
}
|
||||
|
||||
// Parse block body — may have comma-separated expressions
|
||||
// {|x| expr1, expr2} → comma = sequence, returns last value
|
||||
body := p.parseExpr()
|
||||
// {|x| expr1, expr2, expr3} → all evaluated in order, last is
|
||||
// the return value. Earlier impl dropped intermediate exprs by
|
||||
// overwriting `body`, which was a silent miscompile (any
|
||||
// non-trailing side effect — e.g. `<v> := <v> + <x>` in a
|
||||
// multi-pair SUM block — vanished).
|
||||
first := p.parseExpr()
|
||||
var seq []ast.Expr
|
||||
for p.match(token.COMMA) {
|
||||
// Comma-separated: wrap as sequence, keep last
|
||||
body = p.parseExpr()
|
||||
if seq == nil {
|
||||
seq = []ast.Expr{first}
|
||||
}
|
||||
seq = append(seq, p.parseExpr())
|
||||
}
|
||||
rbrace := p.expect(token.RBRACE).Pos
|
||||
var body ast.Expr = first
|
||||
if seq != nil {
|
||||
body = &ast.SeqExpr{Items: seq, StartAt: first.Pos(), EndAt: rbrace}
|
||||
}
|
||||
|
||||
return &ast.BlockExpr{LBrace: lbrace, Params: params, Body: body, RBrace: rbrace}
|
||||
}
|
||||
|
||||
@@ -1218,12 +1218,17 @@ func (p *Parser) parseExprStmt() ast.Stmt {
|
||||
p.peekAt(1) == token.TIMEOUT_KW {
|
||||
return p.parseWithTimeout()
|
||||
}
|
||||
// Keep this list IN SYNC with parseIdentStmt's switch above.
|
||||
// COPY/SORT/COUNT/SUM/AVERAGE/TOTAL/UPDATE/JOIN/DISPLAY/LIST
|
||||
// are no longer here — std.ch rewrites them to function calls
|
||||
// before the parser sees them. Leaving them in the fallback
|
||||
// would silently no-op a typo'd version (e.g. `COPYY TO ...`)
|
||||
// against the user's expectation.
|
||||
switch p.currentUpper() {
|
||||
case "COPY", "SORT", "COUNT", "SUM", "AVERAGE", "TOTAL", "UPDATE",
|
||||
"LABEL", "REPORT", "ACCEPT", "INPUT",
|
||||
"JOIN", "RELEASE", "SAVE", "RESTORE",
|
||||
case "LABEL", "REPORT", "ACCEPT", "INPUT",
|
||||
"RELEASE", "SAVE", "RESTORE",
|
||||
"DIR", "STORE", "NOTE", "TEXT", "ENDTEXT",
|
||||
"WITH", "CLEAR", "DISPLAY", "LIST":
|
||||
"WITH", "CLEAR":
|
||||
// Consume entire line — these are complex multi-word commands
|
||||
p.advance()
|
||||
for p.current.Kind != token.NEWLINE && p.current.Kind != token.EOF {
|
||||
|
||||
Reference in New Issue
Block a user