Files
five/hbrtl/error.go
Charles KWON OhJun 827adeeb99 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>
2026-04-02 15:33:07 +09:00

196 lines
5.1 KiB
Go

// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com)
// All rights reserved.
// Error handling functions: ERRORBLOCK, ERRORNEW, DOSERROR, FERROR
// Harbour error system: Error object + ErrorBlock callback chain.
package hbrtl
import (
"five/hbrt"
)
var (
errorBlock hbrt.Value // current error handler block
lastDosErr int // last OS error code
lastFErr int // last file error code
)
// ERRORBLOCK([bNewBlock]) → bOldBlock
// Gets/sets the error handler code block.
func ErrorBlock(t *hbrt.Thread) {
nParams := t.ParamCount()
t.Frame(nParams, 0)
defer t.EndProc()
old := errorBlock
if old.IsNil() {
old = hbrt.MakeNil()
}
if nParams >= 1 && !t.Local(1).IsNil() {
errorBlock = t.Local(1)
}
t.RetVal(old)
}
// ERRORNEW() → oError
// Creates a new Error object as a hash with standard Harbour error properties.
func ErrorNew(t *hbrt.Thread) {
t.Frame(0, 0)
defer t.EndProc()
keys := []hbrt.Value{
hbrt.MakeString("ARGS"), hbrt.MakeString("CANDEFAULT"),
hbrt.MakeString("CANRETRY"), hbrt.MakeString("CANSUBSTITUTE"),
hbrt.MakeString("CARGO"), hbrt.MakeString("DESCRIPTION"),
hbrt.MakeString("FILENAME"), hbrt.MakeString("GENCODE"),
hbrt.MakeString("OPERATION"), hbrt.MakeString("OSCODE"),
hbrt.MakeString("SEVERITY"), hbrt.MakeString("SUBCODE"),
hbrt.MakeString("SUBSYSTEM"), hbrt.MakeString("TRIES"),
}
vals := []hbrt.Value{
hbrt.MakeNil(), hbrt.MakeBool(false),
hbrt.MakeBool(false), hbrt.MakeBool(false),
hbrt.MakeNil(), hbrt.MakeString(""),
hbrt.MakeString(""), hbrt.MakeInt(0),
hbrt.MakeString(""), hbrt.MakeInt(0),
hbrt.MakeInt(2), hbrt.MakeInt(0),
hbrt.MakeString(""), hbrt.MakeInt(0),
}
h := &hbrt.HbHash{Keys: keys, Values: vals}
order := make([]int, len(keys))
for i := range order {
order[i] = i
}
h.Order = order
t.RetVal(hbrt.MakeHashFrom(h))
}
// DOSERROR([nNewCode]) → nOldCode
func DosError(t *hbrt.Thread) {
nParams := t.ParamCount()
t.Frame(nParams, 0)
defer t.EndProc()
old := lastDosErr
if nParams >= 1 && !t.Local(1).IsNil() {
lastDosErr = t.Local(1).AsInt()
}
t.RetInt(int64(old))
}
// FERROR() → nLastFileError
func FError(t *hbrt.Thread) {
t.Frame(0, 0)
defer t.EndProc()
t.RetInt(int64(lastFErr))
}
// SetFError sets the file error code (called internally by file I/O functions).
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
}