// 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 } // GetValue returns the Break() argument. Used by RECOVER USING via duck typing // (the generated code checks for a `hasValue` interface to avoid import cycles). func (bv BreakValue) GetValue() hbrt.Value { return bv.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 }