feat: SET commands + ErrorBlock/Break error handling

SET commands (setcmd.go):
  - SetDateFunc: __SetDateFormat([cNew]) → cOld
  - SetDecimalsFunc: SET DECIMALS TO n
  - SetEpochFunc: SET EPOCH TO n
  - 11 toggle functions: SetExact, SetDeleted, SetSoftSeek, SetExclusive,
    SetFixed, SetCancel, SetBell, SetConfirm, SetInsert, SetEscape, SetWrap
  - SET constants: _SET_EXACT, _SET_DELETED, etc. for PRG code
  - GetSetDateFormat(), GetSetDecimals(), GetSetEpoch() helpers
  - Default: DATE="mm/dd/yy", EPOCH=1900, DECIMALS=2

Error handling (error.go):
  - Break(xValue): panics with BreakValue, caught by BEGIN SEQUENCE
  - BreakBlock(): returns {|e| Break(e)} code block
  - LaunchError(): dispatches error through ErrorBlock handler
  - RuntimeError(): creates + launches standard runtime error
  - IsBreak(): checks if recovered panic is a BreakValue
  - createErrorHash(): builds Harbour-compatible error hash

Registration: ErrorBlock, ErrorNew, DosError, FError, Break + all SET functions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-02 15:33:07 +09:00
parent 2c812885c3
commit 827adeeb99
5 changed files with 310 additions and 23 deletions

View File

@@ -1,6 +1,6 @@
{ {
"version": "2.0", "version": "2.0",
"lastUpdated": "2026-04-02T06:00:35.695Z", "lastUpdated": "2026-04-02T06:32:27.120Z",
"activeFeatures": [ "activeFeatures": [
"hbrt", "hbrt",
"hbrtl", "hbrtl",
@@ -46,9 +46,9 @@
"documents": {}, "documents": {},
"timestamps": { "timestamps": {
"started": "2026-03-27T11:15:10.675Z", "started": "2026-03-27T11:15:10.675Z",
"lastUpdated": "2026-04-01T02:36:49.307Z" "lastUpdated": "2026-04-02T06:32:27.120Z"
}, },
"lastFile": "/mnt/d/charles/five/hbrtl/strings.go" "lastFile": "/mnt/d/charles/five/hbrtl/missing.go"
}, },
"tests": { "tests": {
"phase": "do", "phase": "do",
@@ -280,7 +280,7 @@
"session": { "session": {
"startedAt": "2026-03-27T06:06:49.620Z", "startedAt": "2026-03-27T06:06:49.620Z",
"onboardingCompleted": false, "onboardingCompleted": false,
"lastActivity": "2026-04-02T06:00:35.695Z" "lastActivity": "2026-04-02T06:32:27.120Z"
}, },
"history": [ "history": [
{ {
@@ -5952,6 +5952,36 @@
"feature": "hbrt", "feature": "hbrt",
"phase": "do", "phase": "do",
"action": "updated" "action": "updated"
},
{
"timestamp": "2026-04-02T06:27:16.050Z",
"feature": "hbrtl",
"phase": "do",
"action": "updated"
},
{
"timestamp": "2026-04-02T06:27:58.417Z",
"feature": "hbrtl",
"phase": "do",
"action": "updated"
},
{
"timestamp": "2026-04-02T06:29:57.154Z",
"feature": "hbrtl",
"phase": "do",
"action": "updated"
},
{
"timestamp": "2026-04-02T06:30:58.549Z",
"feature": "hbrtl",
"phase": "do",
"action": "updated"
},
{
"timestamp": "2026-04-02T06:32:27.120Z",
"feature": "hbrtl",
"phase": "do",
"action": "updated"
} }
] ]
} }

View File

@@ -94,3 +94,102 @@ func FError(t *hbrt.Thread) {
func SetFError(code int) { func SetFError(code int) {
lastFErr = code lastFErr = code
} }
// --- Break / Error dispatch ---
// BreakValue is a special panic value for Break().
// Harbour: HB_BREAK — unwinds to nearest BEGIN SEQUENCE.
type BreakValue struct {
Value hbrt.Value
}
// Break(xValue) → panics with BreakValue, caught by BEGIN SEQUENCE/RECOVER.
func Break(t *hbrt.Thread) {
nParams := t.ParamCount()
t.Frame(nParams, 0)
// Don't defer EndProc — we're panicking
var val hbrt.Value
if nParams >= 1 {
val = t.Local(1)
} else {
val = hbrt.MakeNil()
}
panic(BreakValue{Value: val})
}
// BreakBlock returns a block that calls Break(). Harbour: {|e| Break(e)}
func BreakBlock(t *hbrt.Thread) {
t.Frame(0, 0)
defer t.EndProc()
t.PushBlock(func(bt *hbrt.Thread) {
bt.Frame(1, 0)
val := bt.Local(1)
panic(BreakValue{Value: val})
}, 0)
t.RetValue()
}
// LaunchError creates and dispatches an error through ErrorBlock.
// Harbour: hb_errLaunch — calls error handler, returns action.
// Actions: 0=default, 1=retry, 0xFFFF=break
func LaunchError(t *hbrt.Thread, oErr hbrt.Value) hbrt.Value {
if errorBlock.IsNil() || !errorBlock.IsBlock() {
// No error handler — Break with error object
panic(BreakValue{Value: oErr})
}
// Call error handler block: errorBlock:Eval(oErr)
blk := errorBlock.AsBlock()
t.PushValue(oErr)
t.PendingParams2(1)
blk.Fn(t)
return t.Pop2()
}
// RuntimeError creates and launches a runtime error.
// Harbour: hb_errRT_BASE
func RuntimeError(t *hbrt.Thread, subSystem string, genCode, subCode int,
description, operation string) {
// Create error object
oErr := createErrorHash(subSystem, genCode, subCode, description, operation)
LaunchError(t, oErr)
}
func createErrorHash(subSystem string, genCode, subCode int,
description, operation string) hbrt.Value {
h := &hbrt.HbHash{}
addKV := func(k string, v hbrt.Value) {
h.Keys = append(h.Keys, hbrt.MakeString(k))
h.Values = append(h.Values, v)
}
addKV("SUBSYSTEM", hbrt.MakeString(subSystem))
addKV("GENCODE", hbrt.MakeInt(genCode))
addKV("SUBCODE", hbrt.MakeInt(subCode))
addKV("DESCRIPTION", hbrt.MakeString(description))
addKV("OPERATION", hbrt.MakeString(operation))
addKV("SEVERITY", hbrt.MakeInt(2)) // ES_ERROR
addKV("CANRETRY", hbrt.MakeBool(false))
addKV("CANDEFAULT", hbrt.MakeBool(false))
addKV("CANSUBSTITUTE", hbrt.MakeBool(false))
addKV("TRIES", hbrt.MakeInt(0))
addKV("CARGO", hbrt.MakeNil())
addKV("ARGS", hbrt.MakeNil())
addKV("FILENAME", hbrt.MakeString(""))
addKV("OSCODE", hbrt.MakeInt(0))
return hbrt.MakeHashFrom(h)
}
// GetErrorBlock returns the current error block (for internal use).
func GetErrorBlock() hbrt.Value {
return errorBlock
}
// IsBreak checks if a recovered panic is a BreakValue.
func IsBreak(r interface{}) (hbrt.Value, bool) {
if bv, ok := r.(BreakValue); ok {
return bv.Value, true
}
return hbrt.MakeNil(), false
}

View File

@@ -276,25 +276,7 @@ func PCount(t *hbrt.Thread) {
t.RetInt(int64(t.ParamCount())) t.RetInt(int64(t.ParamCount()))
} }
// Break throws a recoverable error (for BEGIN SEQUENCE). // Break moved to error.go — full implementation with BreakValue type.
func Break(t *hbrt.Thread) {
nParams := t.ParamCount()
t.Frame(nParams, 0)
var desc string
if nParams > 0 {
v := t.Local(1)
if v.IsString() {
desc = v.AsString()
} else {
desc = "BREAK"
}
} else {
desc = "BREAK"
}
// Clean up our own frame before panicking
t.EndProcNoRecover()
panic(&hbrt.HbError{Description: desc, GenCode: 0})
}
// Array creates array of given size. // Array creates array of given size.
func ArrayFunc(t *hbrt.Thread) { func ArrayFunc(t *hbrt.Thread) {

View File

@@ -244,6 +244,24 @@ func RegisterRTL(vm *hbrt.VM) {
// SET commands // SET commands
hbrt.Sym("SET", hbrt.FsPublic, SetFunc), hbrt.Sym("SET", hbrt.FsPublic, SetFunc),
hbrt.Sym("__SETDATEFORMAT", hbrt.FsPublic, SetDateFunc),
hbrt.Sym("__SETDECIMALS", hbrt.FsPublic, SetDecimalsFunc),
hbrt.Sym("__SETEPOCH", hbrt.FsPublic, SetEpochFunc),
// SET constants
hbrt.Sym("_SET_EXACT", hbrt.FsPublic, SetConstExact),
hbrt.Sym("_SET_DELETED", hbrt.FsPublic, SetConstDeleted),
hbrt.Sym("_SET_SOFTSEEK", hbrt.FsPublic, SetConstSoftSeek),
hbrt.Sym("_SET_EXCLUSIVE", hbrt.FsPublic, SetConstExclusive),
hbrt.Sym("_SET_DATEFORMAT", hbrt.FsPublic, SetConstDateFmt),
hbrt.Sym("_SET_DECIMALS", hbrt.FsPublic, SetConstDecimals),
hbrt.Sym("_SET_EPOCH", hbrt.FsPublic, SetConstEpoch),
// Error handling
hbrt.Sym("ERRORBLOCK", hbrt.FsPublic, ErrorBlock),
hbrt.Sym("ERRORNEW", hbrt.FsPublic, ErrorNew),
hbrt.Sym("DOSERROR", hbrt.FsPublic, DosError),
hbrt.Sym("FERROR", hbrt.FsPublic, FError),
hbrt.Sym("BREAK", hbrt.FsPublic, Break),
// File I/O // File I/O
hbrt.Sym("FOPEN", hbrt.FsPublic, FOpen), hbrt.Sym("FOPEN", hbrt.FsPublic, FOpen),

View File

@@ -114,3 +114,161 @@ func GetSetExact() bool {
func GetSetSoftSeek() bool { func GetSetSoftSeek() bool {
return GetSetting(SetSoftSeek).AsBool() return GetSetting(SetSoftSeek).AsBool()
} }
// GetSetDateFormat returns SET DATE format string.
func GetSetDateFormat() string {
v := GetSetting(SetDateFmt)
if v.IsString() {
return v.AsString()
}
return "mm/dd/yy"
}
// GetSetDecimals returns SET DECIMALS value.
func GetSetDecimals() int {
return GetSetting(SetDecimals).AsInt()
}
// GetSetEpoch returns SET EPOCH year.
func GetSetEpoch() int {
v := GetSetting(SetEpoch)
if v.IsNumeric() {
return v.AsInt()
}
return 1900
}
// --- Dedicated SET toggle functions ---
// Harbour: SET EXACT ON/OFF, SET DELETED ON/OFF, etc.
// SetExactFunc: SET(_SET_EXACT [, lNew]) → lOld
func SetExactFunc(t *hbrt.Thread) { setToggle(t, SetExact) }
// SetDeletedFunc: SET(_SET_DELETED [, lNew]) → lOld
func SetDeletedFunc(t *hbrt.Thread) { setToggle(t, SetDeleted) }
// SetSoftSeekFunc: SET(_SET_SOFTSEEK [, lNew]) → lOld
func SetSoftSeekFunc(t *hbrt.Thread) { setToggle(t, SetSoftSeek) }
// SetExclusiveFunc: SET(_SET_EXCLUSIVE [, lNew]) → lOld
func SetExclusiveFunc(t *hbrt.Thread) { setToggle(t, SetExclusive) }
// SetFixedFunc: SET(_SET_FIXED [, lNew]) → lOld
func SetFixedFunc(t *hbrt.Thread) { setToggle(t, SetFixed) }
// SetCancelFunc: SET(_SET_CANCEL [, lNew]) → lOld
func SetCancelFunc(t *hbrt.Thread) { setToggle(t, SetCancel) }
// SetBellFunc: SET(_SET_BELL [, lNew]) → lOld
func SetBellFunc(t *hbrt.Thread) { setToggle(t, SetBell) }
// SetConfirmFunc: SET(_SET_CONFIRM [, lNew]) → lOld
func SetConfirmFunc(t *hbrt.Thread) { setToggle(t, SetConfirm) }
// SetInsertFunc: SET(_SET_INSERT [, lNew]) → lOld
func SetInsertFunc(t *hbrt.Thread) { setToggle(t, SetInsert) }
// SetEscapeFunc: SET(_SET_ESCAPE [, lNew]) → lOld
func SetEscapeFunc(t *hbrt.Thread) { setToggle(t, SetEscape) }
// SetWrapFunc: SET(_SET_WRAP [, lNew]) → lOld
func SetWrapFunc(t *hbrt.Thread) { setToggle(t, SetWrap) }
// setToggle implements SET(n, lNew) → lOld for boolean settings
func setToggle(t *hbrt.Thread, setID int) {
nParams := t.ParamCount()
t.Frame(nParams, 0)
defer t.EndProc()
setMu.Lock()
defer setMu.Unlock()
old, ok := settings[setID]
if !ok {
old = hbrt.MakeBool(false)
}
if nParams >= 1 && !t.Local(1).IsNil() {
settings[setID] = t.Local(1)
}
t.RetVal(old)
}
// SetDateFunc: __SetDateFormat([cNew]) → cOld
func SetDateFunc(t *hbrt.Thread) {
nParams := t.ParamCount()
t.Frame(nParams, 0)
defer t.EndProc()
setMu.Lock()
defer setMu.Unlock()
old, ok := settings[SetDateFmt]
if !ok {
old = hbrt.MakeString("mm/dd/yy")
}
if nParams >= 1 && t.Local(1).IsString() {
dfmt := t.Local(1).AsString()
settings[SetDateFmt] = hbrt.MakeString(dfmt)
}
t.RetVal(old)
}
// SetDecimalsFunc: SET DECIMALS TO n
func SetDecimalsFunc(t *hbrt.Thread) {
nParams := t.ParamCount()
t.Frame(nParams, 0)
defer t.EndProc()
setMu.Lock()
defer setMu.Unlock()
old := settings[SetDecimals]
if nParams >= 1 && t.Local(1).IsNumeric() {
settings[SetDecimals] = t.Local(1)
}
t.RetVal(old)
}
// SetEpochFunc: SET EPOCH TO n
func SetEpochFunc(t *hbrt.Thread) {
nParams := t.ParamCount()
t.Frame(nParams, 0)
defer t.EndProc()
setMu.Lock()
defer setMu.Unlock()
old, ok := settings[SetEpoch]
if !ok {
old = hbrt.MakeInt(1900)
}
if nParams >= 1 && t.Local(1).IsNumeric() {
settings[SetEpoch] = t.Local(1)
}
t.RetVal(old)
}
// --- SET constants for PRG code (registered as RTL functions) ---
// _SET_EXACT etc. — return the SET index constant
func SetConstExact(t *hbrt.Thread) { t.Frame(0, 0); defer t.EndProc(); t.PushInt(SetExact); t.RetValue() }
func SetConstDeleted(t *hbrt.Thread) { t.Frame(0, 0); defer t.EndProc(); t.PushInt(SetDeleted); t.RetValue() }
func SetConstSoftSeek(t *hbrt.Thread) { t.Frame(0, 0); defer t.EndProc(); t.PushInt(SetSoftSeek); t.RetValue() }
func SetConstExclusive(t *hbrt.Thread) { t.Frame(0, 0); defer t.EndProc(); t.PushInt(SetExclusive); t.RetValue() }
func SetConstDateFmt(t *hbrt.Thread) { t.Frame(0, 0); defer t.EndProc(); t.PushInt(SetDateFmt); t.RetValue() }
func SetConstDecimals(t *hbrt.Thread) { t.Frame(0, 0); defer t.EndProc(); t.PushInt(SetDecimals); t.RetValue() }
func SetConstEpoch(t *hbrt.Thread) { t.Frame(0, 0); defer t.EndProc(); t.PushInt(SetEpoch); t.RetValue() }
// init registers default DATE format and EPOCH
func init() {
setMu.Lock()
if _, ok := settings[SetDateFmt]; !ok {
settings[SetDateFmt] = hbrt.MakeString("mm/dd/yy")
}
if _, ok := settings[SetEpoch]; !ok {
settings[SetEpoch] = hbrt.MakeInt(1900)
}
setMu.Unlock()
}