diff --git a/compiler/parser/parser.go b/compiler/parser/parser.go index 6ca936f..edf4b97 100644 --- a/compiler/parser/parser.go +++ b/compiler/parser/parser.go @@ -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 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 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() diff --git a/compiler/pp/command.go b/compiler/pp/command.go index 9360df6..745aa46 100644 --- a/compiler/pp/command.go +++ b/compiler/pp/command.go @@ -221,16 +221,35 @@ func (r *Rule) matchPattern(line string) map[string]string { pi++ case MarkerList: - // Capture comma-separated items until next keyword - var items []string + // Capture a comma-separated list until the next literal + // pattern token. Paren-balanced so nested `(`/`[`/`{` + // don't let an inner `)` terminate the capture. Commas + // at the top level are preserved verbatim in the + // captured string so the `` substitution in the + // result template reproduces the argument list as-is. + var parts []string + depth := 0 + delim := "" + if pi+1 < len(patternWords) { + delim = patternWords[pi+1] + } for li < len(lineWords) { - if pi+1 < len(patternWords) && matchWord(lineWords[li], patternWords[pi+1], r.CaseSens) { + w := lineWords[li] + if depth == 0 && delim != "" && matchWord(w, delim, r.CaseSens) { break } - items = append(items, lineWords[li]) + switch w { + case "(", "[", "{": + depth++ + case ")", "]", "}": + if depth > 0 { + depth-- + } + } + parts = append(parts, w) li++ } - captures[m.Name] = strings.Join(items, " ") + captures[m.Name] = strings.Join(parts, " ") pi++ case MarkerWordList: @@ -567,13 +586,27 @@ func captureExpression(lineWords []string, li *int, patternWords []string, nextP } if delimWord != "" { - // Capture until delimiter keyword + // Capture until the delimiter, paren-balancing so nested + // parens/brackets/braces inside the expression don't falsely + // terminate the capture. Harbour's own PP does the same — + // `_REGULAR_(&(a))` must capture `&(a)` (incl. inner parens) + // and leave the outer `)` for the pattern's own delimiter. var parts []string + depth := 0 for *li < len(lineWords) { - if matchWord(lineWords[*li], delimWord, caseSens) { + w := lineWords[*li] + if depth == 0 && matchWord(w, delimWord, caseSens) { break } - parts = append(parts, lineWords[*li]) + switch w { + case "(", "[", "{": + depth++ + case ")", "]", "}": + if depth > 0 { + depth-- + } + } + parts = append(parts, w) *li++ } return strings.Join(parts, " ")