Files modified (5): hbrt/macro.go — Replace hand-rolled parseFloat/parseInt64 with strconv (#50) Remove stale TODO, redundant TrimSpace hbrt/macroeval.go — Use strconv for literal parsing (was using removed functions) hbrt/class.go — CRITICAL #3: Change RWMutex to Mutex on classList Prevents slice reallocation race on concurrent GetClass hbrt/goroutine.go — #36: Channel double-close protection (sync.Once) #37: Send on closed channel recovery (defer/recover) Add IsClosed(), safe Receive (handles closed channel) hbrt/gobridge.go — Already clean (confirmed) hbrt/hbfunc.go — Already clean (confirmed) Issues resolved: #3 (CRITICAL), #36, #37 (MEDIUM), #50 (LOW) Total fixed: 16/53 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": "2.0",
|
||||
"lastUpdated": "2026-04-01T01:30:25.114Z",
|
||||
"lastUpdated": "2026-04-01T01:50:29.561Z",
|
||||
"activeFeatures": [
|
||||
"hbrt",
|
||||
"hbrtl",
|
||||
@@ -33,7 +33,7 @@
|
||||
"documents": {},
|
||||
"timestamps": {
|
||||
"started": "2026-03-27T09:33:04.512Z",
|
||||
"lastUpdated": "2026-04-01T01:30:25.114Z"
|
||||
"lastUpdated": "2026-04-01T01:50:29.560Z"
|
||||
},
|
||||
"lastFile": "/mnt/d/charles/five/hbrt/macroeval.go"
|
||||
},
|
||||
@@ -280,7 +280,7 @@
|
||||
"session": {
|
||||
"startedAt": "2026-03-27T06:06:49.620Z",
|
||||
"onboardingCompleted": false,
|
||||
"lastActivity": "2026-04-01T01:30:25.114Z"
|
||||
"lastActivity": "2026-04-01T01:50:29.561Z"
|
||||
},
|
||||
"history": [
|
||||
{
|
||||
@@ -5568,6 +5568,66 @@
|
||||
"feature": "hbrt",
|
||||
"phase": "do",
|
||||
"action": "updated"
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-04-01T01:36:41.982Z",
|
||||
"feature": "hbrt",
|
||||
"phase": "do",
|
||||
"action": "updated"
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-04-01T01:36:51.948Z",
|
||||
"feature": "hbrt",
|
||||
"phase": "do",
|
||||
"action": "updated"
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-04-01T01:37:05.337Z",
|
||||
"feature": "hbrt",
|
||||
"phase": "do",
|
||||
"action": "updated"
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-04-01T01:39:24.247Z",
|
||||
"feature": "hbrt",
|
||||
"phase": "do",
|
||||
"action": "updated"
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-04-01T01:40:12.339Z",
|
||||
"feature": "hbrt",
|
||||
"phase": "do",
|
||||
"action": "updated"
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-04-01T01:40:25.832Z",
|
||||
"feature": "hbrt",
|
||||
"phase": "do",
|
||||
"action": "updated"
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-04-01T01:42:45.339Z",
|
||||
"feature": "hbrt",
|
||||
"phase": "do",
|
||||
"action": "updated"
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-04-01T01:43:04.161Z",
|
||||
"feature": "hbrt",
|
||||
"phase": "do",
|
||||
"action": "updated"
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-04-01T01:50:06.804Z",
|
||||
"feature": "hbrt",
|
||||
"phase": "do",
|
||||
"action": "updated"
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-04-01T01:50:29.561Z",
|
||||
"feature": "hbrt",
|
||||
"phase": "do",
|
||||
"action": "updated"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -76,7 +76,7 @@ const (
|
||||
// --- Class Registry ---
|
||||
|
||||
var (
|
||||
classRegMu sync.RWMutex
|
||||
classRegMu sync.Mutex // full mutex (not RW) — prevents slice reallocation race on classList
|
||||
classReg = map[string]*ClassDef{}
|
||||
classList []*ClassDef // index = classID - 1
|
||||
)
|
||||
@@ -102,15 +102,15 @@ func RegisterClass(cls *ClassDef) uint16 {
|
||||
|
||||
// FindClass looks up a class by name.
|
||||
func FindClass(name string) *ClassDef {
|
||||
classRegMu.RLock()
|
||||
defer classRegMu.RUnlock()
|
||||
classRegMu.Lock()
|
||||
defer classRegMu.Unlock()
|
||||
return classReg[strings.ToUpper(name)]
|
||||
}
|
||||
|
||||
// GetClass looks up a class by ID.
|
||||
func GetClass(id uint16) *ClassDef {
|
||||
classRegMu.RLock()
|
||||
defer classRegMu.RUnlock()
|
||||
classRegMu.Lock()
|
||||
defer classRegMu.Unlock()
|
||||
if int(id) > 0 && int(id) <= len(classList) {
|
||||
return classList[id-1]
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ import (
|
||||
// HbChannel wraps Go's chan Value for use in PRG code.
|
||||
type HbChannel struct {
|
||||
Ch chan Value
|
||||
closeOnce sync.Once
|
||||
closed bool
|
||||
}
|
||||
|
||||
// MakeChannel creates a channel Value with optional buffer size.
|
||||
@@ -36,29 +38,49 @@ func (v Value) AsChannel() *HbChannel {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Send sends a value into the channel.
|
||||
// Send sends a value into the channel. Safe if channel is closed (recovers panic).
|
||||
func (ch *HbChannel) Send(val Value) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
// send on closed channel — silently ignore
|
||||
}
|
||||
}()
|
||||
ch.Ch <- val
|
||||
}
|
||||
|
||||
// Receive receives a value from the channel.
|
||||
func (ch *HbChannel) Receive() Value {
|
||||
return <-ch.Ch
|
||||
v, ok := <-ch.Ch
|
||||
if !ok {
|
||||
return MakeNil() // channel closed
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// TryReceive attempts non-blocking receive. Returns (value, true) or (nil, false).
|
||||
func (ch *HbChannel) TryReceive() (Value, bool) {
|
||||
select {
|
||||
case v := <-ch.Ch:
|
||||
case v, ok := <-ch.Ch:
|
||||
if !ok {
|
||||
return MakeNil(), false
|
||||
}
|
||||
return v, true
|
||||
default:
|
||||
return MakeNil(), false
|
||||
}
|
||||
}
|
||||
|
||||
// Close closes the channel.
|
||||
// Close closes the channel. Safe to call multiple times.
|
||||
func (ch *HbChannel) Close() {
|
||||
ch.closeOnce.Do(func() {
|
||||
ch.closed = true
|
||||
close(ch.Ch)
|
||||
})
|
||||
}
|
||||
|
||||
// IsClosed returns true if the channel has been closed.
|
||||
func (ch *HbChannel) IsClosed() bool {
|
||||
return ch.closed
|
||||
}
|
||||
|
||||
// --- WaitGroup ---
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
package hbrt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -39,9 +39,7 @@ func (t *Thread) MacroCompile(expr string) Value {
|
||||
// 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
|
||||
// Try calling as a function (memvar lookup deferred to MacroEval)
|
||||
sym := t.vm.FindSymbol(strings.ToUpper(expr))
|
||||
if sym != nil && sym.Func != nil {
|
||||
t.PushSymbol(sym)
|
||||
@@ -56,15 +54,14 @@ func (t *Thread) MacroCompile(expr string) Value {
|
||||
// Full runtime expression parser would be needed for complete macro support.
|
||||
// This handles common patterns: &("literal"), &(numericExpr)
|
||||
|
||||
// Try numeric
|
||||
expr = strings.TrimSpace(expr)
|
||||
// Try numeric (use stdlib strconv)
|
||||
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 {
|
||||
if f, err := strconv.ParseFloat(expr, 64); err == nil {
|
||||
return MakeDoubleAuto(f)
|
||||
}
|
||||
} else {
|
||||
if n, err := parseInt64(expr); err == nil {
|
||||
if n, err := strconv.ParseInt(expr, 10, 64); err == nil {
|
||||
return MakeNumInt(n)
|
||||
}
|
||||
}
|
||||
@@ -96,57 +93,7 @@ func (t *Thread) MacroPush() {
|
||||
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
|
||||
}
|
||||
// parseFloat and parseInt64 removed — using strconv.ParseFloat/ParseInt instead.
|
||||
|
||||
// isSimpleIdent checks if string is a valid simple identifier.
|
||||
func isSimpleIdent(s string) bool {
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"five/compiler/ast"
|
||||
"five/compiler/parser"
|
||||
"five/compiler/token"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -176,13 +177,13 @@ func (t *Thread) evalLiteral(e *ast.LiteralExpr) Value {
|
||||
case token.FALSE:
|
||||
return MakeBool(false)
|
||||
case token.INT:
|
||||
n, _ := parseInt64(e.Value)
|
||||
n, _ := strconv.ParseInt(e.Value, 10, 64)
|
||||
return MakeNumInt(n)
|
||||
case token.LONG:
|
||||
n, _ := parseInt64(e.Value)
|
||||
n, _ := strconv.ParseInt(e.Value, 10, 64)
|
||||
return MakeLong(n)
|
||||
case token.DOUBLE:
|
||||
f, _ := parseFloat(e.Value)
|
||||
f, _ := strconv.ParseFloat(e.Value, 64)
|
||||
return MakeDoubleAuto(f)
|
||||
case token.STRING:
|
||||
return MakeString(e.Value)
|
||||
|
||||
Reference in New Issue
Block a user