fix: analyzer zero warnings — complete RTL coverage, cross-file awareness

- Register all 479 RTL functions from hbrtl/register.go (was ~60)
- Recognize module-level STATIC variables across all functions
- Declare RECOVER USING variables in analyzer scope
- Register code block parameters ({|x,y| ...}) as declared
- 2-pass multi-file build: collect cross-file function names before analysis
- Add QUIT, ERRORLEVEL, ALTSRC to builtin constants

All 3 test suites pass with 0 warnings:
  go test ./...        — ALL PASS
  FiveSql2 43/43       — 100%
  compat_harbour 51/51 — 100%

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-11 12:11:08 +09:00
parent 468aa1efbd
commit 02026a1966
2 changed files with 326 additions and 42 deletions

View File

@@ -11,6 +11,7 @@ package main
import (
"five/compiler/analyzer"
"five/compiler/ast"
"five/compiler/gengo"
"five/compiler/genpc"
"five/compiler/parser"
@@ -199,10 +200,42 @@ func buildMultiPRG(prgFiles []string, output string) {
}
defer os.RemoveAll(tmpDir)
// Compile each PRG to a .go file
// First file with MAIN gets Generate(), rest get GenerateLibrary()
for i, prgFile := range prgFiles {
goCode := compilePRGMode(prgFile, i > 0) // i>0 = library mode
// Phase 1: Parse all files and collect cross-file function names
type parsedFile struct {
file *ast.File
prgFile string
}
var parsed []parsedFile
crossFileFuncs := make(map[string]bool)
for _, prgFile := range prgFiles {
f := parsePRGFile(prgFile)
parsed = append(parsed, parsedFile{file: f, prgFile: prgFile})
for _, d := range f.Decls {
switch decl := d.(type) {
case *ast.FuncDecl:
crossFileFuncs[strings.ToUpper(decl.Name)] = true
case *ast.ClassDecl:
crossFileFuncs[strings.ToUpper(decl.Name)] = true
}
}
}
// Phase 2: Analyze and generate each file with cross-file function awareness
for i, pf := range parsed {
diags := analyzer.Analyze(pf.file, crossFileFuncs)
for _, d := range diags {
if d.Severity <= analyzer.SevWarning {
fmt.Fprintf(os.Stderr, "%s\n", d)
}
}
var goCode string
if i > 0 {
goCode = gengo.GenerateLibrary(pf.file)
} else {
goCode = gengo.Generate(pf.file)
}
goFile := fmt.Sprintf("prg_%d.go", i)
writeFile(filepath.Join(tmpDir, goFile), goCode)
}
@@ -250,6 +283,48 @@ func buildMultiPRGWithIncludes(prgFiles []string, output string, includes []stri
buildMultiPRG(prgFiles, output)
}
// parsePRGFile preprocesses and parses a PRG file, returning the AST.
func parsePRGFile(prgFile string) *ast.File {
source, err := os.ReadFile(prgFile)
if err != nil {
fatal("cannot read file: " + err.Error())
}
pre := pp.New()
pre.AddIncludeDir(filepath.Dir(prgFile))
pre.AddIncludeDir(filepath.Join(filepath.Dir(prgFile), "include"))
fiveRoot := findFiveRoot()
pre.AddIncludeDir(filepath.Join(fiveRoot, "include"))
if exePath, err := os.Executable(); err == nil {
pre.AddIncludeDir(filepath.Join(filepath.Dir(exePath), "include"))
}
for _, dir := range userIncludeDirs {
pre.AddIncludeDir(dir)
}
if hbInc := os.Getenv("HB_INC"); hbInc != "" {
pre.AddIncludeDir(hbInc)
}
for _, p := range []string{"/usr/local/include/harbour", "/usr/include/harbour"} {
if _, err := os.Stat(p); err == nil {
pre.AddIncludeDir(p)
}
}
processed, ppErrors := pre.Process(prgFile, string(source))
for _, e := range ppErrors {
fmt.Fprintf(os.Stderr, "pp: %s\n", e)
}
file, errs := parser.ParseWithGoDumps(prgFile, processed, pre.GoDumps)
if len(errs) > 0 {
for _, e := range errs {
fmt.Fprintf(os.Stderr, "%s\n", e)
}
fatal(fmt.Sprintf("%d parse error(s) in %s", len(errs), prgFile))
}
return file
}
// compilePRGMode compiles with library flag support.
func compilePRGMode(prgFile string, isLibrary bool) string {
source, err := os.ReadFile(prgFile)