reworking of errors package

removed all messages from errors package. the strings are now hard coded
in place. the original reason for extracting the error strings like that
was (a) for redundancy and (b) for localisation possibilities. However,
in reality there is not much redundancy and since the project started
the scope of localisation is much larger (particularly due to the
addition of a GUI)

this is the first step. the next step is to remove the errors package
altogether. recent additions to Go mean that the functionality of the
errors package is no longer required. moreover, the original idea for
the errors package functions turns out not to have been as useful as
first appeared (the Error() functionality which makes sure no repeating
sub-strings occur is probably better achieved with static tooling).

one idea that comes from this which is quite interesting is the idea of
a curated error. that is, any error that has been "wrapped" as some
'generic' type. the IsAny() function in the errors package effectively
serves this purpose. we use that in one place in the input loop of the
debugger. not strictly necessary but nice to have so it would ideal
if we could remove the errors package but keep that idea.
This commit is contained in:
JetSetIlly 2020-09-27 08:43:45 +01:00
parent 3c7ea1953c
commit 2d5cae17f5
100 changed files with 672 additions and 754 deletions

View file

@ -174,7 +174,7 @@ func (cl *Loader) Load() error {
url, err := url.Parse(cl.Filename)
if err != nil {
return errors.New(errors.CartridgeLoader, err)
return errors.Errorf("cartridgeloader: %v", err)
}
switch url.Scheme {
@ -183,13 +183,13 @@ func (cl *Loader) Load() error {
case "https":
resp, err := http.Get(cl.Filename)
if err != nil {
return errors.New(errors.CartridgeLoader, err)
return errors.Errorf("cartridgeloader: %v", err)
}
defer resp.Body.Close()
cl.Data, err = ioutil.ReadAll(resp.Body)
if err != nil {
return errors.New(errors.CartridgeLoader, err)
return errors.Errorf("cartridgeloader: %v", err)
}
case "file":
@ -198,7 +198,7 @@ func (cl *Loader) Load() error {
case "":
f, err := os.Open(cl.Filename)
if err != nil {
return errors.New(errors.CartridgeLoader, err)
return errors.Errorf("cartridgeloader: %v", err)
}
defer f.Close()
@ -206,18 +206,18 @@ func (cl *Loader) Load() error {
// windows version (when running under wine) does not handle that
cfi, err := os.Stat(cl.Filename)
if err != nil {
return errors.New(errors.CartridgeLoader, err)
return errors.Errorf("cartridgeloader: %v", err)
}
size := cfi.Size()
cl.Data = make([]byte, size)
_, err = f.Read(cl.Data)
if err != nil {
return errors.New(errors.CartridgeLoader, err)
return errors.Errorf("cartridgeloader: %v", err)
}
default:
return errors.New(errors.CartridgeLoader, fmt.Sprintf("unsupported URL scheme (%s)", url.Scheme))
return errors.Errorf("cartridgeloader: %v", fmt.Sprintf("unsupported URL scheme (%s)", url.Scheme))
}
// generate hash
@ -225,7 +225,7 @@ func (cl *Loader) Load() error {
// check for hash consistency
if cl.Hash != "" && cl.Hash != hash {
return errors.New(errors.CartridgeLoader, "unexpected hash value")
return errors.Errorf("cartridgeloader: %v", "unexpected hash value")
}
// not generated hash

View file

@ -101,8 +101,7 @@ func (db *Session) Add(ent Entry) error {
}
if key == maxEntries {
msg := fmt.Sprintf("%d maximum entries exceeded", maxEntries)
return errors.New(errors.DatabaseError, msg)
return errors.Errorf("database: maximum entries exceeded (max %d)", maxEntries)
}
db.entries[key] = ent
@ -115,11 +114,11 @@ func (db *Session) Add(ent Entry) error {
func (db *Session) Delete(key int) error {
ent, ok := db.entries[key]
if !ok {
return errors.New(errors.DatabaseKeyError, key)
return errors.Errorf("database: key not available (%s)", key)
}
if err := ent.CleanUp(); err != nil {
return errors.New(errors.DatabaseError, err)
return errors.Errorf("database: %v", err)
}
delete(db.entries, key)

View file

@ -50,7 +50,7 @@ type Entry interface {
func (db *Session) RegisterEntryType(id string, des Deserialiser) error {
if _, ok := db.entryTypes[id]; ok {
msg := fmt.Sprintf("trying to register a duplicate entry ID [%s]", id)
return errors.New(errors.DatabaseError, msg)
return errors.Errorf("database: %v", msg)
}
db.entryTypes[id] = des
return nil

View file

@ -80,7 +80,7 @@ func (db Session) SelectKeys(onSelect func(Entry) (bool, error), keys ...int) (E
}
if entry == nil {
return nil, errors.New(errors.DatabaseSelectEmpty)
return nil, errors.Errorf("database: select empty")
}
return entry, nil

View file

@ -16,7 +16,6 @@
package database
import (
"fmt"
"io"
"io/ioutil"
"os"
@ -26,6 +25,11 @@ import (
"github.com/jetsetilly/gopher2600/errors"
)
// Sentinal error returned when requested database is not available
const (
NotAvailable = "database: not available (%s)"
)
// Activity is used to specify the general activity of what will be occuring
// during the database session
type Activity int
@ -77,9 +81,9 @@ func StartSession(path string, activity Activity, init func(*Session) error) (*S
if err != nil {
switch err.(type) {
case *os.PathError:
return nil, errors.New(errors.DatabaseFileUnavailable, path)
return nil, errors.Errorf(NotAvailable, path)
}
return nil, errors.New(errors.DatabaseError, err)
return nil, errors.Errorf("databas: %v", err)
}
// closing of db.dbfile requires a call to endSession()
@ -102,7 +106,7 @@ func (db *Session) EndSession(commitChanges bool) error {
// write entries to database
if commitChanges {
if db.activity == ActivityReading {
return errors.New(errors.DatabaseError, "cannot commit to a read-only database")
return errors.Errorf("database: cannot commit to a read-only database")
}
err := db.dbfile.Truncate(0)
@ -164,7 +168,7 @@ func (db *Session) readDBFile() error {
buffer, err := ioutil.ReadAll(db.dbfile)
if err != nil {
return errors.New(errors.DatabaseError, err)
return errors.Errorf("database: %v", err)
}
// split entries
@ -181,26 +185,23 @@ func (db *Session) readDBFile() error {
key, err := strconv.Atoi(fields[leaderFieldKey])
if err != nil {
msg := fmt.Sprintf("invalid key (%s)", fields[leaderFieldKey])
return errors.New(errors.DatabaseReadError, msg, i+1)
return errors.Errorf("invalid key (%s) [line %d]", fields[leaderFieldKey], i+1)
}
if _, ok := db.entries[key]; ok {
msg := fmt.Sprintf("duplicate key (%v)", key)
return errors.New(errors.DatabaseReadError, msg, i+1)
return errors.Errorf("duplicate key (%s) [line %d]", key, i+1)
}
var ent Entry
deserialise, ok := db.entryTypes[fields[leaderFieldID]]
if !ok {
msg := fmt.Sprintf("unrecognised entry type [%s]", fields[leaderFieldID])
return errors.New(errors.DatabaseReadError, msg, i+1)
return errors.Errorf("unrecognised entry type (%s) [line %d]", fields[leaderFieldID], i+1)
}
ent, err = deserialise(strings.Split(fields[numLeaderFields], ","))
if err != nil {
return errors.New(errors.DatabaseReadError, err, i+1)
return errors.Errorf("%v [line %d]", err, i+1)
}
db.entries[key] = ent

View file

@ -164,12 +164,12 @@ func newBreakpoints(dbg *Debugger) (*breakpoints, error) {
bp.checkPcBreak, err = parseTarget(bp.dbg, commandline.TokeniseInput("PC"))
if err != nil {
return nil, errors.New(errors.BreakpointError, "fatality while setting up breakpoint parser")
return nil, errors.Errorf("breakpoint: this should not have failed: %v", err)
}
bp.checkBankBreak, err = parseTarget(bp.dbg, commandline.TokeniseInput("BANK"))
if err != nil {
return nil, errors.New(errors.BreakpointError, "fatality while setting up breakpoint parser")
return nil, errors.Errorf("breakpoint: this should not have failed: %v", err)
}
return bp, err
@ -183,7 +183,7 @@ func (bp *breakpoints) clear() {
// drop a specific breakpoint by position in list
func (bp *breakpoints) drop(num int) error {
if len(bp.breaks)-1 < num {
return errors.New(errors.CommandError, fmt.Sprintf("breakpoint #%d is not defined", num))
return errors.Errorf("breakpoint #%d is not defined", num)
}
h := bp.breaks[:num]
@ -253,7 +253,7 @@ func (bp *breakpoints) parseCommand(tokens *commandline.Tokens) error {
// something appropriate
tgt, err := parseTarget(bp.dbg, commandline.TokeniseInput("PC"))
if err != nil {
return errors.New(errors.BreakpointError, "fatality while setting up breakpoint parser")
return errors.Errorf("breakpoint: this should not have failed: %v", err)
}
// resolvedTarget keeps track of whether we have specified a target but not
@ -298,10 +298,10 @@ func (bp *breakpoints) parseCommand(tokens *commandline.Tokens) error {
case "false":
val = false
default:
err = errors.New(errors.CommandError, fmt.Sprintf("invalid value (%s) for target (%s)", tok, tgt.Label()))
err = errors.Errorf("invalid value (%s) for target (%s)", tok, tgt.Label)
}
default:
return errors.New(errors.CommandError, fmt.Sprintf("unsupported value type (%T) for target (%s)", tgt.TargetValue(), tgt.Label()))
return errors.Errorf("unsupported value type (%T) for target (%s)", tgt.TargetValue(), tgt.Label())
}
if err == nil {
@ -326,7 +326,7 @@ func (bp *breakpoints) parseCommand(tokens *commandline.Tokens) error {
} else {
// make sure we've not left a previous target dangling without a value
if !resolvedTarget {
return errors.New(errors.CommandError, err)
return errors.Errorf("%v", err)
}
// possibly switch composition mode
@ -344,7 +344,7 @@ func (bp *breakpoints) parseCommand(tokens *commandline.Tokens) error {
tokens.Unget()
tgt, err = parseTarget(bp.dbg, tokens)
if err != nil {
return errors.New(errors.CommandError, err)
return errors.Errorf("%v", err)
}
resolvedTarget = false
@ -355,7 +355,7 @@ func (bp *breakpoints) parseCommand(tokens *commandline.Tokens) error {
}
if !resolvedTarget {
return errors.New(errors.CommandError, fmt.Sprintf("need a value (%T) to break on (%s)", tgt.TargetValue(), tgt.Label()))
return errors.Errorf("need a value (%T) to break on (%s)", tgt.TargetValue(), tgt.Label)
}
for _, nb := range newBreaks {
@ -373,7 +373,7 @@ func (bp *breakpoints) parseCommand(tokens *commandline.Tokens) error {
}
if i := bp.checkBreaker(nb); i != noBreakEqualivalent {
return errors.New(errors.CommandError, fmt.Sprintf("already exists (%s)", bp.breaks[i]))
return errors.Errorf("already exists (%s)", bp.breaks[i])
}
bp.breaks = append(bp.breaks, nb)
}

View file

@ -107,7 +107,7 @@ func (dbg *Debugger) tokeniseCommand(cmd string, scribe bool, echo bool) (*comma
// fail when the tokens DO match the scriptUnsafe template (ie. when
// there is no err from the validate function)
if err == nil {
return nil, errors.New(errors.CommandError, fmt.Sprintf("'%s' is unsafe to use in scripts", tokens.String()))
return nil, errors.Errorf("'%s' is unsafe to use in scripts", tokens.String())
}
// record command if it auto is false (is not a result of an "auto" command
@ -141,7 +141,7 @@ func (dbg *Debugger) processTokens(tokens *commandline.Tokens) (bool, error) {
switch command {
default:
return false, errors.New(errors.CommandError, fmt.Sprintf("%s is not yet implemented", command))
return false, errors.Errorf("%s is not yet implemented", command)
case cmdHelp:
keyword, ok := tokens.Get()
@ -202,7 +202,7 @@ func (dbg *Debugger) processTokens(tokens *commandline.Tokens) (bool, error) {
tokens.Unget()
err := dbg.stepTraps.parseCommand(tokens)
if err != nil {
return false, errors.New(errors.CommandError, fmt.Sprintf("unknown step mode (%s)", mode))
return false, errors.Errorf("unknown step mode (%s)", mode)
}
dbg.runUntilHalt = true
}
@ -445,13 +445,10 @@ func (dbg *Debugger) processTokens(tokens *commandline.Tokens) (bool, error) {
default:
symbol := tok
table, symbol, address, err := dbg.Disasm.Symtable.SearchSymbol(symbol, symbols.UnspecifiedSymTable)
if err != nil {
if errors.Is(err, errors.SymbolUnknown) {
dbg.printLine(terminal.StyleFeedback, "%s -> not found", symbol)
return false, nil
}
return false, err
found, table, symbol, address := dbg.Disasm.Symtable.SearchSymbol(symbol, symbols.UnspecifiedSymTable)
if !found {
dbg.printLine(terminal.StyleFeedback, "%s -> not found", symbol)
return false, nil
}
option, ok := tokens.Get()
@ -866,7 +863,7 @@ func (dbg *Debugger) processTokens(tokens *commandline.Tokens) (bool, error) {
// mapAddress, even if it does seem redundant.
ai := dbg.dbgmem.mapAddress(a, false)
if ai == nil {
dbg.printLine(terminal.StyleError, errors.New(errors.UnpokeableAddress, a).Error())
dbg.printLine(terminal.StyleError, fmt.Sprintf(pokeError, a))
return false, nil
}
addr := ai.mappedAddress
@ -1011,12 +1008,12 @@ func (dbg *Debugger) processTokens(tokens *commandline.Tokens) (bool, error) {
case "SCALE":
scl, ok := tokens.Get()
if !ok {
return false, errors.New(errors.CommandError, fmt.Sprintf("value required for %s %s", cmdDisplay, action))
return false, errors.Errorf("value required for %s %s", cmdDisplay, action)
}
scale, err := strconv.ParseFloat(scl, 32)
if err != nil {
return false, errors.New(errors.CommandError, fmt.Sprintf("%s %s value not valid (%s)", cmdDisplay, action, scl))
return false, errors.Errorf("%s %s value not valid (%s)", cmdDisplay, action, scl)
}
err = dbg.scr.ReqFeature(gui.ReqSetScale, float32(scale))
@ -1063,8 +1060,8 @@ func (dbg *Debugger) processTokens(tokens *commandline.Tokens) (bool, error) {
}
if err != nil {
if errors.Is(err, errors.UnsupportedGUIRequest) {
return false, errors.New(errors.CommandError, fmt.Sprintf("display does not support feature %s", action))
if errors.Is(err, gui.UnsupportedGuiFeature) {
return false, errors.Errorf("display does not support feature %s", action)
}
return false, err
}
@ -1286,25 +1283,25 @@ func (dbg *Debugger) processTokens(tokens *commandline.Tokens) (bool, error) {
case cmdBreak:
err := dbg.breakpoints.parseCommand(tokens)
if err != nil {
return false, errors.New(errors.CommandError, err)
return false, errors.Errorf("%v", err)
}
case cmdTrap:
err := dbg.traps.parseCommand(tokens)
if err != nil {
return false, errors.New(errors.CommandError, err)
return false, errors.Errorf("%v", err)
}
case cmdWatch:
err := dbg.watches.parseCommand(tokens)
if err != nil {
return false, errors.New(errors.CommandError, err)
return false, errors.Errorf("%v", err)
}
case cmdTrace:
err := dbg.traces.parseCommand(tokens)
if err != nil {
return false, errors.New(errors.CommandError, err)
return false, errors.Errorf("%v", err)
}
case cmdList:
@ -1334,7 +1331,7 @@ func (dbg *Debugger) processTokens(tokens *commandline.Tokens) (bool, error) {
s, _ := tokens.Get()
num, err := strconv.Atoi(s)
if err != nil {
return false, errors.New(errors.CommandError, fmt.Sprintf("drop attribute must be a number (%s)", s))
return false, errors.Errorf("drop attribute must be a number (%s)", s)
}
drop = strings.ToUpper(drop)
@ -1406,21 +1403,21 @@ func (dbg *Debugger) processTokens(tokens *commandline.Tokens) (bool, error) {
case "LOAD":
err := dbg.Prefs.load()
if err != nil {
return false, errors.New(errors.CommandError, err)
return false, errors.Errorf("%v", err)
}
err = dbg.Disasm.Prefs.Load()
if err != nil {
return false, errors.New(errors.CommandError, err)
return false, errors.Errorf("%v", err)
}
case "SAVE":
err := dbg.Prefs.save()
if err != nil {
return false, errors.New(errors.CommandError, err)
return false, errors.Errorf("%v", err)
}
err = dbg.Disasm.Prefs.Save()
if err != nil {
return false, errors.New(errors.CommandError, err)
return false, errors.Errorf("%v", err)
}
}

View file

@ -29,6 +29,7 @@ import (
"github.com/jetsetilly/gopher2600/gui"
"github.com/jetsetilly/gopher2600/hardware"
"github.com/jetsetilly/gopher2600/hardware/cpu/execution"
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge"
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/banks"
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/plusrom"
@ -160,21 +161,21 @@ func NewDebugger(tv television.Television, scr gui.GUI, term terminal.Terminal,
// create a new VCS instance
dbg.VCS, err = hardware.NewVCS(dbg.tv)
if err != nil {
return nil, errors.New(errors.DebuggerError, err)
return nil, errors.Errorf("debugger: %v", err)
}
// replace player 1 port with savekey
if useSavekey {
err = dbg.VCS.RIOT.Ports.AttachPlayer(ports.Player1ID, savekey.NewSaveKey)
if err != nil {
return nil, errors.New(errors.DebuggerError, err)
return nil, errors.Errorf("debugger: %v", err)
}
}
// create a new disassembly instance
dbg.Disasm, err = disassembly.NewDisassembly()
if err != nil {
return nil, errors.New(errors.DebuggerError, err)
return nil, errors.Errorf("debugger: %v", err)
}
// set up debugging interface to memory. note that we're reaching deep into
@ -198,7 +199,7 @@ func NewDebugger(tv television.Television, scr gui.GUI, term terminal.Terminal,
// set up breakpoints/traps
dbg.breakpoints, err = newBreakpoints(dbg)
if err != nil {
return nil, errors.New(errors.DebuggerError, err)
return nil, errors.Errorf("debugger: %v", err)
}
dbg.traps = newTraps(dbg)
dbg.watches = newWatches(dbg)
@ -229,7 +230,7 @@ func NewDebugger(tv television.Television, scr gui.GUI, term terminal.Terminal,
// connect gui
err = scr.ReqFeature(gui.ReqSetEventChan, dbg.events.GuiEvents)
if err != nil {
return nil, errors.New(errors.DebuggerError, err)
return nil, errors.Errorf("debugger: %v", err)
}
// allocate memory for user input
@ -244,7 +245,7 @@ func NewDebugger(tv television.Television, scr gui.GUI, term terminal.Terminal,
// setup preferences and load from disk
dbg.Prefs, err = newPreferences(dbg)
if err != nil {
return nil, errors.New(errors.DebuggerError, err)
return nil, errors.Errorf("debugger: %v", err)
}
return dbg, nil
@ -255,13 +256,13 @@ func (dbg *Debugger) Start(initScript string, cartload cartridgeloader.Loader) e
// prepare user interface
err := dbg.term.Initialise()
if err != nil {
return errors.New(errors.DebuggerError, err)
return errors.Errorf("debugger: %v", err)
}
defer dbg.term.CleanUp()
err = dbg.loadCartridge(cartload)
if err != nil {
return errors.New(errors.DebuggerError, err)
return errors.Errorf("debugger: %v", err)
}
dbg.running = true
@ -274,7 +275,7 @@ func (dbg *Debugger) Start(initScript string, cartload cartridgeloader.Loader) e
err = dbg.inputLoop(scr, false)
if err != nil {
dbg.term.Silence(false)
return errors.New(errors.DebuggerError, err)
return errors.Errorf("debugger: %v", err)
}
dbg.term.Silence(false)
@ -292,7 +293,7 @@ func (dbg *Debugger) Start(initScript string, cartload cartridgeloader.Loader) e
// debugging session is to be terminated
err = dbg.inputLoop(dbg.term, false)
if err != nil {
return errors.New(errors.DebuggerError, err)
return errors.Errorf("debugger: %v", err)
}
return nil
@ -326,12 +327,12 @@ func (dbg *Debugger) loadCartridge(cartload cartridgeloader.Loader) error {
err := dbg.scr.ReqFeature(gui.ReqChangingCartridge, true)
if err != nil {
return errors.New(errors.DebuggerError, err)
return errors.Errorf("debugger: %v", err)
}
defer dbg.scr.ReqFeature(gui.ReqChangingCartridge, false)
err = setup.AttachCartridge(dbg.VCS, cartload)
if err != nil && !errors.Has(err, errors.CartridgeEjected) {
if err != nil && !errors.Has(err, cartridge.Ejected) {
return err
}

View file

@ -31,7 +31,7 @@ func (dbg *Debugger) guiEventHandler(ev gui.Event) error {
switch ev := ev.(type) {
case gui.EventQuit:
dbg.running = false
return errors.New(errors.UserInterrupt)
return errors.Errorf(terminal.UserInterrupt)
case gui.EventKeyboard:
var handled bool
@ -90,11 +90,6 @@ func (dbg *Debugger) guiEventHandler(ev gui.Event) error {
return err
}
// wrap error in GUIEventError
if err != nil {
err = errors.New(errors.GUIEventError, err)
}
return err
}

View file

@ -19,6 +19,7 @@ import (
"fmt"
"io"
"github.com/jetsetilly/gopher2600/debugger/script"
"github.com/jetsetilly/gopher2600/debugger/terminal"
"github.com/jetsetilly/gopher2600/disassembly"
"github.com/jetsetilly/gopher2600/errors"
@ -49,7 +50,7 @@ func (dbg *Debugger) inputLoop(inputter terminal.Input, videoCycle bool) error {
// to the FormatResult() call in the main dbg.running loop below.
dbg.lastResult, err = dbg.Disasm.FormatResult(dbg.lastBank, dbg.VCS.CPU.LastResult, disassembly.EntryLevelExecuted)
if err != nil {
return errors.New(errors.DebuggerError, err)
return err
}
// update debugger the same way for video quantum as for cpu quantum
@ -179,7 +180,7 @@ func (dbg *Debugger) inputLoop(inputter terminal.Input, videoCycle bool) error {
// pause tv when emulation has halted
err = dbg.scr.ReqFeature(gui.ReqSetPause, true)
if err != nil {
return errors.New(errors.DebuggerError, err)
return err
}
}
@ -204,44 +205,38 @@ func (dbg *Debugger) inputLoop(inputter terminal.Input, videoCycle bool) error {
switch err {
case io.EOF:
// treat EOF events the same as UserInterrupt events
err = errors.New(errors.UserInterrupt)
err = errors.Errorf(terminal.UserInterrupt)
default:
// the error is probably serious. exit input loop with
// err
return errors.New(errors.DebuggerError, err)
// the error is probably serious. exit input loop with err
return err
}
}
// we now know the we have an Atari Error so we can safely
// switch on the internal Errno
switch err.(errors.AtariError).Message {
// we now know the we have an project specific error
switch errors.Head(err) {
// user interrupts are triggered by the user (in a terminal
// environment, usually by pressing ctrl-c)
case errors.UserInterrupt:
case terminal.UserInterrupt:
dbg.handleInterrupt(inputter, inputLen)
// like UserInterrupt but with no confirmation stage
case errors.UserQuit:
case terminal.UserAbort:
dbg.running = false
// a script that is being run will usually end with a ScriptEnd
// error. in these instances we can say simply say so (using
// the error message) with a feedback style (not an error
// style)
case errors.ScriptEnd:
case script.ScriptEnd:
if !videoCycle {
dbg.printLine(terminal.StyleFeedback, err.Error())
}
return nil
// a GUI event has triggered an error
case errors.GUIEventError:
dbg.printLine(terminal.StyleError, err.Error())
// all other errors are passed upwards to the calling function
default:
return errors.New(errors.DebuggerError, err)
return err
}
}
@ -269,7 +264,7 @@ func (dbg *Debugger) inputLoop(inputter terminal.Input, videoCycle bool) error {
if !checkTerm && dbg.runUntilHalt {
err = dbg.scr.ReqFeature(gui.ReqSetPause, false)
if err != nil {
return errors.New(errors.DebuggerError, err)
return err
}
}
}
@ -295,7 +290,7 @@ func (dbg *Debugger) inputLoop(inputter terminal.Input, videoCycle bool) error {
case QuantumVideo:
stepErr = dbg.VCS.Step(vcsStepVideo)
default:
stepErr = errors.New(errors.DebuggerError, "unknown quantum mode")
stepErr = fmt.Errorf("unknown quantum mode")
}
// check step error. note that we format and store last CPU
@ -307,7 +302,7 @@ func (dbg *Debugger) inputLoop(inputter terminal.Input, videoCycle bool) error {
// format last execution result even on error
dbg.lastResult, err = dbg.Disasm.FormatResult(dbg.lastBank, dbg.VCS.CPU.LastResult, disassembly.EntryLevelExecuted)
if err != nil {
return errors.New(errors.DebuggerError, err)
return err
}
// the supercharger ROM will eventually start execution from the PC
@ -341,9 +336,9 @@ func (dbg *Debugger) inputLoop(inputter terminal.Input, videoCycle bool) error {
}
} else {
// exit input loop only if error is not an AtariError...
// exit input loop if error is a plain error
if !errors.IsAny(stepErr) {
return errors.New(errors.DebuggerError, stepErr)
return stepErr
}
// ...set lastStepError instead and allow emulation to halt
@ -366,7 +361,7 @@ func (dbg *Debugger) inputLoop(inputter terminal.Input, videoCycle bool) error {
// update entry and store result as last result
dbg.lastResult, err = dbg.Disasm.UpdateEntry(dbg.lastBank, dbg.VCS.CPU.LastResult, dbg.VCS.CPU.PC.Value())
if err != nil {
return errors.New(errors.DebuggerError, err)
return err
}
// check validity of instruction result
@ -375,7 +370,7 @@ func (dbg *Debugger) inputLoop(inputter terminal.Input, videoCycle bool) error {
if err != nil {
dbg.printLine(terminal.StyleError, "%s", dbg.VCS.CPU.LastResult.Defn)
dbg.printLine(terminal.StyleError, "%s", dbg.VCS.CPU.LastResult)
return errors.New(errors.DebuggerError, err)
return err
}
}
}
@ -422,7 +417,7 @@ func (dbg *Debugger) handleInterrupt(inputter terminal.Input, inputLen int) {
if err != nil {
// another UserInterrupt has occurred. we treat
// UserInterrupt as thought 'y' was pressed
if errors.Is(err, errors.UserInterrupt) {
if errors.Is(err, terminal.UserInterrupt) {
confirm[0] = 'y'
} else {
dbg.printLine(terminal.StyleError, err.Error())

View file

@ -22,6 +22,7 @@ import (
"github.com/jetsetilly/gopher2600/errors"
"github.com/jetsetilly/gopher2600/hardware/memory"
"github.com/jetsetilly/gopher2600/hardware/memory/bus"
"github.com/jetsetilly/gopher2600/hardware/memory/memorymap"
"github.com/jetsetilly/gopher2600/symbols"
)
@ -138,12 +139,18 @@ func (dbgmem memoryDebug) mapAddress(address interface{}, read bool) *addressInf
return ai
}
// poke/peek error formatting, for consistency
const (
pokeError = "cannot poke address (%v)"
peekError = "cannot peek address (%v)"
)
// Peek returns the contents of the memory address, without triggering any side
// effects. address can be expressed numerically or symbolically.
func (dbgmem memoryDebug) peek(address interface{}) (*addressInfo, error) {
ai := dbgmem.mapAddress(address, true)
if ai == nil {
return nil, errors.New(errors.DebuggerError, errors.New(errors.UnpeekableAddress, address))
return nil, errors.Errorf(peekError, address)
}
area := dbgmem.mem.GetArea(ai.area)
@ -151,12 +158,15 @@ func (dbgmem memoryDebug) peek(address interface{}) (*addressInfo, error) {
var err error
ai.data, err = area.Peek(ai.mappedAddress)
if err != nil {
return nil, errors.New(errors.DebuggerError, err)
if errors.Is(err, bus.AddressError) {
return nil, errors.Errorf(peekError, address)
}
return nil, err
}
ai.peeked = true
return ai, err
return ai, nil
}
// Poke writes a value at the specified address, which may be numeric or
@ -164,14 +174,17 @@ func (dbgmem memoryDebug) peek(address interface{}) (*addressInfo, error) {
func (dbgmem memoryDebug) poke(address interface{}, data uint8) (*addressInfo, error) {
ai := dbgmem.mapAddress(address, false)
if ai == nil {
return nil, errors.New(errors.DebuggerError, errors.New(errors.UnpokeableAddress, address))
return nil, errors.Errorf(pokeError, address)
}
area := dbgmem.mem.GetArea(ai.area)
err := area.Poke(ai.mappedAddress, data)
if err != nil {
return nil, errors.New(errors.DebuggerError, err)
if errors.Is(err, bus.AddressError) {
return nil, errors.Errorf(pokeError, address)
}
return nil, err
}
ai.data = data

View file

@ -45,24 +45,24 @@ func newPreferences(dbg *Debugger) (*Preferences, error) {
// setup preferences and load from disk
pth, err := paths.ResourcePath("", prefs.DefaultPrefsFile)
if err != nil {
return nil, errors.New(errors.DebuggerError, err)
return nil, err
}
p.dsk, err = prefs.NewDisk(pth)
if err != nil {
return nil, errors.New(errors.DebuggerError, err)
return nil, err
}
err = p.dsk.Add("debugger.randstate", p.RandomState)
if err != nil {
return nil, errors.New(errors.DebuggerError, err)
return nil, err
}
err = p.dsk.Add("debugger.randpins", p.RandomPins)
if err != nil {
return nil, errors.New(errors.DebuggerError, err)
return nil, err
}
err = p.dsk.Load(true)
if err != nil {
// ignore missing prefs file errors
if !errors.Is(err, errors.PrefsNoFile) {
if !errors.Is(err, prefs.NoPrefsFile) {
return nil, err
}
}

View file

@ -45,7 +45,7 @@ func RescribeScript(scriptfile string) (*Rescribe, error) {
// open script and defer closing
f, err := os.Open(scriptfile)
if err != nil {
return nil, errors.New(errors.ScriptFileUnavailable, err)
return nil, errors.Errorf("script: file not available: %v", err)
}
defer func() {
_ = f.Close()
@ -53,7 +53,7 @@ func RescribeScript(scriptfile string) (*Rescribe, error) {
buffer, err := ioutil.ReadAll(f)
if err != nil {
return nil, errors.New(errors.ScriptFileError, err)
return nil, errors.Errorf("script: %v", err)
}
scr := &Rescribe{scriptFile: scriptfile}
@ -83,10 +83,15 @@ func (scr *Rescribe) IsInteractive() bool {
return false
}
// Sentinal error returned when Rescribe.TermRead() reaches the expected end of the script
const (
ScriptEnd = "end of script (%s)"
)
// TermRead implements the terminal.Input interface
func (scr *Rescribe) TermRead(buffer []byte, _ terminal.Prompt, _ *terminal.ReadEvents) (int, error) {
if scr.lineCt > len(scr.lines)-1 {
return -1, errors.New(errors.ScriptEnd, scr.scriptFile)
return -1, errors.Errorf(ScriptEnd, scr.scriptFile)
}
n := len(scr.lines[scr.lineCt]) + 1

View file

@ -46,7 +46,7 @@ func (scr Scribe) IsActive() bool {
// StartSession a new script
func (scr *Scribe) StartSession(scriptfile string) error {
if scr.IsActive() {
return errors.New(errors.ScriptScribeError, "already active")
return errors.Errorf("script scribe already active")
}
scr.scriptfile = scriptfile
@ -55,10 +55,10 @@ func (scr *Scribe) StartSession(scriptfile string) error {
if os.IsNotExist(err) {
scr.file, err = os.Create(scriptfile)
if err != nil {
return errors.New(errors.ScriptScribeError, "cannot create new script file")
return errors.Errorf("cannot create new script file")
}
} else {
return errors.New(errors.ScriptScribeError, "file already exists")
return errors.Errorf("file already exists")
}
return nil
@ -84,9 +84,9 @@ func (scr *Scribe) EndSession() error {
// if commit() causes an error, continue with the Close() operation and
// return the commit() error if the close succeeds
errClose := scr.file.Close()
if errClose != nil {
return errors.New(errors.ScriptScribeError, errClose)
errb := scr.file.Close()
if errb != nil {
return errors.Errorf("script: scripe: %v", errb)
}
return err
@ -146,20 +146,20 @@ func (scr *Scribe) Commit() error {
if scr.inputLine != "" {
n, err := io.WriteString(scr.file, scr.inputLine)
if err != nil {
return errors.New(errors.ScriptScribeError, err)
return errors.Errorf("script: scribe: %v", err)
}
if n != len(scr.inputLine) {
return errors.New(errors.ScriptScribeError, "output truncated")
return errors.Errorf("script: scribe output truncated")
}
}
if scr.outputLine != "" {
n, err := io.WriteString(scr.file, scr.outputLine)
if err != nil {
return errors.New(errors.ScriptScribeError, err)
return errors.Errorf("script: scribe: %v", err)
}
if n != len(scr.outputLine) {
return errors.New(errors.ScriptScribeError, "output truncated")
return errors.Errorf("script: scribe output truncated")
}
}

View file

@ -215,14 +215,14 @@ func parseTarget(dbg *Debugger, tokens *commandline.Tokens) (*target, error) {
}
default:
return nil, errors.New(errors.InvalidTarget, fmt.Sprintf("%s %s", keyword, subkey))
return nil, errors.Errorf("invalid target: %s %s", keyword, subkey)
}
} else {
return nil, errors.New(errors.InvalidTarget, keyword)
return nil, errors.Errorf("invalid target: %s", keyword)
}
default:
return nil, errors.New(errors.InvalidTarget, keyword)
return nil, errors.Errorf("invalid target: %s", keyword)
}
}

View file

@ -103,7 +103,7 @@ func (ct *ColorTerminal) TermRead(input []byte, prompt terminal.Prompt, events *
// just return the UserInterrupt error and not worry about clearing
// the input line. see easyterm.KeyInterrupt for what happens
// normally.
return 0, errors.New(errors.UserInterrupt)
return 0, errors.Errorf(terminal.UserInterrupt)
case ev := <-events.GuiEvents:
// handle functions that are passsed on over interruptChannel. these can
@ -136,7 +136,7 @@ func (ct *ColorTerminal) TermRead(input []byte, prompt terminal.Prompt, events *
} else {
// there is no input so return UserInterrupt error
ct.EasyTerm.TermPrint("\r\n")
return 0, errors.New(errors.UserInterrupt)
return 0, errors.Errorf(terminal.UserInterrupt)
}
case easyterm.KeySuspend:

View file

@ -69,7 +69,7 @@ func (cmds Commands) String() string {
func (cmds *Commands) AddHelp(helpCommand string, helps map[string]string) error {
// if help command exists then there is nothing to do
if _, ok := cmds.Index[helpCommand]; ok {
return errors.New(errors.HelpError, fmt.Sprintf("%s: already defined", helpCommand))
return errors.Errorf("%s: already defined", helpCommand)
}
// keep reference to helps
@ -109,7 +109,7 @@ func (cmds *Commands) AddHelp(helpCommand string, helps map[string]string) error
// parse the constructed definition
p, d, err := parseDefinition(defn.String(), "")
if err != nil {
return errors.New(errors.HelpError, fmt.Sprintf("%s: %s (char %d)", helpCommand, err, d))
return errors.Errorf("%s: %s (char %d)", helpCommand, err, d)
}
// add parsed definition to list of commands

View file

@ -75,12 +75,12 @@ func ParseCommandTemplate(template []string) (*Commands, error) {
// parse the definition for this command
p, d, err := parseDefinition(defn, "")
if err != nil {
return nil, errors.New(errors.ParserError, fmt.Sprintf("%s [line %d, col %d]", err, t, d))
return nil, errors.Errorf("parser: %v", fmt.Errorf("%s [line %d, col %d]", err, t, d))
}
// check that parsing was complete
if d < len(defn)-1 {
return nil, errors.New(errors.ParserError, fmt.Sprintf("outstanding characters in definition [line %d, col %d]", t, d))
return nil, errors.Errorf("parser: %v", fmt.Errorf("outstanding characters in definition [line %d, col %d]", t, d))
}
// add to list of commands (order doesn't matter at this stage)

View file

@ -42,11 +42,7 @@ func (cmds Commands) ValidateTokens(tokens *Tokens) error {
err := cmds.cmds[n].validate(tokens, false)
if err != nil {
// preserve FormattedError type
if _, ok := err.(errors.AtariError); ok {
return err
}
return errors.New(errors.ValidationError, err)
return err
}
// if we've reached this point and there are still oustanding
@ -56,10 +52,10 @@ func (cmds Commands) ValidateTokens(tokens *Tokens) error {
// special handling for help command
if cmd == cmds.helpCommand {
return errors.New(errors.ValidationError, fmt.Sprintf("no help for %s", strings.ToUpper(arg)))
return errors.Errorf("no help for %s", strings.ToUpper(arg))
}
return errors.New(errors.ValidationError, fmt.Sprintf("unrecognised argument (%s) for %s", arg, cmd))
return errors.Errorf("unrecognised argument (%s) for %s", arg, cmd)
}
return nil
@ -80,7 +76,7 @@ func (n *node) validate(tokens *Tokens, speculative bool) error {
if !ok {
// we treat arguments in the root-group as though they are required
if n.typ == nodeRequired || n.typ == nodeRoot {
return fmt.Errorf("%s required", n.nodeVerbose())
return errors.Errorf("%s required", n.nodeVerbose())
}
return nil
}
@ -95,7 +91,8 @@ func (n *node) validate(tokens *Tokens, speculative bool) error {
// many entries) is an illegal node and should not have been parsed
if n.tag == "" {
if n.next == nil {
return errors.New(errors.PanicError, "commandline validation", "illegal empty node")
// this shouldn't ever happen. return a plain error if it does
return fmt.Errorf("commandline validation: illegal empty node")
}
// speculatively validate the next node. don't do anything with any
@ -216,7 +213,7 @@ func (n *node) validate(tokens *Tokens, speculative bool) error {
}
if !match {
err := fmt.Errorf("unrecognised argument (%s)", tok)
err := errors.Errorf("unrecognised argument (%s)", tok)
// there's still no match but the speculative flag means we were half
// expecting it. return error without further consideration

View file

@ -94,7 +94,7 @@ func (pt PlainTerminal) TermRead(input []byte, prompt terminal.Prompt, events *t
// error to the debugging loop
select {
case <-events.IntEvents:
return 0, errors.New(errors.UserInterrupt)
return 0, errors.Errorf(terminal.UserInterrupt)
default:
}

View file

@ -94,3 +94,9 @@ type TabCompletion interface {
type Broker interface {
GetTerminal() Terminal
}
// Sentinal errors
const (
UserInterrupt = "user interrupt"
UserAbort = "user abort"
)

View file

@ -16,7 +16,6 @@
package debugger
import (
"fmt"
"strings"
"github.com/jetsetilly/gopher2600/debugger/terminal"
@ -60,7 +59,7 @@ func (trc *traces) clear() {
// drop a specific tracer by a position in the list
func (trc *traces) drop(num int) error {
if len(trc.traces)-1 < num {
return errors.New(errors.CommandError, fmt.Sprintf("trace #%d is not defined", num))
return errors.Errorf("trace #%d is not defined", num)
}
h := trc.traces[:num]
@ -133,7 +132,7 @@ func (trc *traces) parseCommand(tokens *commandline.Tokens) error {
if ai == nil {
ai = trc.dbg.dbgmem.mapAddress(a, false)
if ai == nil {
return errors.New(errors.CommandError, fmt.Sprintf("invalid trace address: %s", a))
return errors.Errorf("invalid trace address: %s", a)
}
}
@ -142,7 +141,7 @@ func (trc *traces) parseCommand(tokens *commandline.Tokens) error {
// check to see if trace already exists
for _, t := range trc.traces {
if t.ai.address == nt.ai.address {
return errors.New(errors.CommandError, fmt.Sprintf("already being traced (%s)", t))
return errors.Errorf("already being traced (%s)", t)
}
}

View file

@ -57,7 +57,7 @@ func (tr *traps) clear() {
// drop the numbered trap from the list
func (tr *traps) drop(num int) error {
if len(tr.traps)-1 < num {
return errors.New(errors.CommandError, fmt.Sprintf("trap #%d is not defined", num))
return errors.Errorf("trap #%d is not defined", num)
}
h := tr.traps[:num]

View file

@ -78,7 +78,7 @@ func (wtc *watches) clear() {
// drop a specific watcher by a position in the list
func (wtc *watches) drop(num int) error {
if len(wtc.watches)-1 < num {
return errors.New(errors.CommandError, fmt.Sprintf("watch #%d is not defined", num))
return errors.Errorf("watch #%d is not defined", num)
}
h := wtc.watches[:num]
@ -216,7 +216,7 @@ func (wtc *watches) parseCommand(tokens *commandline.Tokens) error {
// mapping of the address was unsucessful
if ai == nil {
return errors.New(errors.CommandError, fmt.Sprintf("invalid watch address: %s", a))
return errors.Errorf("invalid watch address: %s", a)
}
// get value if possible
@ -226,7 +226,7 @@ func (wtc *watches) parseCommand(tokens *commandline.Tokens) error {
if useVal {
val, err = strconv.ParseUint(v, 0, 8)
if err != nil {
return errors.New(errors.CommandError, fmt.Sprintf("invalid watch value (%s)", a))
return errors.Errorf("invalid watch value (%s)", a)
}
}
@ -252,7 +252,7 @@ func (wtc *watches) parseCommand(tokens *commandline.Tokens) error {
w.ai.read == nw.ai.read &&
w.matchValue == nw.matchValue && w.value == nw.value {
return errors.New(errors.CommandError, fmt.Sprintf("already being watched (%s)", w))
return errors.Errorf("already being watched (%s)", w)
}
}

View file

@ -89,7 +89,7 @@ func (dig *Audio) flushAudio() error {
dig.digest = sha1.Sum(dig.buffer)
n := copy(dig.buffer, dig.digest[:])
if n != len(dig.digest) {
return errors.New(errors.AudioDigest, fmt.Sprintf("digest error while flushing audio stream"))
return errors.Errorf("digest: audio: %v", fmt.Sprintf("digest error while flushing audio stream"))
}
dig.bufferCt = audioBufferStart
return nil

View file

@ -99,7 +99,7 @@ func (dig *Video) NewFrame(frameNum int, _ bool) error {
// to the head of the video data
n := copy(dig.pixels, dig.digest[:])
if n != len(dig.digest) {
return errors.New(errors.VideoDigest, fmt.Sprintf("digest error during new frame"))
return errors.Errorf("digest: video: %v", fmt.Sprintf("digest error during new frame"))
}
dig.digest = sha1.Sum(dig.pixels)
dig.frameNum = frameNum

View file

@ -261,7 +261,7 @@ func (dsm *Disassembly) decode(mc *cpu.CPU, mem *disasmMemory) error {
mc.PC.Load(address)
err := mc.ExecuteInstruction(nil)
unimplementedInstruction := errors.Is(err, errors.UnimplementedInstruction)
unimplementedInstruction := errors.Is(err, cpu.UnimplementedInstruction)
// filter out the predictable errors
if err != nil && !unimplementedInstruction {
@ -297,12 +297,12 @@ func (dsm *Disassembly) decode(mc *cpu.CPU, mem *disasmMemory) error {
// sanity checks
if bankCt != dsm.cart.NumBanks() {
return errors.New(errors.DisasmError, "number of banks in disassembly is different to NumBanks()")
return errors.Errorf("disassembly: number of banks in disassembly is different to NumBanks()")
}
for b := range dsm.disasm {
for _, a := range dsm.disasm[b] {
if a == nil {
return errors.New(errors.DisasmError, "not every address has been decoded")
return errors.Errorf("disassembly: not every address has been decoded")
}
}
}

View file

@ -25,6 +25,7 @@ import (
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge"
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/banks"
"github.com/jetsetilly/gopher2600/hardware/memory/memorymap"
"github.com/jetsetilly/gopher2600/prefs"
"github.com/jetsetilly/gopher2600/symbols"
)
@ -70,8 +71,8 @@ func NewDisassembly() (*Disassembly, error) {
dsm.Prefs, err = newPreferences(dsm)
if err != nil {
if !errors.Is(err, errors.PrefsNoFile) {
return nil, errors.New(errors.DisasmError, err)
if !errors.Is(err, prefs.NoPrefsFile) {
return nil, errors.Errorf("disassembly: %v", err)
}
}
@ -95,12 +96,12 @@ func FromCartridge(cartload cartridgeloader.Loader) (*Disassembly, error) {
err = cart.Attach(cartload)
if err != nil {
return nil, errors.New(errors.DisasmError, err)
return nil, errors.Errorf("disassembly: %v", err)
}
err = dsm.FromMemory(cart, symtable)
if err != nil {
return nil, errors.New(errors.DisasmError, err)
return nil, errors.Errorf("disassembly: %v", err)
}
return dsm, nil
@ -164,7 +165,7 @@ func (dsm *Disassembly) fromMemory(startAddress ...uint16) error {
// create a new NoFlowControl CPU to help disassemble memory
mc, err := cpu.NewCPU(mem)
if err != nil {
return errors.New(errors.DisasmError, err)
return errors.Errorf("disassembly: %v", err)
}
mc.NoFlowControl = true
@ -177,7 +178,7 @@ func (dsm *Disassembly) fromMemory(startAddress ...uint16) error {
// disassemble cartridge binary
err = dsm.disassemble(mc, mem, startAddress...)
if err != nil {
return errors.New(errors.DisasmError, err)
return errors.Errorf("disassembly: %v", err)
}
return nil
@ -228,7 +229,7 @@ func (dsm *Disassembly) UpdateEntry(bank banks.Details, result execution.Result,
var err error
dsm.disasm[bank.Number][idx], err = dsm.formatResult(bank, result, EntryLevelExecuted)
if err != nil {
return nil, errors.New(errors.DisasmError, err)
return nil, errors.Errorf("disassembly: %v", err)
}
} else if e.Level < EntryLevelExecuted || e.UpdateActualOnExecute {

View file

@ -16,8 +16,6 @@
package disassembly
import (
"fmt"
"github.com/jetsetilly/gopher2600/errors"
)
@ -97,7 +95,7 @@ func (dsm *Disassembly) NewBankIteration(minLevel EntryLevel, bank int) (*Iterat
// banks than the current cartridge at the exact moment an illegal bank is
// being drawn by the sdlimgui disassembly window.
if bank >= len(dsm.disasm) {
return nil, 0, errors.New(errors.IterationError, fmt.Sprintf("no bank %d in disasm", bank))
return nil, 0, errors.Errorf("no bank %d in disasm", bank)
}
bitr := &IterateBank{
@ -110,7 +108,7 @@ func (dsm *Disassembly) NewBankIteration(minLevel EntryLevel, bank int) (*Iterat
count := 0
for _, a := range dsm.disasm[bank] {
if a == nil {
return nil, 0, errors.New(errors.IterationError, "disassembly not complete")
return nil, 0, errors.Errorf("disassembly not complete")
}
if a.Level >= minLevel {

View file

@ -44,7 +44,7 @@ func (dsm *Disassembly) Write(output io.Writer, attr WriteAttr) error {
// WriteBank writes the disassembly of the selected bank to io.Writer
func (dsm *Disassembly) WriteBank(output io.Writer, attr WriteAttr, bank int) error {
if bank < 0 || bank > len(dsm.disasm)-1 {
return errors.New(errors.DisasmError, fmt.Sprintf("no such bank (%d)", bank))
return errors.Errorf("disassembly: no such bank (%d)", bank)
}
output.Write([]byte(fmt.Sprintf("--- bank %d ---\n", bank)))

View file

@ -13,14 +13,20 @@
// You should have received a copy of the GNU General Public License
// along with Gopher2600. If not, see <https://www.gnu.org/licenses/>.
// Package errors is a helper package for the error type. It defines the
// AtariError type, an implementation of the error interface, that allows code
// to wrap errors around other errors and to allow normalised formatted output
// of error messages.
// Package errors is a helper package for the plain Go language error type. We
// think of these errors as curated errors. External to this package, curated
// errors are referenced as plain errors (ie. they implement the error
// interface).
//
// The most useful feature is deduplication of wrapped errors. This means that
// code does not need to worry about the immediate context of the function
// which creates the error. For instance:
// Internally, errors are thought of as being composed of parts, as described
// by The Go Programming Language (Donovan, Kernighan): "When the error is
// ultimately handled by the program's main function, it should provide a clear
// causal chain from the root of the problem to the overal failure".
//
// The Error() function implementation for curated errors ensures that this
// chain is normalised. Specifically, that the chain does not contain duplicate
// adjacent parts. The practical advantage of this is that it alleviates the
// problem of when and how to wrap errors. For example:
//
// func main() {
// err := A()
@ -32,7 +38,7 @@
// func A() error {
// err := B()
// if err != nil {
// return errors.New(errors.DebuggerError, err)
// return errors.Errorf("debugger error: %v", err)
// }
// return nil
// }
@ -40,30 +46,22 @@
// func B() error {
// err := C()
// if err != nil {
// return errors.New(errors.DebuggerError, rr)
// return errors.Errorf("debugger error: %v", err)
// }
// return nil
// }
//
// func C() error {
// return errors.New(errors.PanicError, "C()", "not yet implemented")
// return errors.Errorf("not yet implemented")
// }
//
// If we follow the code from main() we can see that first error created is a
// PanicError, wrapped in a DebuggerError, wrapped in another DebuggerError.
// The message for the returned error to main() will be:
// This will result in the main() function printing an error message. Using the
// curated Error() function, the message will be:
//
// error debugging vcs: panic: C(): not yet implemented
// debugger error: not yet implemented
//
// and not
// and not:
//
// error debugging vcs: error debugging vcs: panic: C(): not yet implemented
// debugger error: debugger error: not yet implemented
//
// The PanicError, used in the above example, is a special error that should be
// used when something has happened such that the state of the emulation (or
// the tool) can no longer be guaranteed.
//
// Actual panics should only be used when the error is so terrible that there
// is nothing sensible to be done; useful for brute-enforcement of programming
// constraints and in init() functions.
package errors

View file

@ -23,25 +23,28 @@ import (
// Values is the type used to specify arguments for FormattedErrors
type Values []interface{}
// AtariError allows code to specify a predefined error and not worry too much about the
// message behind that error and how the message will be formatted on output.
type AtariError struct {
Message string
Values Values
// curated erorrs allow code to specify a predefined error and not worry too
// much about the message behind that error and how the message will be
// formatted on output.
type curated struct {
message string
values Values
}
// New is used to create a new instance of an AtariError.
func New(message string, values ...interface{}) AtariError {
return AtariError{
Message: message,
Values: values,
// Errorf creates a new curated error
func Errorf(message string, values ...interface{}) error {
return curated{
message: message,
values: values,
}
}
// Error returns the normalised error message. Most usefully, it compresses
// duplicate adjacent AtariError instances.
func (er AtariError) Error() string {
s := fmt.Sprintf(er.Message, er.Values...)
// Error returns the normalised error message. Normalisation being the removal
// of duplicate adjacent error messsage parts.
//
// Implements the go language error interface.
func (er curated) Error() string {
s := fmt.Errorf(er.message, er.values...).Error()
// de-duplicate error message parts
p := strings.SplitN(s, ": ", 3)
@ -52,39 +55,60 @@ func (er AtariError) Error() string {
return strings.Join(p, ": ")
}
// Is checks if most recently wrapped error is an AtariError with a specific
// head
func Is(err error, head string) bool {
switch er := err.(type) {
case AtariError:
return er.Message == head
// Head returns the leading part of the message.
//
// Similar to Is() but returns the string rather than a boolean. Useful for
// switches.
//
// If err is a plain error then the return of Error() is returned
func Head(err error) string {
if er, ok := err.(curated); ok {
return er.message
}
return false
return err.Error()
}
// IsAny checks if most recently wrapped error is an AtariError, with any head
// IsAny checks if error is being curated by this package
func IsAny(err error) bool {
switch err.(type) {
case AtariError:
return true
}
return false
}
// Has checks to see if the specified AtariError head appears somewhere in the
// sequence of wrapped errors
func Has(err error, head string) bool {
if Is(err, head) {
return true
}
if _, ok := err.(AtariError); !ok {
if err == nil {
return false
}
for i := range err.(AtariError).Values {
if e, ok := err.(AtariError).Values[i].(error); ok {
if Has(e, head) {
if _, ok := err.(curated); ok {
return true
}
return false
}
// Is checks if error has a specific head
func Is(err error, head string) bool {
if err == nil {
return false
}
if er, ok := err.(curated); ok {
return er.message == head
}
return false
}
// Has checks if the message string appears somewhere in the error
func Has(err error, msg string) bool {
if err == nil {
return false
}
if !IsAny(err) {
return false
}
if Is(err, msg) {
return true
}
for i := range err.(curated).values {
if e, ok := err.(curated).values[i].(curated); ok {
if Has(e, msg) {
return true
}
}

View file

@ -20,19 +20,50 @@ import (
"testing"
"github.com/jetsetilly/gopher2600/errors"
"github.com/jetsetilly/gopher2600/test"
)
func TestError(t *testing.T) {
e := errors.New(errors.SetupError, "foo")
if e.Error() != "setup error: foo" {
t.Errorf("unexpected error message")
}
const testError = "test error: %s"
const testErrorB = "test error B: %s"
func TestDuplicateErrors(t *testing.T) {
e := errors.Errorf(testError, "foo")
test.Equate(t, e.Error(), "test error: foo")
// packing errors of the same type next to each other causes
// one of them to be dropped
f := errors.New(errors.SetupError, e)
fmt.Println(f.Error())
if f.Error() != "setup error: foo" {
t.Errorf("unexpected duplicate error message")
}
f := errors.Errorf(testError, e)
test.Equate(t, f.Error(), "test error: foo")
}
func TestIs(t *testing.T) {
e := errors.Errorf(testError, "foo")
test.ExpectedSuccess(t, errors.Is(e, testError))
// Has() should fail because we haven't included testErrorB anywhere in the error
test.ExpectedFailure(t, errors.Has(e, testErrorB))
// packing errors of the same type next to each other causes
// one of them to be dropped
f := errors.Errorf(testErrorB, e)
test.ExpectedFailure(t, errors.Is(f, testError))
test.ExpectedSuccess(t, errors.Is(f, testErrorB))
test.ExpectedSuccess(t, errors.Has(f, testError))
test.ExpectedSuccess(t, errors.Has(f, testErrorB))
// IsAny should return true for these errors also
test.ExpectedSuccess(t, errors.IsAny(e))
test.ExpectedSuccess(t, errors.IsAny(f))
}
func TestPlainErrors(t *testing.T) {
// test plain errors that haven't been formatted with our errors package
e := fmt.Errorf("plain test error")
test.ExpectedFailure(t, errors.IsAny(e))
const testError = "test error: %s"
test.ExpectedFailure(t, errors.Has(e, testError))
}

View file

@ -1,151 +0,0 @@
// This file is part of Gopher2600.
//
// Gopher2600 is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Gopher2600 is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Gopher2600. If not, see <https://www.gnu.org/licenses/>.
package errors
// error messages
const (
// panics
PanicError = "panic: %v: %v"
// sentinals
UserInterrupt = "user interrupt"
UserQuit = "user quit"
ScriptEnd = "end of script (%v)"
PowerOff = "emulated machine has been powered off"
TVOutOfSpec = "tv out of spec: %v"
// program modes
PlayError = "error emulating vcs: %v"
DebuggerError = "error debugging vcs: %v"
PerformanceError = "error during performance profiling: %v"
DisassemblyError = "error during disassembly: %v"
// debugger
InvalidTarget = "invalid target (%v)"
CommandError = "%v"
TerminalError = "%v"
GUIEventError = "%v"
BreakpointError = "breakpoint error: %v"
// commandline
ParserError = "parser error: %v"
HelpError = "help error: %v"
ValidationError = "%v"
// dissassembly
DisasmError = "disasm error: %v"
IterationError = "disasm iteration error: %v"
// script
ScriptFileError = "script error: %v"
ScriptFileUnavailable = "script error: cannot open script file (%v)"
ScriptRunError = "script error: use of '%v' is not allowed in scripts [%v::%d]"
ScriptScribeError = "script scribe error: %v"
// recorder
RecordingError = "recording error: %v"
PlaybackError = "playback error: %v"
PlaybackHashError = "playback error: hash error: %v"
// database
DatabaseError = "database error: %v"
DatabaseReadError = "database error: %v [line %d]"
DatabaseSelectEmpty = "database error: no selected entries"
DatabaseKeyError = "database error: no such key in database [%v]"
DatabaseFileUnavailable = "database error: cannot open database (%v)"
// regression
RegressionError = "regression: %v"
RegressionVideoError = "video regression: %v"
RegressionPlaybackError = "playback regression: %v"
RegressionLogError = "log regression: %v"
// setup
SetupError = "setup error: %v"
SetupPanelError = "panel setup: %v"
SetupPatchError = "patch setup: %v"
SetupTelevisionError = "tv setup: %v"
// patch
PatchError = "patch error: %v"
// symbols
SymbolsFileError = "symbols error: error processing symbols file: %v"
SymbolsFileUnavailable = "symbols error: no symbols file for %v"
SymbolUnknown = "symbols error: unrecognised symbol (%v)"
// cartridgeloader
CartridgeLoader = "cartridge loading error: %v"
// vcs
PolycounterError = "polycounter error: %v"
// cpu
InvalidResult = "cpu error: %v"
InvalidDuringExecution = "cpu error: invalid operation mid-instruction (%v)"
CPUBug = "cpu bug: %v"
// TODO: remove this once all opcodes are defined/implemented
UnimplementedInstruction = "cpu error: unimplemented instruction (%#02x) at (%#04x)"
// memory
UnpokeableAddress = "memory error: cannot poke address (%v)"
UnpeekableAddress = "memory error: cannot peek address (%v)"
MemoryBusError = "memory error: inaccessible address (%v)"
// cartridges
CartridgeError = "cartridge error: %v"
CartridgeEjected = "cartridge error: no cartridge attached"
CartridgeNotMappable = "cartridge error: bank %d can not be mapped to that address (%#04x)"
CartridgePatchOOB = "cartrdige error: patch offset too high (%#04x)"
CartridgeStaticArea = "cartridge error: static area: %v"
SuperchargerError = "cartridge error: AR: %v"
SuperchargerEndOfTape = "end of tape"
// input
InputError = "input error: %v"
UnhandledEvent = "unhandled event: %s does not support event %v"
KeyboardError = "keyboard: %v"
StickError = "stick: %v"
PaddleError = "paddle: %v"
// television
UnknownTVRequest = "television error: unsupported request (%v)"
Television = "television error: %v"
// digests
VideoDigest = "video digest: %v"
AudioDigest = "audio digest: %v"
// audio2wav
WavWriter = "wav writer: %v"
// gui
UnsupportedGUIRequest = "unsupported request (%v)"
SDLDebug = "sdldebug: %v"
SDLPlay = "sdlplay: %v"
SDLImgui = "sdlimgui: %v"
// hiscore server
HiScore = "hiscore: %v"
// linter
Linter = "linter: %v"
// prefs
Prefs = "prefs: %v"
PrefsNoFile = "prefs: no file (%s)"
PrefsNotValid = "prefs: not a valid prefs file (%s)"
)

View file

@ -30,7 +30,6 @@ import (
"github.com/jetsetilly/gopher2600/debugger/terminal/colorterm"
"github.com/jetsetilly/gopher2600/debugger/terminal/plainterm"
"github.com/jetsetilly/gopher2600/disassembly"
"github.com/jetsetilly/gopher2600/errors"
"github.com/jetsetilly/gopher2600/gui"
"github.com/jetsetilly/gopher2600/gui/deprecated/sdldebug"
"github.com/jetsetilly/gopher2600/gui/sdlimgui"
@ -197,7 +196,7 @@ func launch(sync *mainSync) {
case modalflag.ParseHelp:
os.Exit(0)
case modalflag.ParseError:
fmt.Printf("* %s\n", err)
fmt.Printf("* error: %v\n", err)
os.Exit(10)
}
@ -225,7 +224,7 @@ func launch(sync *mainSync) {
}
if err != nil {
fmt.Printf("* %s\n", err)
fmt.Printf("* error in %s mode: %s\n", md.String(), err)
os.Exit(20)
}
}
@ -265,7 +264,7 @@ func play(md *modalflag.Modes, sync *mainSync) error {
tv, err := television.NewTelevision(*spec)
if err != nil {
return errors.New(errors.PlayError, err)
return err
}
defer tv.End()
@ -276,7 +275,7 @@ func play(md *modalflag.Modes, sync *mainSync) error {
if *wav != "" {
aw, err := wavwriter.New(*wav)
if err != nil {
return errors.New(errors.PlayError, err)
return err
}
tv.AddAudioMixer(aw)
}
@ -305,7 +304,7 @@ func play(md *modalflag.Modes, sync *mainSync) error {
}
case err := <-sync.creationError:
return errors.New(errors.PlayError, err)
return err
}
// turn off fallback ctrl-c handling. this so that the playmode can
@ -347,7 +346,7 @@ func debug(md *modalflag.Modes, sync *mainSync) error {
defInitScript, err := paths.ResourcePath("", defaultInitScript)
if err != nil {
return errors.New(errors.DebuggerError, err)
return err
}
mapping := md.AddString("mapping", "AUTO", "force use of cartridge mapping")
@ -364,7 +363,7 @@ func debug(md *modalflag.Modes, sync *mainSync) error {
tv, err := television.NewTelevision(*spec)
if err != nil {
return errors.New(errors.DebuggerError, err)
return err
}
defer tv.End()
@ -388,7 +387,7 @@ func debug(md *modalflag.Modes, sync *mainSync) error {
case g := <-sync.creation:
scr = g.(gui.GUI)
case err := <-sync.creationError:
return errors.New(errors.DebuggerError, err)
return err
}
// if gui implements the terminal.Broker interface use that terminal
@ -502,7 +501,7 @@ func disasm(md *modalflag.Modes) error {
// ignore any further errors
_ = dsm.Write(md.Output, attr)
}
return errors.New(errors.DisassemblyError, err)
return err
}
// output entire disassembly or just a specific bank
@ -513,7 +512,7 @@ func disasm(md *modalflag.Modes) error {
}
if err != nil {
return errors.New(errors.DisassemblyError, err)
return err
}
default:
return fmt.Errorf("too many arguments for %s mode", md)
@ -546,7 +545,7 @@ func perform(md *modalflag.Modes, sync *mainSync) error {
tv, err := television.NewTelevision(*spec)
if err != nil {
return errors.New(errors.PerformanceError, err)
return err
}
defer tv.End()
@ -564,7 +563,7 @@ func perform(md *modalflag.Modes, sync *mainSync) error {
case g := <-sync.creation:
scr = g.(gui.GUI)
case err := <-sync.creationError:
return errors.New(errors.PlayError, err)
return err
}
// set scaling value
@ -581,7 +580,8 @@ func perform(md *modalflag.Modes, sync *mainSync) error {
return err
}
// no saving of gui preferences
// deliberately not saving gui preferences because we don't want any
// changes to the performance window impacting the play mode
default:
return fmt.Errorf("too many arguments for %s mode", md)
@ -672,7 +672,7 @@ func regress(md *modalflag.Modes) error {
return err
}
default:
return fmt.Errorf("only one entry can be deleted at at time when using %s mode", md)
return fmt.Errorf("only one entry can be deleted at at time")
}
case "ADD":

View file

@ -16,6 +16,8 @@
package sdldebug
import (
"fmt"
"github.com/jetsetilly/gopher2600/errors"
"github.com/jetsetilly/gopher2600/gui"
@ -39,8 +41,8 @@ func (scr *SdlDebug) ReqFeature(request gui.FeatureReq, args ...interface{}) (re
func (scr *SdlDebug) serviceFeatureRequests(request featureRequest) {
// lazy (but clear) handling of type assertion errors
defer func() {
if r := recover(); r != nil {
scr.featureErr <- errors.New(errors.PanicError, "sdl.ReqFeature()", r)
if err := recover(); err != nil {
scr.featureErr <- fmt.Errorf("sdldebug: request feature: %v", err)
}
}()
@ -115,7 +117,7 @@ func (scr *SdlDebug) serviceFeatureRequests(request featureRequest) {
// gui doesn't need to know when the cartridge is being changed
default:
err = errors.New(errors.UnsupportedGUIRequest, request.request)
err = errors.Errorf(gui.UnsupportedGuiFeature, request.request)
}
scr.featureErr <- err

View file

@ -115,7 +115,7 @@ func NewSdlDebug(tv television.Television, scale float32) (*SdlDebug, error) {
// set up sdl
err = sdl.Init(sdl.INIT_EVERYTHING)
if err != nil {
return nil, errors.New(errors.SDLDebug, err)
return nil, errors.Errorf("sdldebug: %v", err)
}
setupService()
@ -126,14 +126,14 @@ func NewSdlDebug(tv television.Television, scale float32) (*SdlDebug, error) {
0, 0,
uint32(sdl.WINDOW_HIDDEN))
if err != nil {
return nil, errors.New(errors.SDLDebug, err)
return nil, errors.Errorf("sdldebug: %v", err)
}
// sdl renderer. we set the scaling amount in the setWindow function later
// once we know what the tv specification is
scr.renderer, err = sdl.CreateRenderer(scr.window, -1, uint32(sdl.RENDERER_ACCELERATED))
if err != nil {
return nil, errors.New(errors.SDLDebug, err)
return nil, errors.Errorf("sdldebug: %v", err)
}
// register ourselves as a television.Renderer
@ -143,13 +143,13 @@ func NewSdlDebug(tv television.Television, scale float32) (*SdlDebug, error) {
spec, _ := scr.GetSpec()
err = scr.resize(spec.ScanlineTop, spec.ScanlinesVisible)
if err != nil {
return nil, errors.New(errors.SDLDebug, err)
return nil, errors.Errorf("sdldebug: %v", err)
}
// set window scaling to default value
err = scr.setWindow(scale)
if err != nil {
return nil, errors.New(errors.SDLDebug, err)
return nil, errors.Errorf("sdldebug: %v", err)
}
// note that we've elected not to show the window on startup
@ -260,12 +260,12 @@ func (scr *SdlDebug) resize(topScanline, numScanlines int) error {
scr.textures, err = newTextures(scr.renderer, television.HorizClksScanline, spec.ScanlinesTotal)
if err != nil {
return errors.New(errors.SDLDebug, err)
return errors.Errorf("sdldebug: %v", err)
}
scr.overlay, err = newOverlay(scr.renderer, television.HorizClksScanline, spec.ScanlinesTotal)
if err != nil {
return errors.New(errors.SDLDebug, err)
return errors.Errorf("sdldebug: %v", err)
}
// setWindow dimensions. see commentary for Resize() function in

View file

@ -20,3 +20,8 @@ type GUI interface {
// send a request to set a gui feature
ReqFeature(request FeatureReq, args ...interface{}) error
}
// Sentinal error returned if GUI does no support requested feature
const (
UnsupportedGuiFeature = "unsupported gui feature: %v"
)

View file

@ -91,7 +91,7 @@ func (img *SdlImgui) initPrefs(group prefGroup) error {
err = img.prefs.Load(true)
if err != nil {
// ignore missing prefs file errors
if !errors.Is(err, errors.PrefsNoFile) {
if !errors.Is(err, prefs.NoPrefsFile) {
return err
}
}

View file

@ -110,12 +110,12 @@ func (img *SdlImgui) serviceFeatureRequests(request featureRequest) {
img.plusROMFirstInstallation = request.args[0].(*gui.PlusROMFirstInstallation)
default:
err = errors.New(errors.UnsupportedGUIRequest, request.request)
err = errors.Errorf(gui.UnsupportedGuiFeature, request.request)
}
if err == nil {
img.featureErr <- nil
} else {
img.featureErr <- errors.New(errors.SDLImgui, err)
img.featureErr <- errors.Errorf("sdlimgui: %v", err)
}
}

View file

@ -115,17 +115,17 @@ func NewSdlImgui(tv television.Television, playmode bool) (*SdlImgui, error) {
img.plt, err = newPlatform(img)
if err != nil {
return nil, errors.New(errors.SDLImgui, err)
return nil, errors.Errorf("sdlimgui: %v", err)
}
img.glsl, err = newGlsl(img.io, img)
if err != nil {
return nil, errors.New(errors.SDLImgui, err)
return nil, errors.Errorf("sdlimgui: %v", err)
}
iniPath, err := paths.ResourcePath("", imguiIniFile)
if err != nil {
return nil, errors.New(errors.SDLImgui, err)
return nil, errors.Errorf("sdlimgui: %v", err)
}
img.io.SetIniFilename(iniPath)
@ -138,7 +138,7 @@ func NewSdlImgui(tv television.Television, playmode bool) (*SdlImgui, error) {
img.wm, err = newWindowManager(img)
if err != nil {
return nil, errors.New(errors.SDLImgui, err)
return nil, errors.Errorf("sdlimgui: %v", err)
}
// connect pixel renderer to television and texture renderer to pixel renderer
@ -150,7 +150,7 @@ func NewSdlImgui(tv television.Television, playmode bool) (*SdlImgui, error) {
// implementation in winAudio which visualises the sound
img.audio, err = sdlaudio.NewAudio()
if err != nil {
return nil, errors.New(errors.SDLImgui, err)
return nil, errors.Errorf("sdlimgui: %v", err)
}
tv.AddAudioMixer(img.audio)

View file

@ -134,7 +134,7 @@ func (trm *term) TermRead(buffer []byte, prompt terminal.Prompt, events *termina
ev()
case _ = <-events.IntEvents:
return 0, errors.New(errors.UserQuit)
return 0, errors.Errorf(terminal.UserAbort)
}
}
}

View file

@ -146,14 +146,14 @@ func (mc CPU) HasReset() bool {
// LoadPCIndirect loads the contents of indirectAddress into the PC
func (mc *CPU) LoadPCIndirect(indirectAddress uint16) error {
if !mc.LastResult.Final && !mc.Interrupted {
return errors.New(errors.InvalidDuringExecution, "load PC")
return errors.Errorf("cpu: load PC indirect invalid mid-instruction")
}
// read 16 bit address from specified indirect address
lo, err := mc.mem.Read(indirectAddress)
if err != nil {
if !errors.Is(err, errors.MemoryBusError) {
if !errors.Is(err, bus.AddressError) {
return err
}
mc.LastResult.Error = err.Error()
@ -161,7 +161,7 @@ func (mc *CPU) LoadPCIndirect(indirectAddress uint16) error {
hi, err := mc.mem.Read(indirectAddress + 1)
if err != nil {
if !errors.Is(err, errors.MemoryBusError) {
if !errors.Is(err, bus.AddressError) {
return err
}
mc.LastResult.Error = err.Error()
@ -175,7 +175,7 @@ func (mc *CPU) LoadPCIndirect(indirectAddress uint16) error {
// LoadPC loads the contents of directAddress into the PC
func (mc *CPU) LoadPC(directAddress uint16) error {
if !mc.LastResult.Final && !mc.Interrupted {
return errors.New(errors.InvalidDuringExecution, "load PC")
return errors.Errorf("cpu: load PC invalid mid-instruction")
}
mc.PC.Load(directAddress)
@ -191,7 +191,7 @@ func (mc *CPU) read8Bit(address uint16) (uint8, error) {
val, err := mc.mem.Read(address)
if err != nil {
if !errors.Is(err, errors.MemoryBusError) {
if !errors.Is(err, bus.AddressError) {
return 0, err
}
mc.LastResult.Error = err.Error()
@ -214,7 +214,7 @@ func (mc *CPU) read8BitZeroPage(address uint8) (uint8, error) {
val, err := mc.mem.(bus.CPUBusZeroPage).ReadZeroPage(address)
if err != nil {
if !errors.Is(err, errors.MemoryBusError) {
if !errors.Is(err, bus.AddressError) {
return 0, err
}
mc.LastResult.Error = err.Error()
@ -238,7 +238,7 @@ func (mc *CPU) write8Bit(address uint16, value uint8) error {
if err != nil {
// don't worry about unwritable addresses (unless strict addressing
// is on)
if !errors.Is(err, errors.MemoryBusError) {
if !errors.Is(err, bus.AddressError) {
return err
}
mc.LastResult.Error = err.Error()
@ -254,7 +254,7 @@ func (mc *CPU) write8Bit(address uint16, value uint8) error {
func (mc *CPU) read16Bit(address uint16) (uint16, error) {
lo, err := mc.mem.Read(address)
if err != nil {
if !errors.Is(err, errors.MemoryBusError) {
if !errors.Is(err, bus.AddressError) {
return 0, err
}
mc.LastResult.Error = err.Error()
@ -268,7 +268,7 @@ func (mc *CPU) read16Bit(address uint16) (uint16, error) {
hi, err := mc.mem.Read(address + 1)
if err != nil {
if !errors.Is(err, errors.MemoryBusError) {
if !errors.Is(err, bus.AddressError) {
return 0, err
}
mc.LastResult.Error = err.Error()
@ -296,7 +296,7 @@ func (mc *CPU) read8BitPC(f func(val uint8) error) error {
v, err := mc.mem.Read(mc.PC.Address())
if err != nil {
if !errors.Is(err, errors.MemoryBusError) {
if !errors.Is(err, bus.AddressError) {
return err
}
mc.LastResult.Error = err.Error()
@ -339,7 +339,7 @@ func (mc *CPU) read8BitPC(f func(val uint8) error) error {
func (mc *CPU) read16BitPC() error {
lo, err := mc.mem.Read(mc.PC.Address())
if err != nil {
if !errors.Is(err, errors.MemoryBusError) {
if !errors.Is(err, bus.AddressError) {
return err
}
mc.LastResult.Error = err.Error()
@ -364,7 +364,7 @@ func (mc *CPU) read16BitPC() error {
hi, err := mc.mem.Read(mc.PC.Address())
if err != nil {
if !errors.Is(err, errors.MemoryBusError) {
if !errors.Is(err, bus.AddressError) {
return err
}
mc.LastResult.Error = err.Error()
@ -469,6 +469,11 @@ func (mc *CPU) endCycle() error {
return mc.cycleCallback()
}
// Sentinal error returned by ExecuteInstruction if an unimplemented opcode is encountered
const (
UnimplementedInstruction = "cpu: unimplemented instruction (%#02x) at (%#04x)"
)
// ExecuteInstruction steps CPU forward one instruction. The basic process when
// executing an instruction is this:
//
@ -483,7 +488,7 @@ func (mc *CPU) ExecuteInstruction(cycleCallback func() error) error {
// a previous call to ExecuteInstruction() has not yet completed. it is
// impossible to begin a new instruction
if !mc.LastResult.Final && !mc.Interrupted {
return errors.New(errors.InvalidDuringExecution, "a previous call to ExecuteInstruction() has not yet completed")
return errors.Errorf("cpu: starting a new instruction is invalid mid-instruction")
}
// reset Interrupted flag
@ -519,7 +524,7 @@ func (mc *CPU) ExecuteInstruction(cycleCallback func() error) error {
// !!TODO: remove this once all opcodes are defined/implemented
if defn == nil {
return errors.New(errors.UnimplementedInstruction, val, mc.PC.Address()-1)
return errors.Errorf(UnimplementedInstruction, val, mc.PC.Address()-1)
}
mc.LastResult.Defn = defn
@ -682,7 +687,7 @@ func (mc *CPU) ExecuteInstruction(cycleCallback func() error) error {
lo, err = mc.mem.Read(indirectAddress)
if err != nil {
if !errors.Is(err, errors.MemoryBusError) {
if !errors.Is(err, bus.AddressError) {
return err
}
mc.LastResult.Error = err.Error()
@ -691,7 +696,7 @@ func (mc *CPU) ExecuteInstruction(cycleCallback func() error) error {
// +1 cycle
err = mc.endCycle()
if err != nil {
if !errors.Is(err, errors.MemoryBusError) {
if !errors.Is(err, bus.AddressError) {
return err
}
mc.LastResult.Error = err.Error()

View file

@ -22,6 +22,7 @@ import (
"github.com/jetsetilly/gopher2600/errors"
"github.com/jetsetilly/gopher2600/hardware/cpu"
rtest "github.com/jetsetilly/gopher2600/hardware/cpu/registers/test"
"github.com/jetsetilly/gopher2600/hardware/memory/bus"
)
type mockMem struct {
@ -62,7 +63,7 @@ func (mem *mockMem) Clear() {
func (mem mockMem) Read(address uint16) (uint8, error) {
if address&0xff00 == 0xff00 {
return 0, errors.New(errors.MemoryBusError, address)
return 0, errors.Errorf(bus.AddressError, address)
}
return mem.internal[address], nil
}
@ -73,7 +74,7 @@ func (mem mockMem) ReadZeroPage(address uint8) (uint8, error) {
func (mem *mockMem) Write(address uint16, data uint8) error {
if address&0xff00 == 0xff00 {
return errors.New(errors.MemoryBusError, address)
return errors.Errorf(bus.AddressError, address)
}
mem.internal[address] = data
return nil

View file

@ -16,8 +16,6 @@
package execution
import (
"fmt"
"github.com/jetsetilly/gopher2600/errors"
)
@ -25,52 +23,48 @@ import (
// consistent with the instruction definition.
func (result Result) IsValid() error {
if !result.Final {
return errors.New(errors.InvalidResult, "execution not finalised (bad opcode?)")
return errors.Errorf("cpu: execution not finalised (bad opcode?)")
}
// is PageFault valid given content of Defn
if !result.Defn.PageSensitive && result.PageFault {
return errors.New(errors.InvalidResult, "unexpected page fault")
return errors.Errorf("cpu: unexpected page fault")
}
// byte count
if result.ByteCount != result.Defn.Bytes {
return errors.New(errors.InvalidResult, fmt.Sprintf("unexpected number of bytes read during decode (%d instead of %d)",
result.ByteCount, result.Defn.Bytes))
return errors.Errorf("cpu: unexpected number of bytes read during decode (%d instead of %d)", result.ByteCount, result.Defn.Bytes)
}
// if a bug has been triggered, don't perform the number of cycles check
if result.CPUBug == "" {
if result.Defn.IsBranch() {
if result.ActualCycles != result.Defn.Cycles && result.ActualCycles != result.Defn.Cycles+1 && result.ActualCycles != result.Defn.Cycles+2 {
msg := fmt.Sprintf("number of cycles wrong for opcode %#02x [%s] (%d instead of %d, %d or %d)",
return errors.Errorf("cpu: number of cycles wrong for opcode %#02x [%s] (%d instead of %d, %d or %d)",
result.Defn.OpCode,
result.Defn.Mnemonic,
result.ActualCycles,
result.Defn.Cycles,
result.Defn.Cycles+1,
result.Defn.Cycles+2)
return errors.New(errors.InvalidResult, msg)
}
} else {
if result.Defn.PageSensitive {
if result.PageFault && result.ActualCycles != result.Defn.Cycles && result.ActualCycles != result.Defn.Cycles+1 {
msg := fmt.Sprintf("number of cycles wrong for opcode %#02x [%s] (%d instead of %d, %d)",
return errors.Errorf("cpu: number of cycles wrong for opcode %#02x [%s] (%d instead of %d, %d)",
result.Defn.OpCode,
result.Defn.Mnemonic,
result.ActualCycles,
result.Defn.Cycles,
result.Defn.Cycles+1)
return errors.New(errors.InvalidResult, msg)
}
} else {
if result.ActualCycles != result.Defn.Cycles {
msg := fmt.Sprintf("number of cycles wrong for opcode %#02x [%s] (%d instead of %d)",
return errors.Errorf("cpu: number of cycles wrong for opcode %#02x [%s] (%d instead of %d)",
result.Defn.OpCode,
result.Defn.Mnemonic,
result.ActualCycles,
result.Defn.Cycles)
return errors.New(errors.InvalidResult, msg)
}
}
}

View file

@ -74,3 +74,9 @@ type ChipBus interface {
type UpdateBus interface {
Update(ChipData) bool
}
// Sentinal error returned by memory package functions. Note that the error
// expects a numberic address, which will be formatted as four digit hex.
const (
AddressError = "inaccessible address (%#04x)"
)

View file

@ -48,6 +48,11 @@ type Cartridge struct {
Passive bool
}
// Sentinal error returned if operation is on the ejected cartridge type
const (
Ejected = "cartridge ejected"
)
// NewCartridge is the preferred method of initialisation for the cartridge
// type
func NewCartridge() *Cartridge {
@ -139,7 +144,7 @@ func (cart *Cartridge) Attach(cartload cartridgeloader.Loader) error {
if cartload.Mapping == "" || cartload.Mapping == "AUTO" {
err := cart.fingerprint(cartload)
if err != nil {
return err
return errors.Errorf("cartridge: %v", err)
}
// in addition to the regular fingerprint we also check to see if this
@ -159,7 +164,7 @@ func (cart *Cartridge) Attach(cartload cartridgeloader.Loader) error {
return nil
}
return errors.New(errors.CartridgeError, err)
return errors.Errorf("cartridge: %v", err)
}
// we've wrapped the main cartridge mapper inside the PlusROM
@ -229,13 +234,13 @@ func (cart *Cartridge) Attach(cartload cartridgeloader.Loader) error {
}
if err != nil {
return errors.New(errors.CartridgeError, err)
return errors.Errorf("cartridge: %v", err)
}
if addSuperchip {
if superchip, ok := cart.mapper.(mapper.OptionalSuperchip); ok {
if !superchip.AddSuperchip() {
return errors.New(errors.CartridgeError, "error adding superchip")
return errors.Errorf("cartridge: error adding superchip")
}
}
}

View file

@ -16,8 +16,6 @@
package cartridge
import (
"fmt"
"github.com/jetsetilly/gopher2600/cartridgeloader"
"github.com/jetsetilly/gopher2600/errors"
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/harmony"
@ -234,7 +232,7 @@ func (cart *Cartridge) fingerprint(cartload cartridgeloader.Loader) error {
}
case 65536:
return errors.New(errors.CartridgeError, "65536 bytes not yet supported")
return errors.Errorf("cartridge: 65536 bytes not yet supported")
case 131072:
cart.mapper, err = fingerprint128k(cartload.Data)(cartload.Data)
@ -243,7 +241,7 @@ func (cart *Cartridge) fingerprint(cartload cartridgeloader.Loader) error {
}
default:
return errors.New(errors.CartridgeError, fmt.Sprintf("unrecognised cartridge size (%d bytes)", len(cartload.Data)))
return errors.Errorf("cartridge: unrecognised size (%d bytes)", len(cartload.Data))
}
// if cartridge mapper implements the optionalSuperChip interface then try

View file

@ -19,6 +19,7 @@ import (
"fmt"
"github.com/jetsetilly/gopher2600/errors"
"github.com/jetsetilly/gopher2600/hardware/memory/bus"
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/banks"
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
"github.com/jetsetilly/gopher2600/hardware/memory/memorymap"
@ -75,7 +76,7 @@ func NewDPCplus(data []byte) (mapper.CartMapper, error) {
// size check
if bankLen <= 0 || bankLen%cart.bankSize != 0 {
return nil, errors.New(errors.CartridgeError, fmt.Sprintf("%s: wrong number of bytes in cartridge data", cart.mappingID))
return nil, errors.Errorf("cartridge", fmt.Errorf("%s: wrong number of bytes in cartridge data", cart.mappingID))
}
// partition
@ -151,7 +152,7 @@ func (cart *dpcPlus) Read(addr uint16, passive bool) (uint8, error) {
}
if addr > 0x0027 {
return 0, errors.New(errors.MemoryBusError, addr)
return 0, errors.Errorf(bus.AddressError, addr)
}
switch addr {
@ -276,7 +277,7 @@ func (cart *dpcPlus) Write(addr uint16, data uint8, passive bool, poke bool) err
}
if addr < 0x0028 || addr > 0x007f {
return errors.New(errors.MemoryBusError, addr)
return errors.Errorf(bus.AddressError, addr)
}
switch addr {
@ -553,7 +554,7 @@ func (cart *dpcPlus) Write(addr uint16, data uint8, passive bool, poke bool) err
return nil
}
return errors.New(errors.MemoryBusError, addr)
return errors.Errorf(bus.AddressError, addr)
}
// bankswitch on hotspot access
@ -590,7 +591,7 @@ func (cart dpcPlus) GetBank(addr uint16) banks.Details {
func (cart *dpcPlus) Patch(offset int, data uint8) error {
if offset >= cart.fileSize {
return errors.New(errors.CartridgePatchOOB, offset)
return errors.Errorf("%s: patch offset too high (%v)", cart.ID(), offset)
}
if offset >= cart.freqOffset {

View file

@ -53,24 +53,24 @@ func (cart *dpcPlus) PutStatic(label string, addr uint16, data uint8) error {
switch label {
case "ARM":
if int(addr) >= len(cart.static.Arm) {
return errors.New(errors.CartridgeStaticArea, fmt.Errorf("address too high (%#04x) for %s area", addr, label))
return errors.Errorf("harmony: static: %v", fmt.Errorf("address too high (%#04x) for %s area", addr, label))
}
cart.static.Arm[addr] = data
case "Data":
if int(addr) >= len(cart.static.Data) {
return errors.New(errors.CartridgeStaticArea, fmt.Errorf("address too high (%#04x) for %s area", addr, label))
return errors.Errorf("harmony: static: %v", fmt.Errorf("address too high (%#04x) for %s area", addr, label))
}
cart.static.Data[addr] = data
case "Freq":
if int(addr) >= len(cart.static.Freq) {
return errors.New(errors.CartridgeStaticArea, fmt.Errorf("address too high (%#04x) for %s area", addr, label))
return errors.Errorf("harmony: static: %v", fmt.Errorf("address too high (%#04x) for %s area", addr, label))
}
cart.static.Freq[addr] = data
default:
return errors.New(errors.CartridgeStaticArea, fmt.Errorf("unknown static area (%s)", label))
return errors.Errorf("harmony: static: %v", fmt.Errorf("unknown static area (%s)", label))
}
return nil

View file

@ -63,7 +63,7 @@ func new3e(data []byte) (mapper.CartMapper, error) {
const ramSize = 1024
if len(data)%cart.bankSize != 0 {
return nil, errors.New(errors.CartridgeError, fmt.Sprintf("%s: wrong number bytes in the cartridge data", cart.mappingID))
return nil, errors.Errorf("%s: wrong number bytes in the cartridge data", cart.mappingID)
}
numBanks := len(data) / cart.bankSize
@ -159,7 +159,7 @@ func (cart *m3e) Write(addr uint16, data uint8, passive bool, poke bool) error {
return nil
}
return errors.New(errors.MemoryBusError, addr)
return errors.Errorf(bus.AddressError, addr)
}
// NumBanks implements the mapper.CartMapper interface
@ -178,7 +178,7 @@ func (cart *m3e) GetBank(addr uint16) banks.Details {
// Patch implements the mapper.CartMapper interface
func (cart *m3e) Patch(offset int, data uint8) error {
if offset >= cart.bankSize*len(cart.banks) {
return errors.New(errors.CartridgePatchOOB, offset)
return errors.Errorf("%s: patch offset too high (%v)", cart.ID(), offset)
}
bank := int(offset) / cart.bankSize

View file

@ -65,7 +65,7 @@ func new3ePlus(data []byte) (mapper.CartMapper, error) {
const ramSize = 512
if len(data)%cart.bankSize != 0 {
return nil, errors.New(errors.CartridgeError, fmt.Sprintf("%s: wrong number bytes in the cartridge file", cart.mappingID))
return nil, errors.Errorf("%s: wrong number bytes in the cartridge file", cart.mappingID)
}
numBanks := len(data) / cart.bankSize
@ -175,7 +175,7 @@ func (cart *m3ePlus) Write(addr uint16, data uint8, passive bool, poke bool) err
return nil
}
return errors.New(errors.MemoryBusError, addr)
return errors.Errorf(bus.AddressError, addr)
}
// NumBanks implements the mapper.CartMapper interface
@ -205,7 +205,7 @@ func (cart *m3ePlus) GetBank(addr uint16) banks.Details {
// Patch implements the mapper.CartMapper interface
func (cart *m3ePlus) Patch(offset int, data uint8) error {
if offset >= cart.bankSize*len(cart.banks) {
return errors.New(errors.CartridgePatchOOB, offset)
return errors.Errorf("%s: patch offset too high (%v)", cart.ID(), offset)
}
bank := int(offset) / cart.bankSize

View file

@ -146,7 +146,7 @@ func (cart *atari) Write(addr uint16, data uint8, passive bool, poke bool) error
return nil
}
return errors.New(errors.MemoryBusError, addr)
return errors.Errorf(bus.AddressError, addr)
}
func (cart *atari) addSuperchip() bool {
@ -181,7 +181,7 @@ func (cart *atari) addSuperchip() bool {
// Patch implements the mapper.CartMapper interface
func (cart *atari) Patch(offset int, data uint8) error {
if offset >= cart.bankSize*len(cart.banks) {
return errors.New(errors.CartridgePatchOOB, offset)
return errors.Errorf("%s: patch offset too high (%v)", cart.ID(), offset)
}
bank := int(offset) / cart.bankSize
@ -257,7 +257,7 @@ func newAtari4k(data []byte) (mapper.CartMapper, error) {
cart.banks = make([][]uint8, 1)
if len(data) != cart.bankSize*cart.NumBanks() {
return nil, errors.New(errors.CartridgeError, fmt.Sprintf("%s: wrong number of bytes in the cartridge file", cart.mappingID))
return nil, errors.Errorf("%s: wrong number of bytes in the cartridge file", cart.mappingID)
}
cart.banks[0] = make([]uint8, cart.bankSize)
@ -307,7 +307,7 @@ func newAtari2k(data []byte) (mapper.CartMapper, error) {
cart.banks = make([][]uint8, 1)
if len(data) != cart.bankSize*cart.NumBanks() {
return nil, errors.New(errors.CartridgeError, fmt.Sprintf("%s: wrong number of bytes in the cartridge file", cart.mappingID))
return nil, errors.Errorf("%s: wrong number of bytes in the cartridge file", cart.mappingID)
}
cart.banks[0] = make([]uint8, cart.bankSize)
@ -355,7 +355,7 @@ func newAtari8k(data []uint8) (mapper.CartMapper, error) {
cart.banks = make([][]uint8, cart.NumBanks())
if len(data) != cart.bankSize*cart.NumBanks() {
return nil, errors.New(errors.CartridgeError, fmt.Sprintf("%s: wrong number of bytes in the cartridge file", cart.mappingID))
return nil, errors.Errorf("%s: wrong number of bytes in the cartridge file", cart.mappingID)
}
for k := 0; k < cart.NumBanks(); k++ {
@ -431,7 +431,7 @@ func newAtari16k(data []byte) (mapper.CartMapper, error) {
cart.banks = make([][]uint8, cart.NumBanks())
if len(data) != cart.bankSize*cart.NumBanks() {
return nil, errors.New(errors.CartridgeError, fmt.Sprintf("%s: wrong number of bytes in the cartridge file", cart.mappingID))
return nil, errors.Errorf("%s: wrong number of bytes in the cartridge file", cart.mappingID)
}
for k := 0; k < cart.NumBanks(); k++ {
@ -511,7 +511,7 @@ func newAtari32k(data []byte) (mapper.CartMapper, error) {
cart.banks = make([][]uint8, cart.NumBanks())
if len(data) != cart.bankSize*cart.NumBanks() {
return nil, errors.New(errors.CartridgeError, fmt.Sprintf("%s: wrong number of bytes in the cartridge file", cart.mappingID))
return nil, errors.Errorf("%s: wrong number of bytes in the cartridge file", cart.mappingID)
}
for k := 0; k < cart.NumBanks(); k++ {

View file

@ -49,7 +49,7 @@ func newCBS(data []byte) (mapper.CartMapper, error) {
}
if len(data) != cart.bankSize*cart.NumBanks() {
return nil, errors.New(errors.CartridgeError, fmt.Sprintf("%s: wrong number of bytes in the cartridge data", cart.mappingID))
return nil, errors.Errorf("%s: wrong number of bytes in the cartridge data", cart.mappingID)
}
cart.banks = make([][]uint8, cart.NumBanks())
@ -109,7 +109,7 @@ func (cart *cbs) Write(addr uint16, data uint8, passive bool, poke bool) error {
return nil
}
return errors.New(errors.MemoryBusError, addr)
return errors.Errorf(bus.AddressError, addr)
}
// bankswitch on hotspot access
@ -145,7 +145,7 @@ func (cart cbs) GetBank(addr uint16) banks.Details {
// Patch implements the mapper.CartMapper interface
func (cart *cbs) Patch(offset int, data uint8) error {
if offset >= cart.bankSize*len(cart.banks) {
return errors.New(errors.CartridgePatchOOB, offset)
return errors.Errorf("%s: patch offset too high (%v)", cart.ID(), offset)
}
bank := int(offset) / cart.bankSize

View file

@ -49,7 +49,7 @@ func newDF(data []byte) (mapper.CartMapper, error) {
}
if len(data) != cart.bankSize*cart.NumBanks() {
return nil, errors.New(errors.CartridgeError, fmt.Sprintf("%s: wrong number of bytes in the cartridge data", cart.mappingID))
return nil, errors.Errorf("%s: wrong number of bytes in the cartridge data", cart.mappingID)
}
cart.banks = make([][]uint8, cart.NumBanks())
@ -110,7 +110,7 @@ func (cart *df) Write(addr uint16, data uint8, passive bool, poke bool) error {
return nil
}
return errors.New(errors.MemoryBusError, addr)
return errors.Errorf(bus.AddressError, addr)
}
// bankswitch on hotspot access
@ -207,7 +207,7 @@ func (cart df) GetBank(addr uint16) banks.Details {
// Patch implements the mapper.CartMapper interface
func (cart *df) Patch(offset int, data uint8) error {
if offset >= cart.bankSize*len(cart.banks) {
return errors.New(errors.CartridgePatchOOB, offset)
return errors.Errorf("%s: patch offset too high (%v)", cart.ID(), offset)
}
bank := int(offset) / cart.bankSize

View file

@ -136,7 +136,7 @@ func newDPC(data []byte) (mapper.CartMapper, error) {
cart.banks = make([][]uint8, cart.NumBanks())
if len(data) < cart.bankSize*cart.NumBanks()+staticSize {
return nil, errors.New(errors.CartridgeError, fmt.Sprintf("%s: wrong number of bytes in the cartridge data", cart.mappingID))
return nil, errors.Errorf("%s: wrong number of bytes in the cartridge data", cart.mappingID)
}
for k := 0; k < cart.NumBanks(); k++ {
@ -341,7 +341,7 @@ func (cart *dpc) Write(addr uint16, data uint8, passive bool, poke bool) error {
return nil
}
return errors.New(errors.MemoryBusError, addr)
return errors.Errorf(bus.AddressError, addr)
}
// bank switch on hotspot access
@ -373,7 +373,7 @@ func (cart dpc) GetBank(addr uint16) banks.Details {
// Patch implements the mapper.CartMapper interface
func (cart *dpc) Patch(offset int, data uint8) error {
if offset >= cart.bankSize*len(cart.banks)+len(cart.static.Gfx) {
return errors.New(errors.CartridgePatchOOB, offset)
return errors.Errorf("%s: patch offset too high (%v)", cart.ID(), offset)
}
staticStart := cart.NumBanks() * cart.bankSize
@ -495,11 +495,11 @@ func (cart dpc) GetStatic() []bus.CartStatic {
func (cart *dpc) PutStatic(label string, addr uint16, data uint8) error {
if label == "Gfx" {
if int(addr) >= len(cart.static.Gfx) {
return errors.New(errors.CartridgeStaticArea, fmt.Errorf("address too high (%#04x) for %s area", addr, label))
return errors.Errorf("dpc: static: %v", fmt.Errorf("address too high (%#04x) for %s area", addr, label))
}
cart.static.Gfx[addr] = data
} else {
return errors.New(errors.CartridgeStaticArea, fmt.Errorf("unknown static area (%s)", label))
return errors.Errorf("dpc: static: %v", fmt.Errorf("unknown static area (%s)", label))
}
return nil

View file

@ -53,12 +53,12 @@ func (cart *ejected) Initialise() {
// Read implements the cartMapper interface
func (cart *ejected) Read(_ uint16, _ bool) (uint8, error) {
return 0, errors.New(errors.CartridgeEjected)
return 0, errors.Errorf(Ejected)
}
// Write implements the cartMapper interface
func (cart *ejected) Write(_ uint16, _ uint8, _, _ bool) error {
return errors.New(errors.CartridgeEjected)
return errors.Errorf(Ejected)
}
// NumBanks implements the cartMapper interface
@ -73,7 +73,7 @@ func (cart ejected) GetBank(_ uint16) banks.Details {
// Patch implements the cartMapper interface
func (cart *ejected) Patch(_ int, _ uint8) error {
return errors.New(errors.CartridgeEjected)
return errors.Errorf(Ejected)
}
// Listen implements the cartMapper interface

View file

@ -107,7 +107,7 @@ func newMnetwork(data []byte) (mapper.CartMapper, error) {
cart.banks = make([][]uint8, cart.NumBanks())
if len(data) != cart.bankSize*cart.NumBanks() {
return nil, errors.New(errors.CartridgeError, fmt.Sprintf("%s: wrong number of bytes in the cartridge data", cart.mappingID))
return nil, errors.Errorf("%s: wrong number of bytes in the cartridge data", cart.mappingID)
}
for k := 0; k < cart.NumBanks(); k++ {
@ -185,7 +185,7 @@ func (cart *mnetwork) Read(addr uint16, passive bool) (uint8, error) {
data = cart.banks[cart.NumBanks()-1][addr&0x07ff]
}
} else {
return 0, errors.New(errors.MemoryBusError, addr)
return 0, errors.Errorf(bus.AddressError, addr)
}
return data, nil
@ -212,7 +212,7 @@ func (cart *mnetwork) Write(addr uint16, data uint8, passive bool, poke bool) er
return nil
}
return errors.New(errors.MemoryBusError, addr)
return errors.Errorf(bus.AddressError, addr)
}
// bankswitch on hotspot access
@ -298,7 +298,7 @@ func (cart *mnetwork) GetBank(addr uint16) banks.Details {
// Patch implements the mapper.CartMapper interface
func (cart *mnetwork) Patch(offset int, data uint8) error {
if offset >= cart.bankSize*len(cart.banks) {
return errors.New(errors.CartridgePatchOOB, offset)
return errors.Errorf("%s: patch offset too high (%v)", cart.ID(), offset)
}
bank := int(offset) / cart.bankSize

View file

@ -19,6 +19,7 @@ import (
"fmt"
"github.com/jetsetilly/gopher2600/errors"
"github.com/jetsetilly/gopher2600/hardware/memory/bus"
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/banks"
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
"github.com/jetsetilly/gopher2600/hardware/memory/memorymap"
@ -63,7 +64,7 @@ func newParkerBros(data []byte) (mapper.CartMapper, error) {
cart.banks = make([][]uint8, cart.NumBanks())
if len(data) != cart.bankSize*cart.NumBanks() {
return nil, errors.New(errors.CartridgeError, fmt.Sprintf("%s: wrong number of bytes in the cartridge data", cart.mappingID))
return nil, errors.Errorf("%s: wrong number of bytes in the cartridge data", cart.mappingID)
}
for k := 0; k < cart.NumBanks(); k++ {
@ -128,7 +129,7 @@ func (cart *parkerBros) Write(addr uint16, data uint8, passive bool, poke bool)
cart.hotspot(addr, passive)
return errors.New(errors.MemoryBusError, addr)
return errors.Errorf(bus.AddressError, addr)
}
// bankswitch on hotspot access
@ -224,7 +225,7 @@ func (cart parkerBros) GetBank(addr uint16) banks.Details {
// Patch implements the mapper.CartMapper interface
func (cart *parkerBros) Patch(offset int, data uint8) error {
if offset >= cart.bankSize*len(cart.banks) {
return errors.New(errors.CartridgePatchOOB, offset)
return errors.Errorf("%s: patch offset too high (%v)", cart.ID(), offset)
}
bank := int(offset) / cart.bankSize

View file

@ -19,6 +19,7 @@ import (
"fmt"
"github.com/jetsetilly/gopher2600/errors"
"github.com/jetsetilly/gopher2600/hardware/memory/bus"
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/banks"
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
"github.com/jetsetilly/gopher2600/hardware/memory/memorymap"
@ -66,7 +67,7 @@ func newTigervision(data []byte) (mapper.CartMapper, error) {
}
if len(data)%cart.bankSize != 0 {
return nil, errors.New(errors.CartridgeError, fmt.Sprintf("%s: wrong number bytes in the cartridge data", cart.mappingID))
return nil, errors.Errorf("%s: wrong number bytes in the cartridge data", cart.mappingID)
}
numBanks := len(data) / cart.bankSize
@ -120,7 +121,7 @@ func (cart *tigervision) Write(addr uint16, data uint8, _ bool, poke bool) error
cart.banks[cart.segment[1]][addr&0x07ff] = data
}
}
return errors.New(errors.MemoryBusError, addr)
return errors.Errorf(bus.AddressError, addr)
}
// NumBanks implements the mapper.CartMapper interface
@ -139,7 +140,7 @@ func (cart *tigervision) GetBank(addr uint16) banks.Details {
// Patch implements the mapper.CartMapper interface
func (cart *tigervision) Patch(offset int, data uint8) error {
if offset >= cart.bankSize*len(cart.banks) {
return errors.New(errors.CartridgePatchOOB, offset)
return errors.Errorf("%s: patch offset too high (%v)", cart.ID(), offset)
}
bank := int(offset) / cart.bankSize

View file

@ -43,7 +43,7 @@ func NewPlusROM(child mapper.CartMapper, onLoaded func(cart mapper.CartMapper) e
cart.Prefs, err = newPreferences()
if err != nil {
return nil, errors.New("plusrom: %s", err)
return nil, errors.Errorf("plusrom: %s", err)
}
cart.net = newNetwork(cart.Prefs)
@ -71,7 +71,7 @@ func NewPlusROM(child mapper.CartMapper, onLoaded func(cart mapper.CartMapper) e
b := int((a & 0xf000) >> 12)
if b == 0 || b > cart.NumBanks() {
return nil, errors.New(NotAPlusROM, "invlid NMI vector")
return nil, errors.Errorf(NotAPlusROM, "invlid NMI vector")
}
// normalise indirect address so it's suitable for indexing bank data
@ -115,7 +115,7 @@ func NewPlusROM(child mapper.CartMapper, onLoaded func(cart mapper.CartMapper) e
// fail if host or path is not valid
hostValid, pathValid := cart.SetAddrInfo(host.String(), path.String())
if !hostValid || !pathValid {
return nil, errors.New(NotAPlusROM, "invalid host/path")
return nil, errors.Errorf(NotAPlusROM, "invalid host/path")
}
// log success
@ -125,7 +125,7 @@ func NewPlusROM(child mapper.CartMapper, onLoaded func(cart mapper.CartMapper) e
if onLoaded != nil {
err := onLoaded(cart)
if err != nil {
return nil, errors.New("plusrom %s:", err)
return nil, errors.Errorf("plusrom %s:", err)
}
}

View file

@ -75,7 +75,7 @@ func NewSupercharger(cartload cartridgeloader.Loader) (mapper.CartMapper, error)
cart.tape, err = NewFastLoad(cart, cartload)
}
if err != nil {
return nil, errors.New(errors.SuperchargerError, err)
return nil, errors.Errorf("supercharger: %v", err)
}
// allocate ram
@ -86,7 +86,7 @@ func NewSupercharger(cartload cartridgeloader.Loader) (mapper.CartMapper, error)
// load bios and activate
cart.bios, err = loadBIOS(path.Dir(cartload.Filename))
if err != nil {
return nil, errors.New(errors.SuperchargerError, err)
return nil, errors.Errorf("supercharger: %v", err)
}
// prepare onLoaded function
@ -179,14 +179,14 @@ func (cart *Supercharger) Read(fullAddr uint16, passive bool) (uint8, error) {
if fullAddr == 0xfa1a {
err := cart.onLoaded(cart)
if err != nil {
return 0, errors.New(errors.SuperchargerError, err)
return 0, errors.Errorf("supercharger: %v", err)
}
}
return cart.bios[addr&0x07ff], nil
}
return 0, errors.New(errors.SuperchargerError, "ROM is powered off")
return 0, errors.Errorf("supercharger: ROM is powered off")
}
if !passive && cart.registers.Delay == 1 {
@ -266,7 +266,7 @@ func (cart Supercharger) GetBank(addr uint16) banks.Details {
// Patch implements the cartMapper interface
func (cart *Supercharger) Patch(_ int, _ uint8) error {
return nil
return errors.Errorf("%s: not patchable")
}
// Listen implements the cartMapper interface

View file

@ -223,7 +223,7 @@ func (mem VCSMemory) Peek(address uint16) (uint8, error) {
if area, ok := mem.GetArea(ar).(bus.DebugBus); ok {
return area.Peek(ma)
}
return 0, errors.New(errors.UnpeekableAddress, address)
return 0, errors.Errorf(bus.AddressError, address)
}
// Poke implements the DebugBus interface
@ -232,5 +232,5 @@ func (mem VCSMemory) Poke(address uint16, data uint8) error {
if area, ok := mem.GetArea(ar).(bus.DebugBus); ok {
return area.(bus.DebugBus).Poke(ma, data)
}
return errors.New(errors.UnpokeableAddress, address)
return errors.Errorf(bus.AddressError, address)
}

View file

@ -55,7 +55,7 @@ type ChipMemory struct {
func (area ChipMemory) Peek(address uint16) (uint8, error) {
sym := addresses.Read[address]
if sym == "" {
return 0, errors.New(errors.UnpeekableAddress, fmt.Sprintf("%#04x", address))
return 0, errors.Errorf(bus.AddressError, address)
}
return area.memory[address^area.origin], nil
}
@ -95,7 +95,7 @@ func (area *ChipMemory) Read(address uint16) (uint8, error) {
// do not allow reads from memory that do not have symbol name
if _, ok := addresses.ReadSymbols[address]; !ok {
return 0, errors.New(errors.MemoryBusError, address)
return 0, errors.Errorf(bus.AddressError, address)
}
return area.memory[address^area.origin], nil
@ -111,7 +111,7 @@ func (area *ChipMemory) Write(address uint16, data uint8) error {
// do not allow writes to memory that do not have symbol name
if _, ok := addresses.WriteSymbols[address]; !ok {
return errors.New(errors.MemoryBusError, address)
return errors.Errorf(bus.AddressError, address)
}
// signal the chips that their chip memory has been written to

View file

@ -99,7 +99,7 @@ func (aut *Auto) HandleEvent(event ports.Event, data ports.EventData) error {
err := aut.controller.HandleEvent(event, data)
// if error was because of an unhandled event then return without error
if err != nil && errors.Is(err, errors.UnhandledEvent) {
if err != nil && errors.Is(err, UnhandledEvent) {
return nil
}

View file

@ -19,3 +19,9 @@ package controllers
// returned by the ID() function of the ports.Peripheral implementations in
// this package
var ControllerList = []string{"Stick", "Paddle", "Keyboard"}
// Sentinal error returned if controller implementation does not understand
// event sent to HandleEvent()
const (
UnhandledEvent = "unhandled event: %s does not support event %v"
)

View file

@ -69,7 +69,7 @@ func (key *Keyboard) Name() string {
func (key *Keyboard) HandleEvent(event ports.Event, data ports.EventData) error {
switch event {
default:
return errors.New(errors.UnhandledEvent, key.Name(), event)
return errors.Errorf(UnhandledEvent, key.Name(), event)
case ports.NoEvent:
@ -79,7 +79,7 @@ func (key *Keyboard) HandleEvent(event ports.Event, data ports.EventData) error
k != '4' && k != '5' && k != '6' &&
k != '7' && k != '8' && k != '9' &&
k != '*' && k != '0' && k != '#' {
return errors.New(errors.KeyboardError, fmt.Sprintf("unrecognised rune (%v)", k))
return errors.Errorf("keyboard: unrecognised rune (%v)", k)
}
// note key for use by readKeyboard()

View file

@ -94,7 +94,7 @@ func (pdl *Paddle) Name() string {
func (pdl *Paddle) HandleEvent(event ports.Event, data ports.EventData) error {
switch event {
default:
return errors.New(errors.UnhandledEvent, pdl.Name(), event)
return errors.Errorf(UnhandledEvent, pdl.Name(), event)
case ports.NoEvent:

View file

@ -82,7 +82,7 @@ func (stk *Stick) Name() string {
func (stk *Stick) HandleEvent(event ports.Event, data ports.EventData) error {
switch event {
default:
return errors.New(errors.UnhandledEvent, stk.Name(), event)
return errors.Errorf(UnhandledEvent, stk.Name(), event)
case ports.NoEvent:

View file

@ -131,6 +131,11 @@ func (pan *Panel) write() {
pan.bus.WriteSWCHx(PanelID, v)
}
// Sentinal error returned by Panel.HandleEvent() if power button is pressed
const (
PowerOff = "emulated machine has been powered off"
)
// HandleEvent implements Peripheral interface
func (pan *Panel) HandleEvent(event Event, value EventData) error {
switch event {
@ -160,7 +165,7 @@ func (pan *Panel) HandleEvent(event Event, value EventData) error {
pan.p1pro = !pan.p1pro
case PanelPowerOff:
return errors.New(errors.PowerOff)
return errors.Errorf(PowerOff)
}

View file

@ -271,7 +271,7 @@ func (p *Ports) HandleEvent(id PortID, ev Event, d EventData) error {
}
if err != nil {
return errors.New(errors.InputError, err)
return errors.Errorf("ports: %v", err)
}
// record event with the EventRecorder

View file

@ -54,7 +54,7 @@ func New(numBits int) (*Polycounter, error) {
// sanity check that the table has looped correctly
if pcnt.table[len(pcnt.table)-1] != pcnt.table[0] {
return nil, errors.New(errors.PolycounterError, fmt.Sprintf("error creating %d bit polycounter", numBits))
return nil, errors.Errorf("polycounter: %v", fmt.Errorf("error creating %d bit polycounter", numBits))
}
// force the final value to be the invalid polycounter value. this is only
@ -104,5 +104,5 @@ func (pcnt *Polycounter) Match(pattern string) (int, error) {
return i, nil
}
}
return 0, errors.New(errors.PolycounterError, fmt.Sprintf("could not find pattern (%s) in %d bit lookup table", pattern, pcnt.numBits))
return 0, errors.Errorf("polycounter: %v", fmt.Errorf("could not find pattern (%s) in %d bit lookup table", pattern, pcnt.numBits))
}

View file

@ -19,7 +19,6 @@ import (
"fmt"
"strings"
"github.com/jetsetilly/gopher2600/errors"
"github.com/jetsetilly/gopher2600/hardware/memory/bus"
"github.com/jetsetilly/gopher2600/hardware/tia/audio"
"github.com/jetsetilly/gopher2600/hardware/tia/delay"
@ -490,10 +489,7 @@ func (tia *TIA) Step(readMemory bool) (bool, error) {
// send signal to television
if err := tia.tv.Signal(tia.sig); err != nil {
// allow out-of-spec errors for now. this should be optional
if !errors.Is(err, errors.TVOutOfSpec) {
return !tia.wsync, err
}
return !tia.wsync, err
}
return !tia.wsync, nil

View file

@ -39,7 +39,7 @@ func newPreferences() (*Preferences, error) {
// save server using the prefs package
pth, err := paths.ResourcePath("", prefs.DefaultPrefsFile)
if err != nil {
return nil, errors.New(errors.HiScore, err)
return nil, errors.Errorf("hiscore: %v", err)
}
p.dsk, err = prefs.NewDisk(pth)
@ -48,7 +48,7 @@ func newPreferences() (*Preferences, error) {
err = p.dsk.Load(true)
if err != nil {
return p, errors.New(errors.HiScore, err)
return p, errors.Errorf("hiscore: %v", err)
}
return p, nil

View file

@ -43,7 +43,7 @@ func NewSession() (*Session, error) {
sess.Prefs, err = newPreferences()
if err != nil {
return nil, errors.New(errors.HiScore, err)
return nil, errors.Errorf("hiscore: %v", err)
}
return sess, nil
@ -55,7 +55,7 @@ func (sess *Session) StartSession(name string, hash string) error {
jsonValue, _ := json.Marshal(values)
statusCode, response, err := sess.post("/HiScore/rest/game/", jsonValue)
if err != nil {
return errors.New(errors.HiScore, err)
return errors.Errorf("hiscore: %v", err)
}
switch statusCode {
@ -65,12 +65,12 @@ func (sess *Session) StartSession(name string, hash string) error {
// game is new and has been added to the database
default:
err = fmt.Errorf("register game: unexpected response from HiScore server [%d: %s]", statusCode, response)
return errors.New(errors.HiScore, err)
return errors.Errorf("hiscore: %v", err)
}
err = json.Unmarshal(response, &sess.id)
if err != nil {
return errors.New(errors.HiScore, err)
return errors.Errorf("hiscore: %v", err)
}
return nil
@ -83,7 +83,7 @@ func (sess *Session) EndSession(playTime time.Duration) error {
jsonValue, _ := json.Marshal(values)
statusCode, response, err := sess.post("/HiScore/rest/play/", jsonValue)
if err != nil {
return errors.New(errors.HiScore, err)
return errors.Errorf("hiscore: %v", err)
}
switch statusCode {
@ -91,7 +91,7 @@ func (sess *Session) EndSession(playTime time.Duration) error {
// hiscore has been posted
default:
err = fmt.Errorf("register hiscore: unexpected response from HiScore server [%d: %s]", statusCode, response)
return errors.New(errors.HiScore, err)
return errors.Errorf("hiscore: %v", err)
}
return nil

View file

@ -32,7 +32,7 @@ func SetServer(input io.Reader, output io.Writer, server string) error {
// get reference to hiscore preferences
prefs, err := newPreferences()
if err != nil {
return errors.New(errors.HiScore, err)
return errors.Errorf("hiscore: %v", err)
}
// server has not been provided so prompt for it
@ -42,7 +42,7 @@ func SetServer(input io.Reader, output io.Writer, server string) error {
b = make([]byte, 255)
_, err := input.Read(b)
if err != nil {
return errors.New(errors.HiScore, err)
return errors.Errorf("hiscore: %v", err)
}
server = string(b)
}
@ -53,13 +53,13 @@ func SetServer(input io.Reader, output io.Writer, server string) error {
// parse entered url
url, err := url.Parse(server)
if err != nil {
return errors.New(errors.HiScore, err)
return errors.Errorf("hiscore: %v", err)
}
// error on path, but allow a single slash (by removing it)
if url.Path != "" {
if url.Path != "/" {
return errors.New(errors.HiScore, "do not include path in server setting")
return errors.Errorf("hiscore: %v", "do not include path in server setting")
}
}
@ -82,12 +82,12 @@ func Login(input io.Reader, output io.Writer, username string) error {
// get reference to hiscore preferences
prefs, err := newPreferences()
if err != nil {
return errors.New(errors.HiScore, err)
return errors.Errorf("hiscore: %v", err)
}
// we can't login unless highscore server has been specified
if prefs.Server.Get() == "" {
return errors.New(errors.HiScore, "no highscore server available")
return errors.Errorf("hiscore: %v", "no highscore server available")
}
// prompt for username if it has not been supplied
@ -97,7 +97,7 @@ func Login(input io.Reader, output io.Writer, username string) error {
b = make([]byte, 255)
_, err := input.Read(b)
if err != nil {
return errors.New(errors.HiScore, err)
return errors.Errorf("hiscore: %v", err)
}
username = strings.Split(string(b), "\n")[0]
}
@ -111,7 +111,7 @@ func Login(input io.Reader, output io.Writer, username string) error {
b = make([]byte, 255)
_, err = input.Read(b)
if err != nil {
return errors.New(errors.HiScore, err)
return errors.Errorf("hiscore: %v", err)
}
password := strings.Split(string(b), "\n")[0]
@ -120,20 +120,20 @@ func Login(input io.Reader, output io.Writer, username string) error {
data := url.Values{"username": {username}, "password": {password}}
resp, err := cl.PostForm(fmt.Sprintf("%s/rest-auth/login/", prefs.Server), data)
if err != nil {
return errors.New(errors.HiScore, err)
return errors.Errorf("hiscore: %v", err)
}
// get response
response, err := ioutil.ReadAll(resp.Body)
if err != nil {
return errors.New(errors.HiScore, err)
return errors.Errorf("hiscore: %v", err)
}
// unmarshal response
var key map[string]string
err = json.Unmarshal(response, &key)
if err != nil {
return errors.New(errors.HiScore, err)
return errors.Errorf("hiscore: %v", err)
}
// update authentication key and save changes
@ -146,7 +146,7 @@ func Logoff() error {
// get reference to hiscore preferences
prefs, err := newPreferences()
if err != nil {
return errors.New(errors.HiScore, err)
return errors.Errorf("hiscore: %v", err)
}
// blank authentication key and save changes

View file

@ -38,7 +38,7 @@ func Lint(dsm *disassembly.Disassembly, output io.Writer) error {
// create a new iteration for the bank
bitr, _, err := dsm.NewBankIteration(disassembly.EntryLevelBlessed, b)
if err != nil {
return errors.New(errors.Linter, err)
return errors.Errorf("linter: %v", err)
}
// iterate through disassembled bank

View file

@ -42,16 +42,16 @@ func CartridgeMemory(mem *cartridge.Cartridge, patchFile string) (bool, error) {
p, err := paths.ResourcePath(patchPath, patchFile)
if err != nil {
return false, errors.New(errors.PatchError, err)
return false, errors.Errorf("patch: %v", err)
}
f, err := os.Open(p)
if err != nil {
switch err.(type) {
case *os.PathError:
return false, errors.New(errors.PatchError, fmt.Sprintf("patch file not found (%s)", p))
return false, errors.Errorf("patch: %v", fmt.Sprintf("patch file not found (%s)", p))
}
return false, errors.New(errors.PatchError, err)
return false, errors.Errorf("patch: %v", err)
}
defer f.Close()
@ -118,7 +118,7 @@ func CartridgeMemory(mem *cartridge.Cartridge, patchFile string) (bool, error) {
// patch memory
err = mem.Patch(int(offset), uint8(v))
if err != nil {
return patched, errors.New(errors.PatchError, err)
return patched, errors.Errorf("patch: %v", err)
}
patched = true

View file

@ -36,25 +36,25 @@ func Check(output io.Writer, profile bool, tv television.Television, runTime str
// create vcs using the tv created above
vcs, err := hardware.NewVCS(tv)
if err != nil {
return errors.New(errors.PerformanceError, err)
return errors.Errorf("performance; %v", err)
}
// attach cartridge to te vcs
err = setup.AttachCartridge(vcs, cartload)
if err != nil {
return errors.New(errors.PerformanceError, err)
return errors.Errorf("performance; %v", err)
}
// parse supplied duration
duration, err := time.ParseDuration(runTime)
if err != nil {
return errors.New(errors.PerformanceError, err)
return errors.Errorf("performance; %v", err)
}
// get starting frame number (should be 0)
startFrame, err := tv.GetState(television.ReqFramenum)
if err != nil {
return errors.New(errors.PerformanceError, err)
return errors.Errorf("performance; %v", err)
}
// run for specified period of time
@ -99,7 +99,7 @@ func Check(output io.Writer, profile bool, tv television.Television, runTime str
}
})
if err != nil {
return errors.New(errors.PerformanceError, err)
return errors.Errorf("performance; %v", err)
}
return nil
}
@ -112,13 +112,13 @@ func Check(output io.Writer, profile bool, tv television.Television, runTime str
err = runner()
}
if err != nil {
return errors.New(errors.PerformanceError, err)
return errors.Errorf("performance; %v", err)
}
// get ending frame number
endFrame, err := vcs.TV.GetState(television.ReqFramenum)
if err != nil {
return errors.New(errors.PerformanceError, err)
return errors.Errorf("performance; %v", err)
}
// calculate performance

View file

@ -28,11 +28,11 @@ func ProfileCPU(outFile string, run func() error) error {
// write cpu profile
f, err := os.Create(outFile)
if err != nil {
return errors.New(errors.PerformanceError, err)
return errors.Errorf("performance; %v", err)
}
err = pprof.StartCPUProfile(f)
if err != nil {
return errors.New(errors.PerformanceError, err)
return errors.Errorf("performance; %v", err)
}
defer pprof.StopCPUProfile()
@ -43,12 +43,12 @@ func ProfileCPU(outFile string, run func() error) error {
func ProfileMem(outFile string) error {
f, err := os.Create(outFile)
if err != nil {
return errors.New(errors.PerformanceError, err)
return errors.Errorf("performance; %v", err)
}
runtime.GC()
err = pprof.WriteHeapProfile(f)
if err != nil {
return errors.New(errors.PerformanceError, err)
return errors.Errorf("performance; %v", err)
}
f.Close()

View file

@ -59,7 +59,7 @@ func Play(tv television.Television, scr gui.GUI, newRecording bool, cartload car
// do not allow this if a new recording has been requested
if newRecording {
return errors.New(errors.PlayError, "cannot make a new recording using a playback file")
return errors.Errorf("playmode: %v", "cannot make a new recording using a playback file")
}
recording = cartload.Filename
@ -89,7 +89,7 @@ func Play(tv television.Television, scr gui.GUI, newRecording bool, cartload car
vcs, err := hardware.NewVCS(tv)
if err != nil {
return errors.New(errors.PlayError, err)
return errors.Errorf("playmode: %v", err)
}
scr.ReqFeature(gui.ReqAddVCS, vcs)
@ -97,7 +97,7 @@ func Play(tv television.Television, scr gui.GUI, newRecording bool, cartload car
if useSavekey {
err = vcs.RIOT.Ports.AttachPlayer(ports.Player1ID, savekey.NewSaveKey)
if err != nil {
return errors.New(errors.PlayError, err)
return errors.Errorf("playmode: %v", err)
}
}
@ -116,7 +116,7 @@ func Play(tv television.Television, scr gui.GUI, newRecording bool, cartload car
// prepare new recording
rec, err := recorder.NewRecorder(recording, vcs)
if err != nil {
return errors.New(errors.PlayError, err)
return errors.Errorf("playmode: %v", err)
}
// making sure we end the recording gracefully when we leave the function
@ -128,7 +128,7 @@ func Play(tv television.Television, scr gui.GUI, newRecording bool, cartload car
// setup because we want to catch any setup events in the recording
err = setup.AttachCartridge(vcs, cartload)
if err != nil {
return errors.New(errors.PlayError, err)
return errors.Errorf("playmode: %v", err)
}
} else if recording != "" {
@ -145,7 +145,7 @@ func Play(tv television.Television, scr gui.GUI, newRecording bool, cartload car
// will be applied that way
err = vcs.AttachCartridge(plb.CartLoad)
if err != nil {
return errors.New(errors.PlayError, err)
return errors.Errorf("playmode: %v", err)
}
// the following will fail if the recording was made with different tv
@ -154,7 +154,7 @@ func Play(tv television.Television, scr gui.GUI, newRecording bool, cartload car
// another television implementation.
err = plb.AttachToVCS(vcs)
if err != nil {
return errors.New(errors.PlayError, err)
return errors.Errorf("playmode: %v", err)
}
} else {
@ -163,7 +163,7 @@ func Play(tv television.Television, scr gui.GUI, newRecording bool, cartload car
err = setup.AttachCartridge(vcs, cartload)
if err != nil {
return errors.New(errors.PlayError, err)
return errors.Errorf("playmode: %v", err)
}
// apply patch if requested. note that this will be in addition to any
@ -171,7 +171,7 @@ func Play(tv television.Television, scr gui.GUI, newRecording bool, cartload car
if patchFile != "" {
_, err := patch.CartridgeMemory(vcs.Mem.Cart, patchFile)
if err != nil {
return errors.New(errors.PlayError, err)
return errors.Errorf("playmode: %v", err)
}
}
}
@ -186,20 +186,20 @@ func Play(tv television.Television, scr gui.GUI, newRecording bool, cartload car
// connect gui
err = scr.ReqFeature(gui.ReqSetEventChan, pl.guiChan)
if err != nil {
return errors.New(errors.PlayError, err)
return errors.Errorf("playmode: %v", err)
}
// request television visibility
err = scr.ReqFeature(gui.ReqSetVisibility, true)
if err != nil {
return errors.New(errors.PlayError, err)
return errors.Errorf("playmode: %v", err)
}
// if a waitForEmulationStart channel has been created then halt the
// goroutine until we recieve a non-error signal
if waitForEmulationStart != nil {
if err := <-waitForEmulationStart; err != nil {
return errors.New(errors.PlayError, err)
return errors.Errorf("playmode: %v", err)
}
}
@ -217,12 +217,12 @@ func Play(tv television.Television, scr gui.GUI, newRecording bool, cartload car
if hiscoreServer {
sess, err = hiscore.NewSession()
if err != nil {
return errors.New(errors.PlayError, err)
return errors.Errorf("playmode: %v", err)
}
err = sess.StartSession(cartload.ShortName(), vcs.Mem.Cart.Hash)
if err != nil {
return errors.New(errors.PlayError, err)
return errors.Errorf("playmode: %v", err)
}
}
@ -238,17 +238,17 @@ func Play(tv television.Television, scr gui.GUI, newRecording bool, cartload car
// send to high score server
if hiscoreServer {
if err := sess.EndSession(playTime); err != nil {
return errors.New(errors.PlayError, err)
return errors.Errorf("playmode: %v", err)
}
}
if err != nil {
if errors.Has(err, errors.PowerOff) {
if errors.Has(err, ports.PowerOff) {
// PowerOff is okay and is to be expected. swallow the error
// message and return as normal
return nil
}
return errors.New(errors.PlayError, err)
return errors.Errorf("playmode: %v", err)
}
return nil

View file

@ -83,7 +83,7 @@ func NewDisk(path string) (*Disk, error) {
func (dsk *Disk) Add(key string, p pref) error {
for _, r := range key {
if !(r == '.' || unicode.IsLetter(r)) {
return errors.New(errors.Prefs, fmt.Sprintf("illegal character [%c] in key string [%s]", r, key))
return errors.Errorf("prefs: %v", fmt.Errorf("illegal character [%c] in key string [%s]", r, key))
}
}
@ -91,6 +91,11 @@ func (dsk *Disk) Add(key string, p pref) error {
return nil
}
// Sentinal error returned if prefs file does not exist
const (
NoPrefsFile = "prefs: file does not exist (%s)"
)
// Save current preference values to disk
func (dsk *Disk) Save() error {
// load entirity of currently saved prefs file to a temporary entryMap
@ -99,7 +104,7 @@ func (dsk *Disk) Save() error {
// load *all* existing entries to temporary entryMap
_, err := load(dsk.path, &entries, false)
if err != nil {
if !errors.Is(err, errors.PrefsNoFile) {
if !errors.Is(err, NoPrefsFile) {
return err
}
}
@ -113,7 +118,7 @@ func (dsk *Disk) Save() error {
// create a new prefs file
f, err := os.Create(dsk.path)
if err != nil {
return errors.New(errors.Prefs, err)
return errors.Errorf("prefs: %v", err)
}
defer f.Close()
@ -123,19 +128,19 @@ func (dsk *Disk) Save() error {
// add warning label
n, err = fmt.Fprintf(f, fmt.Sprintf("%s\n", WarningBoilerPlate))
if err != nil {
return errors.New(errors.Prefs, err)
return errors.Errorf("prefs: %v", err)
}
if n != len(WarningBoilerPlate)+1 {
return errors.New(errors.Prefs, "incorrect number of characters writtent to file")
return errors.Errorf("prefs: %v", "incorrect number of characters writtent to file")
}
// write entries (combination of old and live entries) to disk
n, err = fmt.Fprintf(f, entries.String())
if err != nil {
return errors.New(errors.Prefs, err)
return errors.Errorf("prefs: %v", err)
}
if n != len(entries.String()) {
return errors.New(errors.Prefs, "incorrect number of characters writtent to file")
return errors.Errorf("prefs: %v", "incorrect number of characters writtent to file")
}
return nil
@ -177,9 +182,9 @@ func load(path string, entries *entryMap, limit bool) (int, error) {
if err != nil {
switch err.(type) {
case *os.PathError:
return numLoaded, errors.New(errors.PrefsNoFile, path)
return numLoaded, errors.Errorf(NoPrefsFile, path)
}
return numLoaded, errors.New(errors.Prefs, err)
return numLoaded, errors.Errorf("prefs: %v", err)
}
defer f.Close()
@ -189,7 +194,7 @@ func load(path string, entries *entryMap, limit bool) (int, error) {
// check validity of file by checking the first line
scanner.Scan()
if scanner.Text() != WarningBoilerPlate {
return 0, errors.New(errors.PrefsNotValid, path)
return 0, errors.Errorf("prefs: %v", fmt.Errorf("not a valid prefs file (%s)", path))
}
// key and value strings
@ -213,7 +218,7 @@ func load(path string, entries *entryMap, limit bool) (int, error) {
if p, ok := (*entries)[k]; ok {
err = p.Set(v)
if err != nil {
return numLoaded, errors.New(errors.Prefs, err)
return numLoaded, errors.Errorf("prefs: %v", err)
}
numLoaded++
} else if !limit {
@ -221,7 +226,7 @@ func load(path string, entries *entryMap, limit bool) (int, error) {
var dummy String
err = dummy.Set(v)
if err != nil {
return numLoaded, errors.New(errors.Prefs, err)
return numLoaded, errors.Errorf("prefs: %v", err)
}
(*entries)[k] = &dummy
}

View file

@ -59,7 +59,7 @@ func (p *Bool) Set(v Value) error {
p.value = false
}
default:
return errors.New(errors.Prefs, fmt.Sprintf("cannot convert %T to prefs.Bool", v))
return errors.Errorf("prefs: %v", fmt.Errorf("cannot convert %T to prefs.Bool", v))
}
if p.callback != nil {
@ -149,10 +149,10 @@ func (p *Int) Set(v Value) error {
var err error
p.value, err = strconv.Atoi(v)
if err != nil {
return errors.New(errors.Prefs, fmt.Sprintf("cannot convert %T to prefs.Int", v))
return errors.Errorf("prefs: %v", fmt.Errorf("cannot convert %T to prefs.Int", v))
}
default:
return errors.New(errors.Prefs, fmt.Sprintf("cannot convert %T to prefs.Int", v))
return errors.Errorf("prefs: %v", fmt.Errorf("cannot convert %T to prefs.Int", v))
}
if p.callback != nil {

View file

@ -74,12 +74,12 @@ func (rec *Recorder) writeHeader() error {
if err != nil {
rec.output.Close()
return errors.New(errors.RecordingError, err)
return errors.Errorf("recorder: %v", err)
}
if n != len(line) {
rec.output.Close()
return errors.New(errors.RecordingError, "output truncated")
return errors.Errorf("recorder: output truncated")
}
return nil
@ -87,7 +87,7 @@ func (rec *Recorder) writeHeader() error {
func (plb *Playback) readHeader(lines []string) error {
if lines[lineMagicString] != magicString {
return errors.New(errors.PlaybackError, fmt.Sprintf("not a valid playback transcript (%s)", plb.transcript))
return errors.Errorf("playback: not a valid transcript (%s)", plb.transcript)
}
// read header

View file

@ -74,7 +74,7 @@ func (plb Playback) String() string {
func (plb Playback) EndFrame() (bool, error) {
currFrame, err := plb.digest.GetState(television.ReqFramenum)
if err != nil {
return false, errors.New(errors.RegressionPlaybackError, err)
return false, errors.Errorf("playback: %v", err)
}
if currFrame > plb.endFrame {
@ -96,15 +96,15 @@ func NewPlayback(transcript string) (*Playback, error) {
tf, err := os.Open(transcript)
if err != nil {
return nil, errors.New(errors.PlaybackError, err)
return nil, errors.Errorf("playback: %v", err)
}
buffer, err := ioutil.ReadAll(tf)
if err != nil {
return nil, errors.New(errors.PlaybackError, err)
return nil, errors.Errorf("playback: %v", err)
}
err = tf.Close()
if err != nil {
return nil, errors.New(errors.PlaybackError, err)
return nil, errors.Errorf("playback: %v", err)
}
// convert file contents to an array of lines
@ -123,15 +123,13 @@ func NewPlayback(transcript string) (*Playback, error) {
// ignore lines that don't have enough fields
if len(toks) != numFields {
msg := fmt.Sprintf("expected %d fields at line %d", numFields, i+1)
return nil, errors.New(errors.PlaybackError, msg)
return nil, errors.Errorf("playback: expected %d fields at line %d", numFields, i+1)
}
// add a new playbackSequence for the id if it doesn't exist
n, err := strconv.Atoi(toks[fieldID])
if err != nil {
msg := fmt.Sprintf("%s line %d, col %d", err, i+1, len(strings.Join(toks[:fieldID+1], fieldSep)))
return nil, errors.New(errors.PlaybackError, msg)
return nil, errors.Errorf("playback: %s line %d, col %d", err, i+1, len(strings.Join(toks[:fieldID+1], fieldSep)))
}
// create a new entry and convert tokens accordingly
@ -170,8 +168,7 @@ func NewPlayback(transcript string) (*Playback, error) {
entry.frame, err = strconv.Atoi(toks[fieldFrame])
if err != nil {
msg := fmt.Sprintf("%s line %d, col %d", err, i+1, len(strings.Join(toks[:fieldFrame+1], fieldSep)))
return nil, errors.New(errors.PlaybackError, msg)
return nil, errors.Errorf("playback: %s line %d, col %d", err, i+1, len(strings.Join(toks[:fieldFrame+1], fieldSep)))
}
// assuming that frames are listed in order in the file. update
@ -180,14 +177,12 @@ func NewPlayback(transcript string) (*Playback, error) {
entry.scanline, err = strconv.Atoi(toks[fieldScanline])
if err != nil {
msg := fmt.Sprintf("%s line %d, col %d", err, i+1, len(strings.Join(toks[:fieldScanline+1], fieldSep)))
return nil, errors.New(errors.PlaybackError, msg)
return nil, errors.Errorf("playback: %s line %d, col %d", err, i+1, len(strings.Join(toks[:fieldScanline+1], fieldSep)))
}
entry.horizpos, err = strconv.Atoi(toks[fieldHorizPos])
if err != nil {
msg := fmt.Sprintf("%s line %d, col %d", err, i+1, len(strings.Join(toks[:fieldHorizPos+1], fieldSep)))
return nil, errors.New(errors.PlaybackError, msg)
return nil, errors.Errorf("playback: %s line %d, col %d", err, i+1, len(strings.Join(toks[:fieldHorizPos+1], fieldSep)))
}
entry.hash = toks[fieldHash]
@ -231,7 +226,7 @@ func parseEventData(value string) ports.EventData {
func (plb *Playback) AttachToVCS(vcs *hardware.VCS) error {
// check we're working with correct information
if vcs == nil || vcs.TV == nil {
return errors.New(errors.PlaybackError, "no playback hardware available")
return errors.Errorf("playback: no playback hardware available")
}
plb.vcs = vcs
@ -239,16 +234,14 @@ func (plb *Playback) AttachToVCS(vcs *hardware.VCS) error {
// specification. some combinations may work but there's no compelling
// reason to figure that out just now.
if plb.vcs.TV.SpecIDOnCreation() != plb.TVSpec {
return errors.New(errors.PlaybackError,
fmt.Sprintf("recording was made with the %s TV spec. trying to playback with a TV spec of %s.",
plb.TVSpec, vcs.TV.SpecIDOnCreation()))
return errors.Errorf("playback: recording was made with the %s TV spec. trying to playback with a TV spec of %s.", plb.TVSpec, vcs.TV.SpecIDOnCreation())
}
var err error
plb.digest, err = digest.NewVideo(plb.vcs.TV)
if err != nil {
return errors.New(errors.RecordingError, err)
return errors.Errorf("playback: %v", err)
}
// attach playback to all vcs ports
@ -257,6 +250,11 @@ func (plb *Playback) AttachToVCS(vcs *hardware.VCS) error {
return nil
}
// Sentinal error returned by GetPlayback if a hash error is encountered
const (
PlaybackHashError = "playback: hash error [line %d]"
)
// GetPlayback returns an event and source portID for an event occuring at the
// current TV frame/scanline/horizpos
func (plb *Playback) GetPlayback() (ports.PortID, ports.Event, ports.EventData, error) {
@ -268,15 +266,15 @@ func (plb *Playback) GetPlayback() (ports.PortID, ports.Event, ports.EventData,
// get current state of the television
frame, err := plb.vcs.TV.GetState(television.ReqFramenum)
if err != nil {
return ports.NoPortID, ports.NoEvent, nil, errors.New(errors.PlaybackError, err)
return ports.NoPortID, ports.NoEvent, nil, errors.Errorf("playback: %v", err)
}
scanline, err := plb.vcs.TV.GetState(television.ReqScanline)
if err != nil {
return ports.NoPortID, ports.NoEvent, nil, errors.New(errors.PlaybackError, err)
return ports.NoPortID, ports.NoEvent, nil, errors.Errorf("playback: %v", err)
}
horizpos, err := plb.vcs.TV.GetState(television.ReqHorizPos)
if err != nil {
return ports.NoPortID, ports.NoEvent, nil, errors.New(errors.PlaybackError, err)
return ports.NoPortID, ports.NoEvent, nil, errors.Errorf("playback: %v", err)
}
// compare current state with the recording
@ -284,7 +282,7 @@ func (plb *Playback) GetPlayback() (ports.PortID, ports.Event, ports.EventData,
if frame == entry.frame && scanline == entry.scanline && horizpos == entry.horizpos {
plb.seqCt++
if entry.hash != plb.digest.Hash() {
return ports.NoPortID, ports.NoEvent, nil, errors.New(errors.PlaybackHashError, fmt.Sprintf("line %d", entry.line))
return ports.NoPortID, ports.NoEvent, nil, errors.Errorf(PlaybackHashError, entry.line)
}
return entry.portID, entry.event, entry.value, nil
}

View file

@ -48,7 +48,7 @@ func NewRecorder(transcript string, vcs *hardware.VCS) (*Recorder, error) {
// check we're working with correct information
if vcs == nil || vcs.TV == nil {
return nil, errors.New(errors.RecordingError, "hardware is not suitable for recording")
return nil, errors.Errorf("recorder: hardware is not suitable for recording")
}
rec := &Recorder{vcs: vcs}
@ -59,7 +59,7 @@ func NewRecorder(transcript string, vcs *hardware.VCS) (*Recorder, error) {
// video digester for playback verification
rec.digest, err = digest.NewVideo(vcs.TV)
if err != nil {
return nil, errors.New(errors.RecordingError, err)
return nil, errors.Errorf("recorder: %v", err)
}
// open file
@ -67,10 +67,10 @@ func NewRecorder(transcript string, vcs *hardware.VCS) (*Recorder, error) {
if os.IsNotExist(err) {
rec.output, err = os.Create(transcript)
if err != nil {
return nil, errors.New(errors.RecordingError, "can't create file")
return nil, errors.Errorf("recorder: can't create file")
}
} else {
return nil, errors.New(errors.RecordingError, "file already exists")
return nil, errors.Errorf("recorder: file already exists")
}
// delay writing of header until the first call to transcribe. we're
@ -90,12 +90,12 @@ func (rec *Recorder) End() error {
// write the power off event to the transcript
err := rec.RecordEvent(ports.PanelID, ports.PanelPowerOff, nil)
if err != nil {
return errors.New(errors.RecordingError, err)
return errors.Errorf("recorder: %v", err)
}
err = rec.output.Close()
if err != nil {
return errors.New(errors.RecordingError, err)
return errors.Errorf("recorder: %v", err)
}
return nil
@ -109,7 +109,7 @@ func (rec *Recorder) RecordEvent(id ports.PortID, event ports.Event, value ports
if !rec.headerWritten {
err = rec.writeHeader()
if err != nil {
return errors.New(errors.RecordingError, err)
return errors.Errorf("recorder: %v", err)
}
rec.headerWritten = true
}
@ -121,11 +121,11 @@ func (rec *Recorder) RecordEvent(id ports.PortID, event ports.Event, value ports
// sanity checks
if rec.output == nil {
return errors.New(errors.RecordingError, "recording file is not open")
return errors.Errorf("recorder: recording file is not open")
}
if rec.vcs == nil || rec.vcs.TV == nil {
return errors.New(errors.RecordingError, "hardware is not suitable for recording")
return errors.Errorf("recorder: hardware is not suitable for recording")
}
// create line and write to file
@ -159,10 +159,10 @@ func (rec *Recorder) RecordEvent(id ports.PortID, event ports.Event, value ports
n, err := io.WriteString(rec.output, line)
if err != nil {
return errors.New(errors.RecordingError, err)
return errors.Errorf("recorder: %v", err)
}
if n != len(line) {
return errors.New(errors.RecordingError, "output truncated")
return errors.Errorf("recorder: output truncated")
}
return nil

View file

@ -60,10 +60,10 @@ func deserialiseLogEntry(fields database.SerialisedEntry) (database.Entry, error
// basic sanity check
if len(fields) > numLogFields {
return nil, errors.New(errors.RegressionLogError, "too many fields")
return nil, errors.Errorf("log: too many fields")
}
if len(fields) < numLogFields {
return nil, errors.New(errors.RegressionLogError, "too few fields")
return nil, errors.Errorf("log: too few fields")
}
// string fields need no conversion
@ -79,7 +79,7 @@ func deserialiseLogEntry(fields database.SerialisedEntry) (database.Entry, error
reg.NumFrames, err = strconv.Atoi(fields[logFieldNumFrames])
if err != nil {
msg := fmt.Sprintf("invalid numFrames field [%s]", fields[logFieldNumFrames])
return nil, errors.New(errors.RegressionLogError, msg)
return nil, errors.Errorf("log: %v", msg)
}
return reg, nil
@ -126,19 +126,19 @@ func (reg *LogRegression) regress(newRegression bool, output io.Writer, msg stri
// create headless television. we'll use this to initialise the digester
tv, err := television.NewTelevision(reg.TVtype)
if err != nil {
return false, "", errors.New(errors.RegressionLogError, err)
return false, "", errors.Errorf("log: %v", err)
}
defer tv.End()
// create VCS and attach cartridge
vcs, err := hardware.NewVCS(tv)
if err != nil {
return false, "", errors.New(errors.RegressionLogError, err)
return false, "", errors.Errorf("log: %v", err)
}
err = setup.AttachCartridge(vcs, reg.CartLoad)
if err != nil {
return false, "", errors.New(errors.RegressionLogError, err)
return false, "", errors.Errorf("log: %v", err)
}
// display ticker for progress meter
@ -163,7 +163,7 @@ func (reg *LogRegression) regress(newRegression bool, output io.Writer, msg stri
})
if err != nil {
return false, "", errors.New(errors.RegressionLogError, err)
return false, "", errors.Errorf("log: %v", err)
}
// get hash of log output

View file

@ -27,6 +27,7 @@ import (
"github.com/jetsetilly/gopher2600/digest"
"github.com/jetsetilly/gopher2600/errors"
"github.com/jetsetilly/gopher2600/hardware"
"github.com/jetsetilly/gopher2600/hardware/riot/ports"
"github.com/jetsetilly/gopher2600/recorder"
"github.com/jetsetilly/gopher2600/television"
)
@ -53,10 +54,10 @@ func deserialisePlaybackEntry(fields database.SerialisedEntry) (database.Entry,
// basic sanity check
if len(fields) > numPlaybackFields {
return nil, errors.New(errors.RegressionPlaybackError, "too many fields")
return nil, errors.Errorf("playback: too many fields")
}
if len(fields) < numPlaybackFields {
return nil, errors.New(errors.RegressionPlaybackError, "too few fields")
return nil, errors.Errorf("playback: too few fields")
}
// string fields need no conversion
@ -105,28 +106,28 @@ func (reg *PlaybackRegression) regress(newRegression bool, output io.Writer, msg
plb, err := recorder.NewPlayback(reg.Script)
if err != nil {
return false, "", errors.New(errors.RegressionPlaybackError, err)
return false, "", errors.Errorf("playback: %v", err)
}
tv, err := television.NewTelevision(plb.TVSpec)
if err != nil {
return false, "", errors.New(errors.RegressionPlaybackError, err)
return false, "", errors.Errorf("playback: %v", err)
}
defer tv.End()
_, err = digest.NewVideo(tv)
if err != nil {
return false, "", errors.New(errors.RegressionPlaybackError, err)
return false, "", errors.Errorf("playback: %v", err)
}
vcs, err := hardware.NewVCS(tv)
if err != nil {
return false, "", errors.New(errors.RegressionPlaybackError, err)
return false, "", errors.Errorf("playback: %v", err)
}
err = plb.AttachToVCS(vcs)
if err != nil {
return false, "", errors.New(errors.RegressionPlaybackError, err)
return false, "", errors.Errorf("playback: %v", err)
}
// not using setup.AttachCartridge. if the playback was recorded with setup
@ -134,7 +135,7 @@ func (reg *PlaybackRegression) regress(newRegression bool, output io.Writer, msg
// will be applied that way
err = vcs.AttachCartridge(plb.CartLoad)
if err != nil {
return false, "", errors.New(errors.RegressionPlaybackError, err)
return false, "", errors.Errorf("playback: %v", err)
}
// prepare ticker for progress meter
@ -145,10 +146,10 @@ func (reg *PlaybackRegression) regress(newRegression bool, output io.Writer, msg
err = vcs.Run(func() (bool, error) {
hasEnded, err := plb.EndFrame()
if err != nil {
return false, errors.New(errors.RegressionPlaybackError, err)
return false, errors.Errorf("playback: %v", err)
}
if hasEnded {
return false, errors.New(errors.RegressionPlaybackError, "playback has not ended as expected")
return false, errors.Errorf("playback: ended unexpectedly")
}
// display progress meter every 1 second
@ -161,9 +162,9 @@ func (reg *PlaybackRegression) regress(newRegression bool, output io.Writer, msg
})
if err != nil {
if errors.Has(err, errors.PowerOff) {
if errors.Has(err, ports.PowerOff) {
// PowerOff is okay and is to be expected
} else if errors.Has(err, errors.PlaybackHashError) {
} else if errors.Has(err, recorder.PlaybackHashError) {
// PlaybackHashError means that a screen digest somewhere in the
// playback script did not work. filter error and return false to
// indicate failure
@ -173,7 +174,7 @@ func (reg *PlaybackRegression) regress(newRegression bool, output io.Writer, msg
failm := fmt.Sprintf("%v: at fr=%d, sl=%d, hp=%d", err, fr, sl, hp)
return false, failm, nil
} else {
return false, "", errors.New(errors.RegressionPlaybackError, err)
return false, "", errors.Errorf("playback: %v", err)
}
}
@ -183,7 +184,7 @@ func (reg *PlaybackRegression) regress(newRegression bool, output io.Writer, msg
// create a unique filename
newScript, err := uniqueFilename("playback", plb.CartLoad)
if err != nil {
return false, "", errors.New(errors.RegressionPlaybackError, err)
return false, "", errors.Errorf("playback: %v", err)
}
// check that the filename is unique
@ -191,32 +192,28 @@ func (reg *PlaybackRegression) regress(newRegression bool, output io.Writer, msg
// no need to bother with returned error. nf tells us everything we
// need
if nf != nil {
msg := fmt.Sprintf("script already exists (%s)", newScript)
return false, "", errors.New(errors.RegressionPlaybackError, msg)
return false, "", errors.Errorf("playback: script already exists (%s)", newScript)
}
nf.Close()
// create new file
nf, err = os.Create(newScript)
if err != nil {
msg := fmt.Sprintf("error copying playback script: %s", err)
return false, "", errors.New(errors.RegressionPlaybackError, msg)
return false, "", errors.Errorf("playback: while copying playback script: %v", err)
}
defer nf.Close()
// open old file
of, err := os.Open(reg.Script)
if err != nil {
msg := fmt.Sprintf("error copying playback script: %s", err)
return false, "", errors.New(errors.RegressionPlaybackError, msg)
return false, "", errors.Errorf("playback: while copying playback script: %v", err)
}
defer of.Close()
// copy old file to new file
_, err = io.Copy(nf, of)
if err != nil {
msg := fmt.Sprintf("error copying playback script: %s", err)
return false, "", errors.New(errors.RegressionPlaybackError, msg)
return false, "", errors.Errorf("playback: while copying playback script: %v", err)
}
// update script name in regression type

View file

@ -69,7 +69,7 @@ func initDBSession(db *database.Session) error {
// make sure regression script directory exists
// if err := os.MkdirAll(paths.ResourcePath(regressionScripts), 0755); err != nil {
// msg := fmt.Sprintf("regression script directory: %s", err)
// return errors.New(errors.RegressionError, msg)
// return errors.Errorf("regression: %v", msg)
// }
return nil
@ -78,12 +78,12 @@ func initDBSession(db *database.Session) error {
// RegressList displays all entries in the database
func RegressList(output io.Writer) error {
if output == nil {
return errors.New(errors.PanicError, "RegressList()", "io.Writer should not be nil (use a nopWriter)")
return fmt.Errorf("regression: list: io.Writer should not be nil (use a nopWriter)")
}
dbPth, err := paths.ResourcePath("", regressionDBFile)
if err != nil {
return errors.New(errors.RegressionError, err)
return errors.Errorf("regression: %v", err)
}
db, err := database.StartSession(dbPth, database.ActivityReading, initDBSession)
@ -103,12 +103,12 @@ func RegressAdd(output io.Writer, reg Regressor) error {
defer rand.Seed(int64(time.Now().Second()))
if output == nil {
return errors.New(errors.PanicError, "RegressAdd()", "io.Writer should not be nil (use nopWriter)")
return fmt.Errorf("regression: add: io.Writer should not be nil (use a nopWriter)")
}
dbPth, err := paths.ResourcePath("", regressionDBFile)
if err != nil {
return errors.New(errors.RegressionError, err)
return errors.Errorf("regression: %v", err)
}
db, err := database.StartSession(dbPth, database.ActivityCreating, initDBSession)
@ -132,18 +132,17 @@ func RegressAdd(output io.Writer, reg Regressor) error {
// RegressDelete removes a cartridge from the regression db
func RegressDelete(output io.Writer, confirmation io.Reader, key string) error {
if output == nil {
return errors.New(errors.PanicError, "RegressDelete()", "io.Writer should not be nil (use nopWriter)")
return fmt.Errorf("regression: delete: io.Writer should not be nil (use a nopWriter)")
}
v, err := strconv.Atoi(key)
if err != nil {
msg := fmt.Sprintf("invalid key [%s]", key)
return errors.New(errors.RegressionError, msg)
return errors.Errorf("regression: invalid key [%s]", key)
}
dbPth, err := paths.ResourcePath("", regressionDBFile)
if err != nil {
return errors.New(errors.RegressionError, err)
return errors.Errorf("regression: %v", err)
}
db, err := database.StartSession(dbPth, database.ActivityModifying, initDBSession)
@ -154,13 +153,7 @@ func RegressDelete(output io.Writer, confirmation io.Reader, key string) error {
ent, err := db.SelectKeys(nil, v)
if err != nil {
if !errors.Is(err, errors.DatabaseSelectEmpty) {
return err
}
// select returned no entries; create DatabaseKeyError and wrap it in a
// RegressionError
return errors.New(errors.RegressionError, errors.New(errors.DatabaseKeyError, v))
return errors.Errorf("regression: %v", err)
}
output.Write([]byte(fmt.Sprintf("%s\ndelete? (y/n): ", ent)))
@ -193,17 +186,17 @@ func RegressRunTests(output io.Writer, verbose bool, failOnError bool, filterKey
defer rand.Seed(int64(time.Now().Second()))
if output == nil {
return errors.New(errors.PanicError, "RegressRunEntries()", "io.Writer should not be nil (use nopWriter)")
return fmt.Errorf("regression: run: io.Writer should not be nil (use a nopWriter)")
}
dbPth, err := paths.ResourcePath("", regressionDBFile)
if err != nil {
return errors.New(errors.RegressionError, err)
return errors.Errorf("regression: %v", err)
}
db, err := database.StartSession(dbPth, database.ActivityReading, initDBSession)
if err != nil {
return errors.New(errors.RegressionError, err)
return errors.Errorf("regression: %v", err)
}
defer db.EndSession(false)
@ -212,8 +205,7 @@ func RegressRunTests(output io.Writer, verbose bool, failOnError bool, filterKey
for k := range filterKeys {
v, err := strconv.Atoi(filterKeys[k])
if err != nil {
msg := fmt.Sprintf("invalid key [%s]", filterKeys[k])
return errors.New(errors.RegressionError, msg)
return errors.Errorf("regression: invalid key [%s]", filterKeys[k])
}
keysV = append(keysV, v)
}
@ -238,7 +230,7 @@ func RegressRunTests(output io.Writer, verbose bool, failOnError bool, filterKey
// datbase entry should also satisfy Regressor interface
reg, ok := ent.(Regressor)
if !ok {
return false, errors.New(errors.PanicError, "RegressRunTests()", "database entry does not satisfy Regressor interface")
return false, fmt.Errorf("regression: run: database entry does not satisfy Regressor interface")
}
// run regress() function with message. message does not have a

View file

@ -20,7 +20,6 @@ import (
"time"
"github.com/jetsetilly/gopher2600/cartridgeloader"
"github.com/jetsetilly/gopher2600/errors"
"github.com/jetsetilly/gopher2600/paths"
)
@ -33,7 +32,7 @@ func uniqueFilename(prepend string, cartload cartridgeloader.Loader) (string, er
scrPth, err := paths.ResourcePath(regressionScripts, newScript)
if err != nil {
return "", errors.New(errors.RegressionError, err)
return "", err
}
return scrPth, nil

View file

@ -67,10 +67,10 @@ func deserialiseVideoEntry(fields database.SerialisedEntry) (database.Entry, err
// basic sanity check
if len(fields) > numVideoFields {
return nil, errors.New(errors.RegressionVideoError, "too many fields")
return nil, errors.Errorf("video: too many fields")
}
if len(fields) < numVideoFields {
return nil, errors.New(errors.RegressionVideoError, "too few fields")
return nil, errors.Errorf("video: too few fields")
}
// string fields need no conversion
@ -85,8 +85,7 @@ func deserialiseVideoEntry(fields database.SerialisedEntry) (database.Entry, err
// convert number of frames field
reg.NumFrames, err = strconv.Atoi(fields[videoFieldNumFrames])
if err != nil {
msg := fmt.Sprintf("invalid numFrames field [%s]", fields[videoFieldNumFrames])
return nil, errors.New(errors.RegressionVideoError, msg)
return nil, errors.Errorf("video: invalid numFrames field [%s]", fields[videoFieldNumFrames])
}
// handle state field
@ -102,8 +101,7 @@ func deserialiseVideoEntry(fields database.SerialisedEntry) (database.Entry, err
case "CPU":
reg.State = StateCPU
default:
msg := fmt.Sprintf("invalid state field [%s]", fields[videoFieldState])
return nil, errors.New(errors.RegressionVideoError, msg)
return nil, errors.Errorf("video: invalid state field [%s]", fields[videoFieldState])
}
// state options
@ -112,7 +110,7 @@ func deserialiseVideoEntry(fields database.SerialisedEntry) (database.Entry, err
// and state file field
if fields[videoFieldStateFile] != "" {
if reg.State == StateNone {
return nil, errors.New(errors.RegressionVideoError, "invalid state file field: no state type specifier")
return nil, errors.Errorf("video: invalid state file field: no state type specifier")
}
reg.stateFile = fields[videoFieldStateFile]
}
@ -184,24 +182,24 @@ func (reg *VideoRegression) regress(newRegression bool, output io.Writer, msg st
// create headless television. we'll use this to initialise the digester
tv, err := television.NewTelevision(reg.TVtype)
if err != nil {
return false, "", errors.New(errors.RegressionVideoError, err)
return false, "", errors.Errorf("video: %v", err)
}
defer tv.End()
dig, err := digest.NewVideo(tv)
if err != nil {
return false, "", errors.New(errors.RegressionVideoError, err)
return false, "", errors.Errorf("video: %v", err)
}
// create VCS and attach cartridge
vcs, err := hardware.NewVCS(tv)
if err != nil {
return false, "", errors.New(errors.RegressionVideoError, err)
return false, "", errors.Errorf("video: %v", err)
}
err = setup.AttachCartridge(vcs, reg.CartLoad)
if err != nil {
return false, "", errors.New(errors.RegressionVideoError, err)
return false, "", errors.Errorf("video: %v", err)
}
// list of state information. we'll either save this in the event of
@ -252,7 +250,7 @@ func (reg *VideoRegression) regress(newRegression bool, output io.Writer, msg st
})
if err != nil {
return false, "", errors.New(errors.RegressionVideoError, err)
return false, "", errors.Errorf("video: %v", err)
}
if newRegression {
@ -262,7 +260,7 @@ func (reg *VideoRegression) regress(newRegression bool, output io.Writer, msg st
// create a unique filename
reg.stateFile, err = uniqueFilename("state", reg.CartLoad)
if err != nil {
return false, "", errors.New(errors.RegressionVideoError, err)
return false, "", errors.Errorf("video: %v", err)
}
// check that the filename is unique
@ -271,24 +269,21 @@ func (reg *VideoRegression) regress(newRegression bool, output io.Writer, msg st
// no need to bother with returned error. nf tells us everything we
// need
if nf != nil {
msg := fmt.Sprintf("state recording file already exists (%s)", reg.stateFile)
return false, "", errors.New(errors.RegressionVideoError, msg)
return false, "", errors.Errorf("video: state recording file already exists (%s)", reg.stateFile)
}
nf.Close()
// create new file
nf, err = os.Create(reg.stateFile)
if err != nil {
msg := fmt.Sprintf("error creating state recording file: %s", err)
return false, "", errors.New(errors.RegressionVideoError, msg)
return false, "", errors.Errorf("video: error creating state recording file: %v", err)
}
defer nf.Close()
for i := range state {
s := fmt.Sprintf("%s\n", state[i])
if n, err := nf.WriteString(s); err != nil || len(s) != n {
msg := fmt.Sprintf("error writing state recording file: %s", err)
return false, "", errors.New(errors.RegressionVideoError, msg)
return false, "", errors.Errorf("video: error writing state recording file: %v", err)
}
}
}
@ -303,8 +298,7 @@ func (reg *VideoRegression) regress(newRegression bool, output io.Writer, msg st
if reg.State != StateNone {
nf, err := os.Open(reg.stateFile)
if err != nil {
msg := fmt.Sprintf("old state recording file not present (%s)", reg.stateFile)
return false, "", errors.New(errors.RegressionVideoError, msg)
return false, "", errors.Errorf("video: old state recording file not present (%s)", reg.stateFile)
}
defer nf.Close()

View file

@ -16,7 +16,6 @@
package regression
import (
"fmt"
"strings"
"github.com/jetsetilly/gopher2600/errors"
@ -49,7 +48,7 @@ func NewStateType(state string) (StateType, error) {
case "CPU":
return StateCPU, nil
}
return StateNone, errors.New(errors.RegressionVideoError, fmt.Sprintf("unrecognised state type [%s]", state))
return StateNone, errors.Errorf("regression: video: unrecognised state type [%s]", state)
}
func (t StateType) String() string {

View file

@ -52,10 +52,10 @@ func deserialisePanelSetupEntry(fields database.SerialisedEntry) (database.Entry
// basic sanity check
if len(fields) > numPanelSetupFields {
return nil, errors.New(errors.SetupPanelError, "too many fields in panel entry")
return nil, errors.Errorf("panel: too many fields in panel entry")
}
if len(fields) < numPanelSetupFields {
return nil, errors.New(errors.SetupPanelError, "too few fields in panel entry")
return nil, errors.Errorf("panel: too few fields in panel entry")
}
var err error
@ -63,15 +63,15 @@ func deserialisePanelSetupEntry(fields database.SerialisedEntry) (database.Entry
set.cartHash = fields[panelSetupFieldCartHash]
if set.p0, err = strconv.ParseBool(fields[panelSetupFieldP0]); err != nil {
return nil, errors.New(errors.SetupPanelError, "invalid player 0 setting")
return nil, errors.Errorf("panel: invalid player 0 setting")
}
if set.p1, err = strconv.ParseBool(fields[panelSetupFieldP1]); err != nil {
return nil, errors.New(errors.SetupPanelError, "invalid player 1 setting")
return nil, errors.Errorf("panel: invalid player 1 setting")
}
if set.col, err = strconv.ParseBool(fields[panelSetupFieldCol]); err != nil {
return nil, errors.New(errors.SetupPanelError, "invalid color setting")
return nil, errors.Errorf("panel: invalid color setting")
}
set.notes = fields[panelSetupFieldNotes]

View file

@ -46,10 +46,10 @@ func deserialisePatchEntry(fields database.SerialisedEntry) (database.Entry, err
// basic sanity check
if len(fields) > numPatchFields {
return nil, errors.New(errors.SetupPatchError, "too many fields in patch entry")
return nil, errors.Errorf("patch: too many fields in patch entry")
}
if len(fields) < numPatchFields {
return nil, errors.New(errors.SetupPatchError, "too few fields in patch entry")
return nil, errors.Errorf("patch: too few fields in patch entry")
}
set.cartHash = fields[patchFieldCartHash]
@ -94,7 +94,7 @@ func (set Patch) matchCartHash(hash string) bool {
func (set Patch) apply(vcs *hardware.VCS) error {
_, err := patch.CartridgeMemory(vcs.Mem.Cart, set.patchFile)
if err != nil {
return errors.New(errors.SetupPatchError, err)
return errors.Errorf("patch: %v", err)
}
return nil
}

View file

@ -16,6 +16,8 @@
package setup
import (
"fmt"
"github.com/jetsetilly/gopher2600/cartridgeloader"
"github.com/jetsetilly/gopher2600/database"
"github.com/jetsetilly/gopher2600/errors"
@ -68,16 +70,16 @@ func AttachCartridge(vcs *hardware.VCS, cartload cartridgeloader.Loader) error {
dbPth, err := paths.ResourcePath("", setupDBFile)
if err != nil {
return errors.New(errors.SetupError, err)
return errors.Errorf("setup: %v", err)
}
db, err := database.StartSession(dbPth, database.ActivityReading, initDBSession)
if err != nil {
if errors.Is(err, errors.DatabaseFileUnavailable) {
if errors.Is(err, database.NotAvailable) {
// silently ignore absence of setup database
return nil
}
return errors.New(errors.SetupError, err)
return errors.Errorf("setup: %v", err)
}
defer db.EndSession(false)
@ -85,7 +87,7 @@ func AttachCartridge(vcs *hardware.VCS, cartload cartridgeloader.Loader) error {
// database entry should also satisfy setupEntry interface
set, ok := ent.(setupEntry)
if !ok {
return false, errors.New(errors.PanicError, "setup.AttachCartridge()", "database entry does not satisfy setupEntry interface")
return false, fmt.Errorf("setup: attach cartridge: database entry does not satisfy setupEntry interface")
}
if set.matchCartHash(vcs.Mem.Cart.Hash) {
@ -100,7 +102,7 @@ func AttachCartridge(vcs *hardware.VCS, cartload cartridgeloader.Loader) error {
_, err = db.SelectAll(onSelect)
if err != nil {
return errors.New(errors.SetupError, err)
return errors.Errorf("setup: %v", err)
}
return nil

View file

@ -45,10 +45,10 @@ func deserialiseTelevisionEntry(fields database.SerialisedEntry) (database.Entry
// basic sanity check
if len(fields) > numtelevisionFields {
return nil, errors.New(errors.SetupTelevisionError, "too many fields in television entry")
return nil, errors.Errorf("television: too many fields in television entry")
}
if len(fields) < numtelevisionFields {
return nil, errors.New(errors.SetupTelevisionError, "too few fields in television entry")
return nil, errors.Errorf("television: too few fields in television entry")
}
set.cartHash = fields[televisionFieldCartHash]

View file

@ -68,7 +68,7 @@ func ReadSymbolsFile(cartridgeFilename string) (*Table, error) {
sf, err := os.Open(symFilename)
if err != nil {
return tbl, errors.New(errors.SymbolsFileUnavailable, cartridgeFilename)
return tbl, errors.Errorf("symbols: file not available (%s)", cartridgeFilename)
}
defer func() {
_ = sf.Close()
@ -76,7 +76,7 @@ func ReadSymbolsFile(cartridgeFilename string) (*Table, error) {
sym, err := ioutil.ReadAll(sf)
if err != nil {
return nil, errors.New(errors.SymbolsFileError, err)
return nil, errors.Errorf("symbols: processing error: %v", err)
}
lines := strings.Split(string(sym), "\n")

View file

@ -17,8 +17,6 @@ package symbols
import (
"strings"
"github.com/jetsetilly/gopher2600/errors"
)
// TableType is used to select and identify a symbol table
@ -51,26 +49,26 @@ const (
// SearchSymbol return the address of the supplied symbol. Search is
// case-insensitive and is conducted on the subtables in order: locations >
// read > write.
func (tbl *Table) SearchSymbol(symbol string, target TableType) (TableType, string, uint16, error) {
func (tbl *Table) SearchSymbol(symbol string, target TableType) (bool, TableType, string, uint16) {
symbolUpper := strings.ToUpper(symbol)
if target == UnspecifiedSymTable || target == LocationSymTable {
if addr, ok := tbl.Locations.search(symbolUpper); ok {
return LocationSymTable, symbol, addr, nil
return true, LocationSymTable, symbol, addr
}
}
if target == UnspecifiedSymTable || target == ReadSymTable {
if addr, ok := tbl.Read.search(symbolUpper); ok {
return ReadSymTable, symbol, addr, nil
return true, ReadSymTable, symbol, addr
}
}
if target == UnspecifiedSymTable || target == WriteSymTable {
if addr, ok := tbl.Write.search(symbolUpper); ok {
return WriteSymTable, symbol, addr, nil
return true, WriteSymTable, symbol, addr
}
}
return UnspecifiedSymTable, symbol, 0, errors.New(errors.SymbolUnknown, symbol)
return false, UnspecifiedSymTable, symbol, 0
}

View file

@ -319,14 +319,14 @@ func (tv *television) newFrame(synced bool) error {
// GetState implements the Television interface
func (tv *television) GetState(request StateReq) (int, error) {
switch request {
default:
return 0, errors.New(errors.UnknownTVRequest, request)
case ReqFramenum:
return tv.frameNum, nil
case ReqScanline:
return tv.scanline, nil
case ReqHorizPos:
return tv.horizPos - HorizClksHBlank, nil
default:
return 0, errors.Errorf("television: unhandled tv state request (%v)", request)
}
}
@ -342,9 +342,8 @@ func (tv *television) SetSpec(spec string) error {
case "AUTO":
tv.spec = SpecNTSC
tv.auto = true
default:
return errors.New(errors.Television, fmt.Sprintf("unsupported tv specifcation (%s)", spec))
return errors.Errorf("television: unsupported spec (%s)", spec)
}
tv.top = tv.spec.ScanlineTop

View file

@ -53,7 +53,7 @@ func (aw *WavWriter) SetAudio(audioData uint8) error {
func (aw *WavWriter) EndMixing() error {
f, err := os.Create(aw.filename)
if err != nil {
return errors.New(errors.WavWriter, err)
return errors.Errorf("wavwriter: %v", err)
}
defer f.Close()
@ -61,7 +61,7 @@ func (aw *WavWriter) EndMixing() error {
enc := wav.NewEncoder(f, tiaAudio.SampleFreq, 8, 1, 1)
if enc == nil {
return errors.New(errors.WavWriter, "bad parameters for wav encoding")
return errors.Errorf("wavwriter: %v", "bad parameters for wav encoding")
}
defer enc.Close()
@ -77,7 +77,7 @@ func (aw *WavWriter) EndMixing() error {
err = enc.Write(buf.AsIntBuffer())
if err != nil {
return errors.New(errors.WavWriter, err)
return errors.Errorf("wavwriter: %v", err)
}
return nil