Files
five/hbrt/macro.go
Charles KWON OhJun d7513eeb24 fix: Code review round 2 — race conditions, dead code, hardcoded paths
CRITICAL fixes:
- #1 vm.go: Mutex on libModules/dynamicFuncs global slices
  RegisterLibModule/RegisterDynamicFunc now thread-safe
  RegisterLibModules copies under lock, clears, releases
- #4 shutdown.go: Signal handler goroutine leak fixed
  Uses done channel + select for clean exit on normal shutdown

HIGH fixes:
- #7-8 gobridge.go: Remove dead if/else branches (both identical)
- #13-14 main.go: Remove hardcoded /mnt/d/harbour-core paths
  Use HB_INC env var + standard /usr/local/include/harbour only
- #15 main.go: Remove unused frbModSeq variable

MEDIUM fixes:
- #22 expr.go: Remove unused parts variable in parseInterpolatedString
- #51 macro.go: Remove var _ = fmt.Sprintf import guard
- macroeval.go: Remove unused lexer import and guard

Total fixed this session: 12/53 issues resolved
Remaining: 41 (CRITICAL: 1, HIGH: 9, MEDIUM: 16, LOW: 16)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 10:32:09 +09:00

168 lines
4.1 KiB
Go

// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com)
// All rights reserved.
// Runtime macro compiler for Five.
// Implements &variable and &(expression) — runtime code compilation.
//
// Harbour has a full macro compiler (src/macro/macro.y) that parses
// and compiles expressions at runtime. Five uses a simplified approach:
// parse the expression string, then evaluate it using the existing
// lexer/parser/evaluator infrastructure.
//
// Usage:
// LOCAL cField := "salary"
// ? &cField → evaluates variable named "salary"
// ? &(cField + "_new") → evaluates variable named "salary_new"
//
// Reference: /mnt/d/harbour-core/src/macro/
package hbrt
import (
"fmt"
"strings"
)
// MacroCompile compiles and evaluates a macro expression string.
// Returns the result value.
//
// For simple variable references (&cVar):
// Looks up the variable name in memvars/locals.
//
// For complex expressions (&(expr)):
// Would need full expression parser — simplified for now.
func (t *Thread) MacroCompile(expr string) Value {
expr = strings.TrimSpace(expr)
if expr == "" {
return MakeNil()
}
// Simple case: expression is a variable name
// Look up in memvars first, then try as function call
if isSimpleIdent(expr) {
// Try PUBLIC/PRIVATE memvar
// TODO: full memvar system
// For now, try calling it as a function
sym := t.vm.FindSymbol(strings.ToUpper(expr))
if sym != nil && sym.Func != nil {
t.PushSymbol(sym)
t.PushNil()
t.Function(0)
return t.pop()
}
return MakeString(expr) // return as string if not found
}
// Complex expression: try parsing as number, then as function call
// Full runtime expression parser would be needed for complete macro support.
// This handles common patterns: &("literal"), &(numericExpr)
// Try numeric
expr = strings.TrimSpace(expr)
if len(expr) > 0 && (expr[0] >= '0' && expr[0] <= '9' || expr[0] == '-' || expr[0] == '+') {
if strings.Contains(expr, ".") {
if f, err := parseFloat(expr); err == nil {
return MakeDoubleAuto(f)
}
} else {
if n, err := parseInt64(expr); err == nil {
return MakeNumInt(n)
}
}
}
// Try string literal
if len(expr) >= 2 && (expr[0] == '"' && expr[len(expr)-1] == '"' || expr[0] == '\'' && expr[len(expr)-1] == '\'') {
return MakeString(expr[1 : len(expr)-1])
}
// Try .T./.F.
upper := strings.ToUpper(expr)
if upper == ".T." {
return MakeBool(true)
}
if upper == ".F." {
return MakeBool(false)
}
// Return as string (field name, variable name, etc.)
return MakeString(expr)
}
// MacroPush compiles a macro and pushes the result on stack.
// Harbour: HB_P_MACROPUSH
func (t *Thread) MacroPush() {
exprVal := t.pop()
result := t.MacroCompile(exprVal.AsString())
t.push(result)
}
func parseFloat(s string) (float64, error) {
var result float64
var sign float64 = 1
i := 0
if i < len(s) && s[i] == '-' {
sign = -1
i++
} else if i < len(s) && s[i] == '+' {
i++
}
for i < len(s) && s[i] >= '0' && s[i] <= '9' {
result = result*10 + float64(s[i]-'0')
i++
}
if i < len(s) && s[i] == '.' {
i++
frac := 0.1
for i < len(s) && s[i] >= '0' && s[i] <= '9' {
result += float64(s[i]-'0') * frac
frac /= 10
i++
}
}
if i != len(s) {
return 0, fmt.Errorf("invalid float")
}
return sign * result, nil
}
func parseInt64(s string) (int64, error) {
var result int64
var sign int64 = 1
i := 0
if i < len(s) && s[i] == '-' {
sign = -1
i++
} else if i < len(s) && s[i] == '+' {
i++
}
if i >= len(s) {
return 0, fmt.Errorf("empty")
}
for i < len(s) {
if s[i] < '0' || s[i] > '9' {
return 0, fmt.Errorf("invalid int")
}
result = result*10 + int64(s[i]-'0')
i++
}
return sign * result, nil
}
// isSimpleIdent checks if string is a valid simple identifier.
func isSimpleIdent(s string) bool {
if len(s) == 0 {
return false
}
ch := s[0]
if !((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_') {
return false
}
for i := 1; i < len(s); i++ {
ch = s[i]
if !((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch == '_') {
return false
}
}
return true
}