From 827adeeb997bff8dea8daff472092c8a68a2a069 Mon Sep 17 00:00:00 2001 From: Charles KWON OhJun Date: Thu, 2 Apr 2026 15:33:07 +0900 Subject: [PATCH] feat: SET commands + ErrorBlock/Break error handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- docs/.pdca-status.json | 38 ++++++++-- hbrtl/error.go | 99 ++++++++++++++++++++++++++ hbrtl/missing.go | 20 +----- hbrtl/register.go | 18 +++++ hbrtl/setcmd.go | 158 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 310 insertions(+), 23 deletions(-) diff --git a/docs/.pdca-status.json b/docs/.pdca-status.json index 5d95426..7fc3873 100644 --- a/docs/.pdca-status.json +++ b/docs/.pdca-status.json @@ -1,6 +1,6 @@ { "version": "2.0", - "lastUpdated": "2026-04-02T06:00:35.695Z", + "lastUpdated": "2026-04-02T06:32:27.120Z", "activeFeatures": [ "hbrt", "hbrtl", @@ -46,9 +46,9 @@ "documents": {}, "timestamps": { "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": { "phase": "do", @@ -280,7 +280,7 @@ "session": { "startedAt": "2026-03-27T06:06:49.620Z", "onboardingCompleted": false, - "lastActivity": "2026-04-02T06:00:35.695Z" + "lastActivity": "2026-04-02T06:32:27.120Z" }, "history": [ { @@ -5952,6 +5952,36 @@ "feature": "hbrt", "phase": "do", "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" } ] } \ No newline at end of file diff --git a/hbrtl/error.go b/hbrtl/error.go index dfb522d..1737f3e 100644 --- a/hbrtl/error.go +++ b/hbrtl/error.go @@ -94,3 +94,102 @@ func FError(t *hbrt.Thread) { func SetFError(code int) { 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 +} diff --git a/hbrtl/missing.go b/hbrtl/missing.go index db1784e..9bf8fd6 100644 --- a/hbrtl/missing.go +++ b/hbrtl/missing.go @@ -276,25 +276,7 @@ func PCount(t *hbrt.Thread) { t.RetInt(int64(t.ParamCount())) } -// Break throws a recoverable error (for BEGIN SEQUENCE). -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}) -} +// Break moved to error.go — full implementation with BreakValue type. // Array creates array of given size. func ArrayFunc(t *hbrt.Thread) { diff --git a/hbrtl/register.go b/hbrtl/register.go index 5924342..c7a4771 100644 --- a/hbrtl/register.go +++ b/hbrtl/register.go @@ -244,6 +244,24 @@ func RegisterRTL(vm *hbrt.VM) { // SET commands 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 hbrt.Sym("FOPEN", hbrt.FsPublic, FOpen), diff --git a/hbrtl/setcmd.go b/hbrtl/setcmd.go index 245fe9b..773d7c3 100644 --- a/hbrtl/setcmd.go +++ b/hbrtl/setcmd.go @@ -114,3 +114,161 @@ func GetSetExact() bool { func GetSetSoftSeek() bool { 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() +}