feat: FiveSql2 43/43, @byref, mutable closure, RTL 479, DateTime fix

Major changes since last commit:
- FiveSql2 SQL:1999 engine (10,458 LOC) — 43/43 ALL PASS
- 21 compiler/runtime bugs fixed (short-circuit AND/OR, FOR LOOP, etc.)
- @byref pass-by-reference via RefCell pattern
- Mutable closure capture (EnsureLocalRef + RefCell sharing)
- RTL: 400 → 479 functions (+79: file, string, datetime, hash, UTF-8)
- DateTime/Timestamp fully working (hb_DateTime, hb_Hour/Min/Sec, display)
- Reserved word guard (39 keywords blocked from function calls)
- AEval arg order fix (element before index)
- Closure capture redecl fix (unique _cap_ names per block)
- Hash/string indexing in ArrayPush/ArrayPop
- Harbour compat test suite: 51/51
- 4 docs: Porting Report, Implementation Plan, Optimization Plan, Commercialization

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-11 11:35:37 +09:00
parent d451b836a6
commit 486e466592
129 changed files with 35248 additions and 241 deletions

View File

@@ -591,6 +591,11 @@ func (p *Parser) parseClassDecl() *ast.ClassDecl {
p.advance() // skip keyword
p.advance() // skip :
p.skipNewlines()
} else if upper == "CLASSDATA" || upper == "CLASSVAR" {
// CLASSDATA / CLASSVAR — class-level variable (treat as DATA)
p.tokens[p.pos].Kind = token.DATA
p.current = p.tokens[p.pos]
members = append(members, p.parseDataDecl())
} else if upper == "CLASS" {
// CLASS VAR — class-level variable
p.advance() // skip CLASS
@@ -1511,6 +1516,8 @@ func (p *Parser) parseUse() *ast.UseCmd {
pos := p.expect(token.USE).Pos
var file ast.Expr
var via, alias string
var aliasExprNode ast.Expr
var shared, readOnly bool
// USE without args = close
if p.current.Kind != token.NEWLINE && p.current.Kind != token.EOF {
@@ -1520,8 +1527,22 @@ func (p *Parser) parseUse() *ast.UseCmd {
p.expectEndOfStmt()
return &ast.UseCmd{UsePos: pos}
}
file = p.parseExpr()
p.consumeFileExtension(file)
// 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)
name := p.advance().Literal
if p.at(token.DOT) && (p.peekAt(1) == token.IDENT || p.peekAt(1) == token.INT) {
p.advance() // skip DOT
ext := p.advance().Literal
name = name + "." + ext
}
file = &ast.LiteralExpr{ValuePos: pos, Kind: token.STRING, Value: name}
} else {
file = p.parseExpr()
p.consumeFileExtension(file)
}
}
// Parse optional clauses: VIA, ALIAS, EXCLUSIVE, SHARED, NEW, READONLY
@@ -1530,20 +1551,44 @@ func (p *Parser) parseUse() *ast.UseCmd {
upper := p.currentUpper()
if upper == "VIA" {
p.advance()
via = p.expectMethodName().Literal
if p.at(token.STRING) {
via = p.current.Literal
p.advance()
} else {
via = p.expectMethodName().Literal
}
continue
}
if upper == "ALIAS" {
p.advance()
if p.at(token.AMPERSAND) {
p.parseMacro() // macro alias — skip
} else if p.at(token.LPAREN) {
// ALIAS ( expr ) — parenthesized alias expression (runtime)
p.advance() // skip (
aliasExpr := p.parseExpr()
p.expect(token.RPAREN)
if lit, ok := aliasExpr.(*ast.LiteralExpr); ok && lit.Kind == token.STRING {
alias = lit.Value // constant string — store directly
} else {
aliasExprNode = aliasExpr // dynamic — evaluate at runtime
}
} else {
alias = p.expectMethodName().Literal
}
continue
}
if upper == "EXCLUSIVE" || upper == "SHARED" || upper == "NEW" || upper == "READONLY" ||
upper == "ADDITIVE" {
if upper == "SHARED" {
shared = true
p.advance()
continue
}
if upper == "READONLY" {
readOnly = true
p.advance()
continue
}
if upper == "EXCLUSIVE" || upper == "NEW" || upper == "ADDITIVE" {
p.advance()
continue
}
@@ -1557,6 +1602,16 @@ func (p *Parser) parseUse() *ast.UseCmd {
p.advance()
if p.at(token.AMPERSAND) {
p.parseMacro()
} else if p.at(token.LPAREN) {
// ALIAS ( expr ) — parenthesized alias expression
p.advance()
ae := p.parseExpr()
p.expect(token.RPAREN)
if lit, ok := ae.(*ast.LiteralExpr); ok && lit.Kind == token.STRING {
alias = lit.Value
} else {
aliasExprNode = ae
}
} else {
alias = p.expectMethodName().Literal
}
@@ -1571,7 +1626,7 @@ func (p *Parser) parseUse() *ast.UseCmd {
}
p.expectEndOfStmt()
return &ast.UseCmd{UsePos: pos, File: file, Via: via, Alias: alias}
return &ast.UseCmd{UsePos: pos, File: file, Via: via, Alias: alias, AliasExpr: aliasExprNode, Shared: shared, ReadOnly: readOnly}
}
func (p *Parser) parseSelect() *ast.SelectCmd {