feat(parser+pp): USE with macros and paren-balanced PP capture
Two related fixes for Harbour's data-driven `USE &cFile ALIAS &cAlias
INDEX &cNdx` idiom — common in any app that dispatches table names
at runtime.
Parser (compiler/parser/parser.go parseUse):
- `USE &cFile` / `USE &(expr)` previously triggered a
skipToEndOfLine short-circuit, emitting an empty UseCmd (equivalent
to bare USE = close current area). Now parseMacro runs and the
MacroExpr becomes the File node, so codegen emits MacroPush +
dbUseArea.
- `ALIAS &cAlias` / `ALIAS &a.1` similarly dropped the macro result;
now captures it into UseCmd.AliasExpr so codegen evaluates the
alias at runtime. Both the IDENT-path ("ALIAS") and keyword-path
(token.ALIAS) handlers fixed.
PP (compiler/pp/command.go):
- captureExpression and the MarkerList branch now paren-balance
`(`/`[`/`{` so nested grouping inside a macro argument doesn't let
an inner `)` terminate the capture. Example:
_REGULAR_(&(a))
previously captured `&(a` (missing inner `)`) and left the outer
`)` dangling, producing parse errors in the expanded output.
- MarkerList capture still joins tokens with " " for raw `<z>`
substitution — comma tokens stay in the stream, so `s(<z>)`
re-emits them as argument separators and the list expands cleanly.
Bench: harbour-core/tests/pp.prg 2 errors → 0 for the realistic
`USE ¯o` / `&(expr)` patterns. Remaining parse errors on line 70
are a pathological `_REGULAR_L` list that includes `&a. [2]`
(space between macro's terminating dot and an array index) — the
PP expands it correctly but Five's lexer refuses the expanded
result. That form doesn't occur in real code.
/tmp/test_use_macro.prg — all four patterns (`USE &f`, `USE &f ALIAS
&f`, `USE &f ALIAS &f INDEX &i`, dot-terminated) now compile. FiveSql2
43/43, Harbour compat 56/56, Go test ALL PASS.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1723,17 +1723,14 @@ func (p *Parser) parseUse() *ast.UseCmd {
|
||||
|
||||
// USE without args = close
|
||||
if p.current.Kind != token.NEWLINE && p.current.Kind != token.EOF {
|
||||
// If file starts with macro &, skip entire USE to EOL (complex macro syntax)
|
||||
// `USE &cFile` / `USE &(expr)` — macro expression yields the
|
||||
// filename at runtime. Harbour uses this heavily for data-
|
||||
// driven apps (USE &cTable INDEX &cIndex ...).
|
||||
if p.at(token.AMPERSAND) {
|
||||
p.skipToEndOfLine()
|
||||
p.expectEndOfStmt()
|
||||
return &ast.UseCmd{UsePos: pos}
|
||||
}
|
||||
// Bare ident as filename: USE myfile / USE myfile.dbf / USE myfile NEW
|
||||
// In Harbour, USE <name> treats name as a filename string, not a variable.
|
||||
// Only use parseExpr for parenthesized (USE (expr)) or string literal (USE "file").
|
||||
if p.at(token.IDENT) {
|
||||
// Check if it's a bare filename (ident optionally followed by .ext)
|
||||
file = p.parseMacro()
|
||||
} else if p.at(token.IDENT) {
|
||||
// Bare ident as filename: USE myfile / USE myfile.dbf / USE myfile NEW
|
||||
// In Harbour, USE <name> treats name as a filename string, not a variable.
|
||||
name := p.advance().Literal
|
||||
if p.at(token.DOT) && (p.peekAt(1) == token.IDENT || p.peekAt(1) == token.INT) {
|
||||
p.advance() // skip DOT
|
||||
@@ -1764,7 +1761,9 @@ func (p *Parser) parseUse() *ast.UseCmd {
|
||||
if upper == "ALIAS" {
|
||||
p.advance()
|
||||
if p.at(token.AMPERSAND) {
|
||||
p.parseMacro() // macro alias — skip
|
||||
// `ALIAS &cAlias` / `&cAlias.1` — compute the alias
|
||||
// name at runtime via macro evaluation.
|
||||
aliasExprNode = p.parseMacro()
|
||||
} else if p.at(token.LPAREN) {
|
||||
// ALIAS ( expr ) — parenthesized alias expression (runtime)
|
||||
p.advance() // skip (
|
||||
@@ -1803,7 +1802,7 @@ func (p *Parser) parseUse() *ast.UseCmd {
|
||||
if p.current.Kind == token.ALIAS {
|
||||
p.advance()
|
||||
if p.at(token.AMPERSAND) {
|
||||
p.parseMacro()
|
||||
aliasExprNode = p.parseMacro()
|
||||
} else if p.at(token.LPAREN) {
|
||||
// ALIAS ( expr ) — parenthesized alias expression
|
||||
p.advance()
|
||||
|
||||
Reference in New Issue
Block a user