From 02026a19664cfc7ead7df5036ff2d4b2fc3cac3c Mon Sep 17 00:00:00 2001 From: CharlesKWON Date: Sat, 11 Apr 2026 12:11:08 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20analyzer=20zero=20warnings=20=E2=80=94?= =?UTF-8?q?=20complete=20RTL=20coverage,=20cross-file=20awareness?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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) --- cmd/five/main.go | 83 +++++++++- compiler/analyzer/analyzer.go | 285 +++++++++++++++++++++++++++++----- 2 files changed, 326 insertions(+), 42 deletions(-) diff --git a/cmd/five/main.go b/cmd/five/main.go index ca6dbda..d9b76d8 100644 --- a/cmd/five/main.go +++ b/cmd/five/main.go @@ -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) diff --git a/compiler/analyzer/analyzer.go b/compiler/analyzer/analyzer.go index 86f64fb..8d806cc 100644 --- a/compiler/analyzer/analyzer.go +++ b/compiler/analyzer/analyzer.go @@ -65,20 +65,23 @@ type VarInfo struct { // Analyzer performs semantic analysis on a parsed AST file. type Analyzer struct { - file *ast.File - diagnostics []Diagnostic - scope *Scope - funcNames map[string]bool // declared function names + file *ast.File + diagnostics []Diagnostic + scope *Scope + funcNames map[string]bool // declared function names (this file + external) + moduleStatics map[string]VarInfo // module-level STATIC variables } // Analyze runs semantic analysis and returns diagnostics. -func Analyze(file *ast.File) []Diagnostic { +// externalFuncs (optional) provides function names from other files in multi-file builds. +func Analyze(file *ast.File, externalFuncs ...map[string]bool) []Diagnostic { a := &Analyzer{ - file: file, - funcNames: make(map[string]bool), + file: file, + funcNames: make(map[string]bool), + moduleStatics: make(map[string]VarInfo), } - // Phase 1: Collect all function names + // Phase 1: Collect all function names from this file for _, d := range file.Decls { switch decl := d.(type) { case *ast.FuncDecl: @@ -88,6 +91,26 @@ func Analyze(file *ast.File) []Diagnostic { } } + // Merge external function names (from other PRG files in multi-file build) + for _, ext := range externalFuncs { + for name := range ext { + a.funcNames[name] = true + } + } + + // Phase 1.5: Collect module-level STATIC variables + for _, d := range file.Decls { + if vd, ok := d.(*ast.VarDecl); ok && vd.Scope == ast.ScopeStatic { + for _, v := range vd.Vars { + a.moduleStatics[strings.ToUpper(v.Name)] = VarInfo{ + Name: v.Name, + Pos: v.NamePos, + Kind: ast.ScopeStatic, + } + } + } + } + // Phase 2: Analyze each function for _, d := range file.Decls { switch decl := d.(type) { @@ -106,6 +129,11 @@ func (a *Analyzer) analyzeFunc(fn *ast.FuncDecl) { Used: make(map[string]bool), } + // Register module-level STATIC variables (visible to all functions in this file) + for name, info := range a.moduleStatics { + a.scope.Declared[name] = info + } + // Register parameters as declared for _, p := range fn.Params { a.scope.Declared[strings.ToUpper(p.Name)] = VarInfo{ @@ -211,6 +239,14 @@ func (a *Analyzer) analyzeStmt(stmt ast.Stmt) { for _, st := range s.Body { a.analyzeStmt(st) } + // RECOVER USING var — declare the variable in scope + if s.RecoverVar != "" { + a.scope.Declared[strings.ToUpper(s.RecoverVar)] = VarInfo{ + Name: s.RecoverVar, + Pos: s.BeginPos, + Kind: ast.ScopeLocal, + } + } for _, st := range s.RecoverBody { a.analyzeStmt(st) } @@ -330,6 +366,14 @@ func (a *Analyzer) analyzeExpr(expr ast.Expr) { a.analyzeExpr(e.Values[i]) } case *ast.BlockExpr: + // Register block parameters (e.g., {|x,y| x + y}) + for _, p := range e.Params { + a.scope.Declared[strings.ToUpper(p)] = VarInfo{ + Name: p, + Pos: e.LBrace, + IsParam: true, + } + } a.analyzeExpr(e.Body) case *ast.AliasExpr: a.analyzeExpr(e.Alias) @@ -388,48 +432,213 @@ func (a *Analyzer) markUsed(name string) { } } +// rtlFunctions contains all 479 RTL functions registered in hbrtl/register.go. +// Generated from: grep -o 'hbrt.Sym("[^"]*"' hbrtl/register.go +var rtlFunctions = map[string]bool{ + // Console + "QOUT": true, "QQOUT": true, + // String/Conversion + "STR": true, "VAL": true, "LEN": true, "SUBSTR": true, "UPPER": true, "LOWER": true, + "ALLTRIM": true, "LTRIM": true, "RTRIM": true, "TRIM": true, "SPACE": true, + "PADR": true, "PADL": true, "PADC": true, "REPLICATE": true, + // Type/Conversion + "VALTYPE": true, "EMPTY": true, "ABS": true, "INT": true, + // Array + "AADD": true, "ADEL": true, "AINS": true, "ASIZE": true, "ACLONE": true, + "ACOPY": true, "AFILL": true, "ASORT": true, "AEVAL": true, "ASCAN": true, "ATAIL": true, + // Hash + "HB_HASH": true, "HB_HGET": true, "HB_HSET": true, "HB_HDEL": true, + "HB_HHASKEY": true, "HB_HKEYS": true, "HB_HVALUES": true, + "HB_HPOS": true, "HB_HKEYAT": true, "HB_HVALUEAT": true, "HB_HCLONE": true, + // Date/Time + "DATE": true, "TIME": true, "YEAR": true, "MONTH": true, "DAY": true, "DOW": true, + "SECONDS": true, "DTOC": true, "DTOS": true, "STOD": true, "CTOD": true, + "CDOW": true, "CMONTH": true, "DAYS": true, "ELAPTIME": true, "AMPM": true, "SECS": true, + // Eval + "EVAL": true, + // String Extended + "AT": true, "LEFT": true, "RIGHT": true, "ASC": true, "CHR": true, + "STRTRAN": true, "STUFF": true, "RAT": true, "HARDCR": true, + "HB_STRREPLACE": true, "HB_NTOS": true, "DESCEND": true, + "HB_VALTOSTR": true, "HB_VALTOEXP": true, "HB_CSTR": true, + // Math + "ROUND": true, "MAX": true, "MIN": true, "SQRT": true, "LOG": true, "EXP": true, "MOD": true, + // Misc + "TYPE": true, "PCOUNT": true, "BREAK": true, "ARRAY": true, "FCOUNT": true, + "FIELDNAME": true, "SELECT": true, "FILE": true, "INKEY": true, "TRANSFORM": true, + "SETDATEFORMAT": true, "SETEPOCH": true, "SETCENTURY": true, + "IIF": true, "IF": true, "STRZERO": true, "OUTSTD": true, + "CENTER": true, "SOUNDEX": true, "TONE": true, + // Terminal + "SETPOS": true, "ROW": true, "COL": true, "DEVPOS": true, "DEVOUT": true, + "DISPOUT": true, "DEVOUTPICT": true, "DISPBOX": true, "CLS": true, "SCROLL": true, + "SETCOLOR": true, "SETCURSOR": true, "MAXROW": true, "MAXCOL": true, + // dbEdit/Browse + "DBEDIT": true, "TBROWSEDB": true, "TBROWSENEW": true, "TBCOLUMNNEW": true, + // RDD + "EOF": true, "BOF": true, "FOUND": true, "RECNO": true, "RECCOUNT": true, + "LASTREC": true, "DELETED": true, "FIELDGET": true, "FIELDPUT": true, + "FIELDPOS": true, "FIELDBLOCK": true, "FIELDWBLOCK": true, "AFIELDS": true, + "DBSTRUCT": true, + // Database + "ALIAS": true, "DBEVAL": true, "USED": true, "DBUSEAREA": true, + "DBCLOSEAREA": true, "DBCLOSEALL": true, "DBGOTO": true, "DBSKIP": true, + "DBGOTOP": true, "DBGOBOTTOM": true, "DBAPPEND": true, "DBDELETE": true, + "DBRECALL": true, "DBCOMMIT": true, "DBRLOCK": true, "DBRUNLOCK": true, + "DBSEEK": true, "DBSELECTAREA": true, "DBPACK": true, "DBZAP": true, + "DBCREATE": true, "DBINFO": true, "DBORDERINFO": true, "DBSETINDEX": true, + "RECALL": true, "PACK": true, "ZAP": true, + "__DBPACK": true, "__DBZAP": true, + // Locate/Filter + "DBLOCATE": true, "__DBLOCATE": true, "__DBCONTINUE": true, + "DBSETFILTER": true, "DBCLEARFILTER": true, "DBFILTER": true, + // Encoding/Hashing + "HB_MD5": true, "HB_SHA256": true, "HB_BASE64ENCODE": true, + "HB_BASE64DECODE": true, "HB_CRC32": true, + // Bit Operations + "HB_BITAND": true, "HB_BITOR": true, "HB_BITXOR": true, "HB_BITNOT": true, + "HB_BITSHIFT": true, "HB_BITTEST": true, "HB_BITSET": true, "HB_BITRESET": true, + // Regex + "HB_REGEXCOMP": true, "HB_REGEXMATCH": true, "HB_REGEXSPLIT": true, + "HB_REGEXALL": true, "HB_REGEXREPLACE": true, + // Memo + "MEMOREAD": true, "MEMOWRIT": true, "MEMOTRAN": true, "MEMOLINE": true, "MLCOUNT": true, + // Binary Conversion + "BIN2I": true, "BIN2L": true, "BIN2W": true, "I2BIN": true, "L2BIN": true, "W2BIN": true, + // Keyboard + "LASTKEY": true, "NEXTKEY": true, "READKEY": true, "SETKEY": true, + "KEYBOARD": true, "HB_KEYPUT": true, "HB_KEYCHAR": true, "HB_KEYINS": true, + // Display + "DISPBEGIN": true, "DISPEND": true, "DISPCOUNT": true, + "SAVESCREEN": true, "RESTSCREEN": true, "ALERT": true, + // Error Handling + "ERRORBLOCK": true, "ERRORNEW": true, "DOSERROR": true, "FERROR": true, "ERRORSYS": true, + // SET Commands + "SET": true, "__SETDATEFORMAT": true, "__SETDECIMALS": true, "__SETEPOCH": true, + "SETDELETED": true, "SETEXACT": true, "SETSOFTSEEK": true, "SETEXCLUSIVE": true, + "SETFIXED": true, "SETCANCEL": true, "SETBELL": true, "SETCONFIRM": true, + "SETINSERT": true, "SETESCAPE": true, "SETWRAP": true, + "_SET_EXACT": true, "_SET_DELETED": true, "_SET_SOFTSEEK": true, + "_SET_EXCLUSIVE": true, "_SET_DATEFORMAT": true, "_SET_DECIMALS": true, "_SET_EPOCH": true, + // File I/O + "FOPEN": true, "FCREATE": true, "FCLOSE": true, "FREAD": true, "FWRITE": true, + "FSEEK": true, "FERASE": true, "FRENAME": true, "HB_FILEEXISTS": true, + // Directory/Disk + "CURDIR": true, "DIRCHANGE": true, "DIRECTORY": true, "DIRMAKE": true, + "DIRREMOVE": true, "DISKSPACE": true, + // Type Checking + "HB_ISARRAY": true, "HB_ISBLOCK": true, "HB_ISCHAR": true, "HB_ISSTRING": true, + "HB_ISDATE": true, "HB_ISDATETIME": true, "HB_ISLOGICAL": true, "HB_ISNUMERIC": true, + "HB_ISOBJECT": true, "HB_ISHASH": true, "HB_ISNIL": true, "HB_ISPOINTER": true, + "HB_ISEVALITEM": true, "HB_ISNULL": true, + // OS/Environment + "GETENV": true, "HB_GETENV": true, "SETENV": true, "HB_SETENV": true, + "OS": true, "VERSION": true, "HB_RUN": true, + "HB_FNAMEDIR": true, "HB_FNAMEEXT": true, "HB_FNAMENAME": true, "HB_FNAMEMERGE": true, + "HB_FNAMESPLIT": true, "HB_FNAMEEXISTS": true, "HB_FNAMEEXTSET": true, "HB_FNAMENAMEEXT": true, + // Character Classification + "ISDIGIT": true, "ISALPHA": true, "ISALNUM": true, + "ISUPPER": true, "ISLOWER": true, "ISSPACE": true, + // Harbour Extensions + "HB_ASCIIUPPER": true, "HB_ASCIILOWER": true, + "HB_DEFAULT": true, "HB_DEFAULTVALUE": true, + "HB_DISPOUTAT": true, "HB_DISPOUTATBOX": true, "HB_DISPBOX": true, + "HB_COLORINDEX": true, "HB_LEFTEQ": true, "HB_LEFTEQI": true, + "HB_VAL": true, "HB_TOKENGET": true, "HB_TOKENCOUNT": true, + "__DEFAULTNIL": true, "MEMVARBLOCK": true, + // Bitmap/Rushmore + "BM_DBSETFILTER": true, "BM_DBSEEKWILD": true, "BM_TURBO": true, + "BM_DBGETFILTERARRAY": true, "BM_DBSETFILTERARRAY": true, + "BM_DBSETFILTERARRAYADD": true, "BM_DBSETFILTERARRAYDEL": true, + // HBSIX Compatibility + "SX_SETTAG": true, "SX_INDEXTAG": true, "SX_TAGORDER": true, + "SX_TAGCOUNT": true, "SX_TAGS": true, "SX_SETFILEORD": true, + "SX_ISDBT": true, "SX_ISFPT": true, "SX_ISSMT": true, + "SX_AUTOOPEN": true, "SX_AUTOSHARE": true, "SX_BLOB2FILE": true, + "SX_FILE2BLOB": true, "SX_SETTRIGGER": true, "SX_VFGET": true, + "SX_DBFENCRYPT": true, "SX_DBFDECRYPT": true, "SX_COMPRESS": true, + "SX_DECOMPRESS": true, "RDDINFO": true, "RDDNAME": true, "RDDLIST": true, + // Timestamp + "HB_DATETIME": true, "HB_HOUR": true, "HB_MINUTE": true, "HB_SEC": true, + "HB_TTOC": true, "HB_CTOT": true, "HB_SECOND": true, "HB_ATOKENS": true, + "HB_CDPSELECT": true, "HB_TTOS": true, "HB_STOT": true, "HB_MILLISECONDS": true, + "HB_DATE": true, "HB_CTOD": true, "HB_DTOC": true, "HB_STOD": true, + "HB_DTOT": true, "HB_TTOD": true, "HB_TTOHOUR": true, "HB_TTOMIN": true, + "HB_TTOSEC": true, "HB_TTOMSEC": true, "HB_TTON": true, "HB_NTOT": true, + "HB_NTOHOUR": true, "HB_NTOMIN": true, "HB_NTOSEC": true, + "HB_WEEK": true, "HB_CDAY": true, + // Index/DB Introspection + "INDEXORD": true, "INDEXKEY": true, "ORDSETFOCUS": true, "ORDCOUNT": true, + "ORDNAME": true, "ORDKEY": true, "ORDFOR": true, "ORDINFO": true, + "ORDSCOPE": true, "RDDSETDEFAULT": true, + // Directory/Temp + "HB_DIREXISTS": true, "HB_DIRCREATE": true, "HB_FTEMPCREATE": true, "HB_DIRTEMP": true, + // File Extended + "HB_FSIZE": true, "HB_FCOPY": true, "HB_FEOF": true, "HB_FCOMMIT": true, + "HB_FREADLEN": true, "HB_FGETATTR": true, "HB_FSETATTR": true, + "HB_FGETDATETIME": true, "HB_FSETDATETIME": true, "HB_FLOCK": true, "HB_FUNLOCK": true, + "HB_FILEDELETE": true, "HB_FILEMATCH": true, + "HB_MEMOREAD": true, "HB_MEMOWRIT": true, "HB_DISKSPACE": true, + // String Extended 2 + "HB_AT": true, "HB_RAT": true, "HB_ATI": true, "HB_ATX": true, + "HB_ASCIIISALPHA": true, "HB_ASCIIISDIGIT": true, + "HB_ASCIIISLOWER": true, "HB_ASCIIISUPPER": true, + "HB_STRISUTF8": true, "HB_STRDECODESCAPE": true, "HB_STRXOR": true, + "HB_WILDMATCH": true, "HB_WILDMATCHI": true, + "HB_STRTOHEX": true, "HB_HEXTOSTR": true, "HB_STRFORMAT": true, + // Hex Conversion + "HB_NUMTOHEX": true, "HB_HEXTONUM": true, + // Token + "TOKEN": true, "NUMTOKEN": true, + // Stack Introspection + "PROCNAME": true, "PROCLINE": true, "PROCFILE": true, "ERRORLEVEL": true, + // JSON + "HB_JSONENCODE": true, "HB_JSONDECODE": true, + "JSONPRETTY": true, "JSONTO": true, "JSONFROM": true, "JSONPATH": true, + "JSONMERGE": true, "JSONTYPE": true, "JSONVALID": true, + "JSONHTTPGET": true, "JSONHTTPPOST": true, + // Random + "HB_RANDOM": true, "HB_RANDOMINT": true, "HB_RANDOMSEED": true, "HB_RANDSTR": true, + // OS Info + "HB_VERSION": true, "HB_COMPILER": true, "HB_OSNEWLINE": true, + "HB_OSPATHSEPARATOR": true, "HB_CWD": true, "HB_DIRBASE": true, + "HB_PROGNAME": true, "HB_USERNAME": true, "HB_GETHOSTNAME": true, + // Process + "HB_PROCESSRUN": true, "WAIT": true, + // UTF-8 + "HB_UTF8TOSTR": true, "HB_STRTOUTF8": true, "HB_UTF8LEN": true, + "HB_UTF8SUBSTR": true, "HB_UTF8LEFT": true, "HB_UTF8RIGHT": true, "HB_UTF8AT": true, + // FRB + "FRBLOAD": true, "FRBDO": true, "FRBUNLOAD": true, "FRBRUN": true, + "FRBCOMPILE": true, "FRBEXEC": true, + // Concurrency + "GO": true, "CHANNEL": true, "CHSEND": true, "CHRECEIVE": true, "CHCLOSE": true, + "WAITGROUP": true, "WGDONE": true, "WGWAIT": true, "WGADD": true, + "MUTEX": true, "LOCK": true, "UNLOCK": true, "SLEEP": true, + // Harbour compat aliases + "HB_SYMBOL_UNUSED": true, "HB_IDLEADD": true, "HB_IDLESLEEP": true, + "HB_PS": true, "HB_EOL": true, +} + func (a *Analyzer) isKnownFunction(name string) bool { - // Check declared functions in this file + // Check declared functions in this file (and external files) if a.funcNames[name] { return true } - // Common RTL functions - rtl := map[string]bool{ - "LEN": true, "SUBSTR": true, "LEFT": true, "RIGHT": true, - "UPPER": true, "LOWER": true, "TRIM": true, "LTRIM": true, "RTRIM": true, - "STR": true, "VAL": true, "STRTRAN": true, "AT": true, "RAT": true, - "SPACE": true, "REPLICATE": true, "PADR": true, "PADL": true, "PADC": true, - "VALTYPE": true, "TYPE": true, "EMPTY": true, "HB_ISSTRING": true, - "EVAL": true, "AEVAL": true, "ASCAN": true, "ASORT": true, - "AADD": true, "ADEL": true, "AINS": true, "ASIZE": true, "ACOPY": true, "ACLONE": true, - "ARRAY": true, "HASH": true, "HB_HASH": true, - "DTOC": true, "CTOD": true, "DTOS": true, "DATE": true, "TIME": true, "YEAR": true, "MONTH": true, "DAY": true, - "QOUT": true, "QQOUT": true, "OUTSTD": true, "ALERT": true, - "INKEY": true, "LASTKEY": true, "CHR": true, "ASC": true, - "FILE": true, "FOPEN": true, "FCLOSE": true, "FREAD": true, "FWRITE": true, - "IIF": true, "IF": true, "STRZERO": true, "TRANSFORM": true, - "FIELDNAME": true, "FIELDPUT": true, "FIELDGET": true, "FCOUNT": true, - "ALIAS": true, "DBAPPEND": true, "DBDELETE": true, "DBSKIP": true, - "DBGOTO": true, "DBGOTOP": true, "DBGOBOTTOM": true, "DBCOMMIT": true, - "RECNO": true, "RECCOUNT": true, "EOF": true, "BOF": true, "FOUND": true, - "CHANNEL": true, "CHSEND": true, "CHRECEIVE": true, - "SLEEP": true, "HB_IDLEADD": true, "SECONDS": true, - "ERRORBLOCK": true, "BREAK": true, "PCOUNT": true, "PROCNAME": true, - "SETPOS": true, "ROW": true, "COL": true, "MAXROW": true, "MAXCOL": true, - "ABS": true, "INT": true, "ROUND": true, "SQRT": true, "LOG": true, "EXP": true, - "MAX": true, "MIN": true, "MOD": true, - "SETCOLOR": true, "DISPBOX": true, "DISPBEGIN": true, "DISPEND": true, - "HB_SYMBOL_UNUSED": true, "HB_DEFAULT": true, "HB_NTOS": true, - } - return rtl[name] + return rtlFunctions[name] } func (a *Analyzer) isBuiltinConstant(name string) bool { constants := map[string]bool{ "NIL": true, "TRUE": true, "FALSE": true, "SELF": true, "SUPER": true, + // Harbour commands treated as identifiers + "QUIT": true, "ERRORLEVEL": true, + // Keyboard constants "K_ESC": true, "K_ENTER": true, "K_UP": true, "K_DOWN": true, "K_LEFT": true, "K_RIGHT": true, "K_PGUP": true, "K_PGDN": true, + // Alternate source (SET ALTERNATE) + "ALTSRC": true, } return constants[name] }