Gopher2600/debugger/commands.go
steve c59a4a2dde o debugger
- RAM command now has CART option. displays any additional RAM the
	cartridge may have

o cartridge
    - implemented RAM() command. returns copy of RAM array
    - save/restore banks functions are now save/restore state and deal
	with cartridge RAM in addition to bank information

o debugger/memory
    - better error messages for peek and poke
2020-01-05 18:58:36 +00:00

1115 lines
28 KiB
Go

package debugger
import (
"fmt"
"gopher2600/debugger/commandline"
"gopher2600/debugger/console"
"gopher2600/debugger/script"
"gopher2600/errors"
"gopher2600/gui"
"gopher2600/hardware/cpu/register"
"gopher2600/hardware/cpu/result"
"gopher2600/hardware/peripherals"
"gopher2600/symbols"
"os"
"sort"
"strconv"
"strings"
)
// debugger keywords. not a useful data structure but we can use these to form
// the more useful DebuggerCommands and Help structures
const (
cmdBall = "BALL"
cmdBreak = "BREAK"
cmdCPU = "CPU"
cmdCartridge = "CARTRIDGE"
cmdClear = "CLEAR"
cmdDebuggerState = "DEBUGGERSTATE"
cmdDigest = "DIGEST"
cmdDisassembly = "DISASSEMBLY"
cmdDisplay = "DISPLAY"
cmdDrop = "DROP"
cmdGrep = "GREP"
cmdHexLoad = "HEXLOAD"
cmdInsert = "INSERT"
cmdLast = "LAST"
cmdList = "LIST"
cmdMemMap = "MEMMAP"
cmdMissile = "MISSILE"
cmdOnHalt = "ONHALT"
cmdOnStep = "ONSTEP"
cmdPeek = "PEEK"
cmdPlayer = "PLAYER"
cmdPlayfield = "PLAYFIELD"
cmdPoke = "POKE"
cmdQuit = "QUIT"
cmdExit = "EXIT"
cmdRAM = "RAM"
cmdRIOT = "RIOT"
cmdReset = "RESET"
cmdRun = "RUN"
cmdScript = "SCRIPT"
cmdStep = "STEP"
cmdGranularity = "GRANULARITY"
cmdStick = "STICK"
cmdSymbol = "SYMBOL"
cmdTIA = "TIA"
cmdTV = "TV"
cmdTerse = "TERSE"
cmdTrap = "TRAP"
cmdVerbose = "VERBOSE"
cmdVerbosity = "VERBOSITY"
cmdWatch = "WATCH"
)
const cmdHelp = "HELP"
var commandTemplate = []string{
cmdBall,
cmdBreak + " [%S %N|%N] {& %S %N|& %N}",
cmdCPU + " (SET [PC|A|X|Y|SP] [%N])",
cmdCartridge + " (ANALYSIS)",
cmdClear + " [BREAKS|TRAPS|WATCHES|ALL]",
cmdDebuggerState,
cmdDigest + " (RESET)",
cmdDisassembly,
cmdDisplay + " (OFF|DEBUG|SCALE [%P]|DEBUGCOLORS)", // see notes
cmdDrop + " [BREAK|TRAP|WATCH] %N",
cmdGrep + " %S",
cmdHexLoad + " %N %N {%N}",
cmdInsert + " %F",
cmdLast + " (DEFN)",
cmdList + " [BREAKS|TRAPS|WATCHES|ALL]",
cmdMemMap,
cmdMissile + " (0|1)",
cmdOnHalt + " (OFF|ON|%S {%S})",
cmdOnStep + " (OFF|ON|%S {%S})",
cmdPeek + " [%S] {%S}",
cmdPlayer + " (0|1)",
cmdPlayfield,
cmdPoke + " [%S] %N",
cmdQuit,
cmdExit,
cmdRAM + " (CART)",
cmdRIOT + " (TIMER)",
cmdReset,
cmdRun,
cmdScript + " [WRITE %S|END|%F]",
cmdStep + " (CPU|VIDEO|%S)",
cmdGranularity + " (CPU|VIDEO)",
cmdStick + " [0|1] [LEFT|RIGHT|UP|DOWN|FIRE|NOLEFT|NORIGHT|NOUP|NODOWN|NOFIRE]",
cmdSymbol + " [%S (ALL|MIRRORS)|LIST (LOCATIONS|READ|WRITE)]",
cmdTIA + " (DELAY|DELAYS)",
cmdTV + " (SPEC)",
cmdTerse,
cmdTrap + " [%S] {%S}",
cmdVerbose,
cmdVerbosity,
cmdWatch + " (READ|WRITE) [%S] (%S)",
}
// list of commands that should not be executed when recording/playing scripts
var scriptUnsafeTemplate = []string{
cmdScript + " [WRITE [%S]]",
cmdRun,
}
var debuggerCommands *commandline.Commands
var scriptUnsafeCommands *commandline.Commands
var debuggerCommandsIdx *commandline.Index
func init() {
var err error
// parse command template
debuggerCommands, err = commandline.ParseCommandTemplateWithOutput(commandTemplate, os.Stdout)
if err != nil {
fmt.Println(err)
os.Exit(100)
}
err = debuggerCommands.AddHelp(cmdHelp)
if err != nil {
fmt.Println(err)
os.Exit(100)
}
sort.Stable(debuggerCommands)
scriptUnsafeCommands, err = commandline.ParseCommandTemplate(scriptUnsafeTemplate)
if err != nil {
fmt.Println(err)
os.Exit(100)
}
sort.Stable(scriptUnsafeCommands)
debuggerCommandsIdx = commandline.CreateIndex(debuggerCommands)
}
type parseCommandResult int
const (
doNothing parseCommandResult = iota
emptyInput
stepContinue
setDefaultStep
scriptRecordStarted
scriptRecordEnded
)
// parseCommand/enactCommand scans user input for a valid command and acts upon
// it. commands that cause the emulation to move forward (RUN, STEP) return
// true for the first return value. other commands return false and act upon
// the command immediately. note that the empty string is the same as the STEP
// command
func (dbg *Debugger) parseCommand(userInput *string, interactive bool) (parseCommandResult, error) {
// tokenise input
tokens := commandline.TokeniseInput(*userInput)
// normalise user input -- we don't use the results in this
// function but we do use it futher-up. eg. when capturing user input to a
// script
*userInput = tokens.String()
// if there are no tokens in the input then return emptyInput directive
if tokens.Remaining() == 0 {
return emptyInput, nil
}
// check validity of tokenised input
err := debuggerCommands.ValidateTokens(tokens)
if err != nil {
return doNothing, err
}
// the absolute best thing about the ValidateTokens() function is that we
// don't need to worrying too much about the success of tokens.Get() in the
// enactCommand() function below:
//
// arg, _ := tokens.Get()
//
// is an acceptable pattern even when an argument is required. the
// ValidateTokens() function has already caught invalid attempts.
//
// default values can be handled thus:
//
// arg, ok := tokens.Get()
// if ok {
// switch arg {
// ...
// }
// } else {
// // default action
// ...
// }
//
// unfortunately, there is no way currently to handle the case where the
// command templates don't match expectation in the code below. the code
// won't break but some error messages may be misleading but hopefully, it
// will be obvious something went wrong.
// test to see if command is allowed when recording/playing a script
if dbg.scriptScribe.IsActive() || !interactive {
tokens.Reset()
err := scriptUnsafeCommands.ValidateTokens(tokens)
// fail when the tokens DO match the scriptUnsafe template (ie. when
// there is no err from the validate function)
if err == nil {
return doNothing, errors.NewFormattedError(errors.CommandError, fmt.Sprintf("'%s' is unsafe to use in scripts", tokens.String()))
}
}
return dbg.enactCommand(tokens, interactive)
}
func (dbg *Debugger) enactCommand(tokens *commandline.Tokens, interactive bool) (parseCommandResult, error) {
// check first token. if this token makes sense then we will consume the
// rest of the tokens appropriately
tokens.Reset()
command, _ := tokens.Get()
// take uppercase value of the first token. it's useful to take the
// uppercase value but we have to be careful when we do it because
command = strings.ToUpper(command)
switch command {
default:
return doNothing, errors.NewFormattedError(errors.CommandError, fmt.Sprintf("%s is not yet implemented", command))
case cmdHelp:
keyword, present := tokens.Get()
if present {
keyword = strings.ToUpper(keyword)
helpTxt, ok := Help[keyword]
if !ok {
dbg.print(console.StyleHelp, "no help for %s", keyword)
} else {
helpTxt = fmt.Sprintf("%s\n\n Usage: %s", helpTxt, (*debuggerCommandsIdx)[keyword].String())
dbg.print(console.StyleHelp, helpTxt)
}
} else {
dbg.print(console.StyleHelp, debuggerCommands.String())
}
case cmdInsert:
cart, _ := tokens.Get()
err := dbg.loadCartridge(cart)
if err != nil {
return doNothing, err
}
dbg.print(console.StyleFeedback, "machine reset with new cartridge (%s)", cart)
case cmdScript:
option, _ := tokens.Get()
switch strings.ToUpper(option) {
case "WRITE":
var err error
saveFile, _ := tokens.Get()
err = dbg.scriptScribe.StartSession(saveFile)
if err != nil {
return doNothing, err
}
return scriptRecordStarted, nil
case "END":
dbg.scriptScribe.Rollback()
err := dbg.scriptScribe.EndSession()
return scriptRecordEnded, err
default:
// run a script
plb, err := script.StartPlayback(option)
if err != nil {
return doNothing, err
}
if dbg.scriptScribe.IsActive() {
// if we're currently recording a script we want to write this
// command to the new script file but indicate that we'll be
// entering a new script and so don't want to repeat the
// commands from that script
dbg.scriptScribe.StartPlayback()
defer func() {
dbg.scriptScribe.EndPlayback()
}()
// !!TODO: provide a recording option to allow insertion of
// the actual script commands rather than the call to the
// script itself
}
err = dbg.inputLoop(plb, false)
if err != nil {
return doNothing, err
}
}
case cmdDisassembly:
dbg.disasm.Dump(dbg.printStyle(console.StyleFeedback))
case cmdGrep:
search, _ := tokens.Get()
output := strings.Builder{}
dbg.disasm.Grep(search, &output, false, 3)
if output.Len() == 0 {
dbg.print(console.StyleError, "%s not found in disassembly", search)
} else {
dbg.print(console.StyleFeedback, output.String())
}
case cmdSymbol:
tok, _ := tokens.Get()
switch strings.ToUpper(tok) {
case "LIST":
option, present := tokens.Get()
if present {
switch strings.ToUpper(option) {
default:
// already caught by command line ValidateTokens()
case "LOCATIONS":
dbg.disasm.Symtable.ListLocations(dbg.printStyle(console.StyleFeedback))
case "READ":
dbg.disasm.Symtable.ListReadSymbols(dbg.printStyle(console.StyleFeedback))
case "WRITE":
dbg.disasm.Symtable.ListWriteSymbols(dbg.printStyle(console.StyleFeedback))
}
} else {
dbg.disasm.Symtable.ListSymbols(dbg.printStyle(console.StyleFeedback))
}
default:
symbol := tok
table, symbol, address, err := dbg.disasm.Symtable.SearchSymbol(symbol, symbols.UnspecifiedSymTable)
if err != nil {
switch err := err.(type) {
case errors.FormattedError:
if err.Errno == errors.SymbolUnknown {
dbg.print(console.StyleFeedback, "%s -> not found", symbol)
return doNothing, nil
}
}
return doNothing, err
}
option, present := tokens.Get()
if present {
switch strings.ToUpper(option) {
default:
// already caught by command line ValidateTokens()
case "ALL", "MIRRORS":
dbg.print(console.StyleFeedback, "%s -> %#04x", symbol, address)
// find all instances of symbol address in memory space
// assumption: the address returned by SearchSymbol is the
// first address in the complete list
for m := address + 1; m < dbg.vcs.Mem.Cart.Origin(); m++ {
if dbg.vcs.Mem.MapAddress(m, table == symbols.ReadSymTable) == address {
dbg.print(console.StyleFeedback, "%s -> %#04x", symbol, m)
}
}
}
} else {
dbg.print(console.StyleFeedback, "%s -> %#04x", symbol, address)
}
}
case cmdBreak:
err := dbg.breakpoints.parseBreakpoint(tokens)
if err != nil {
return doNothing, errors.NewFormattedError(errors.CommandError, err)
}
case cmdTrap:
err := dbg.traps.parseTrap(tokens)
if err != nil {
return doNothing, errors.NewFormattedError(errors.CommandError, err)
}
case cmdWatch:
err := dbg.watches.parseWatch(tokens, dbg.dbgmem)
if err != nil {
return doNothing, errors.NewFormattedError(errors.CommandError, err)
}
case cmdList:
list, _ := tokens.Get()
list = strings.ToUpper(list)
switch list {
case "BREAKS":
dbg.breakpoints.list()
case "TRAPS":
dbg.traps.list()
case "WATCHES":
dbg.watches.list()
case "ALL":
// !!TODO: refine output. requires headings
dbg.breakpoints.list()
dbg.traps.list()
dbg.watches.list()
default:
// already caught by command line ValidateTokens()
}
case cmdClear:
clear, _ := tokens.Get()
clear = strings.ToUpper(clear)
switch clear {
case "BREAKS":
dbg.breakpoints.clear()
dbg.print(console.StyleFeedback, "breakpoints cleared")
case "TRAPS":
dbg.traps.clear()
dbg.print(console.StyleFeedback, "traps cleared")
case "WATCHES":
dbg.watches.clear()
dbg.print(console.StyleFeedback, "watches cleared")
case "ALL":
dbg.breakpoints.clear()
dbg.traps.clear()
dbg.watches.clear()
dbg.print(console.StyleFeedback, "breakpoints, traps and watches cleared")
default:
// already caught by command line ValidateTokens()
}
case cmdDrop:
drop, _ := tokens.Get()
s, _ := tokens.Get()
num, err := strconv.Atoi(s)
if err != nil {
return doNothing, errors.NewFormattedError(errors.CommandError, fmt.Sprintf("drop attribute must be a number (%s)", s))
}
drop = strings.ToUpper(drop)
switch drop {
case "BREAK":
err := dbg.breakpoints.drop(num)
if err != nil {
return doNothing, err
}
dbg.print(console.StyleFeedback, "breakpoint #%d dropped", num)
case "TRAP":
err := dbg.traps.drop(num)
if err != nil {
return doNothing, err
}
dbg.print(console.StyleFeedback, "trap #%d dropped", num)
case "WATCH":
err := dbg.watches.drop(num)
if err != nil {
return doNothing, err
}
dbg.print(console.StyleFeedback, "watch #%d dropped", num)
default:
// already caught by command line ValidateTokens()
}
case cmdOnHalt:
if tokens.Remaining() == 0 {
if dbg.commandOnHalt == "" {
dbg.print(console.StyleFeedback, "auto-command on halt: OFF")
} else {
dbg.print(console.StyleFeedback, "auto-command on halt: %s", dbg.commandOnHalt)
}
return doNothing, nil
}
// !!TODO: non-interactive check of tokens against scriptUnsafeTemplate
//
var newCommands string
option, _ := tokens.Peek()
switch strings.ToUpper(option) {
case "OFF":
newCommands = ""
case "ON":
newCommands = dbg.commandOnHaltStored
default:
// use remaininder of command line to form the ONHALT command sequence
newCommands = tokens.Remainder()
// we can't use semi-colons when specifying the sequence so allow use of
// commas to act as an alternative
newCommands = strings.Replace(newCommands, ",", ";", -1)
}
// run the new/restored ONHALT command(s)
_, err := dbg.parseInput(newCommands, false, false)
if err != nil {
return doNothing, err
}
dbg.commandOnHalt = newCommands
// display the new/restored ONHALT command(s)
if newCommands == "" {
dbg.print(console.StyleFeedback, "auto-command on halt: OFF")
} else {
dbg.print(console.StyleFeedback, "auto-command on halt: %s", dbg.commandOnHalt)
// store the new command so we can reuse it after an ONHALT OFF
// !!TODO: normalise case of specified command sequence
dbg.commandOnHaltStored = newCommands
}
return doNothing, nil
case cmdOnStep:
if tokens.Remaining() == 0 {
if dbg.commandOnHalt == "" {
dbg.print(console.StyleFeedback, "auto-command on step: OFF")
} else {
dbg.print(console.StyleFeedback, "auto-command on step: %s", dbg.commandOnStep)
}
return doNothing, nil
}
// !!TODO: non-interactive check of tokens against scriptUnsafeTemplate
var newCommands string
option, _ := tokens.Peek()
switch strings.ToUpper(option) {
case "OFF":
newCommands = ""
case "ON":
newCommands = dbg.commandOnStepStored
default:
// use remaininder of command line to form the ONSTEP command sequence
newCommands = tokens.Remainder()
// we can't use semi-colons when specifying the sequence so allow use of
// commas to act as an alternative
newCommands = strings.Replace(newCommands, ",", ";", -1)
}
// run the new/restored ONSTEP command(s)
_, err := dbg.parseInput(newCommands, false, false)
if err != nil {
return doNothing, err
}
dbg.commandOnStep = newCommands
// display the new/restored ONSTEP command(s)
if newCommands == "" {
dbg.print(console.StyleFeedback, "auto-command on step: OFF")
} else {
dbg.print(console.StyleFeedback, "auto-command on step: %s", dbg.commandOnStep)
// store the new command so we can reuse it after an ONSTEP OFF
// !!TODO: normalise case of specified command sequence
dbg.commandOnStepStored = newCommands
}
return doNothing, nil
case cmdLast:
if dbg.lastResult != nil {
option, ok := tokens.Get()
if ok {
switch strings.ToUpper(option) {
case "DEFN":
dbg.print(console.StyleFeedback, "%s", dbg.lastResult.Defn)
}
} else {
var printTag console.Style
if dbg.lastResult.Final {
printTag = console.StyleCPUStep
} else {
printTag = console.StyleVideoStep
}
dbg.print(printTag, "%s", dbg.lastResult.GetString(dbg.disasm.Symtable, result.StyleExecution))
}
}
case cmdMemMap:
dbg.print(console.StyleMachineInfo, "%v", dbg.vcs.Mem.MemoryMap())
case cmdExit:
fallthrough
case cmdQuit:
dbg.running = false
case cmdReset:
err := dbg.vcs.Reset()
if err != nil {
return doNothing, err
}
err = dbg.gui.Reset()
if err != nil {
return doNothing, err
}
dbg.print(console.StyleFeedback, "machine reset")
case cmdRun:
if !dbg.gui.IsVisible() && dbg.commandOnStep == "" {
dbg.print(console.StyleEmulatorInfo, "running with no display or terminal output")
}
dbg.runUntilHalt = true
return stepContinue, nil
case cmdStep:
mode, _ := tokens.Get()
mode = strings.ToUpper(mode)
switch mode {
case "":
// calling step with no argument is the normal case
case "CPU":
// changes granularity
dbg.inputEveryVideoCycle = false
case "VIDEO":
// changes granularity
dbg.inputEveryVideoCycle = true
default:
dbg.inputEveryVideoCycle = false
tokens.Unget()
err := dbg.stepTraps.parseTrap(tokens)
if err != nil {
return doNothing, errors.NewFormattedError(errors.CommandError, fmt.Sprintf("unknown step mode (%s)", mode))
}
dbg.runUntilHalt = true
}
return setDefaultStep, nil
case cmdGranularity:
mode, present := tokens.Get()
if present {
mode = strings.ToUpper(mode)
switch mode {
case "CPU":
dbg.inputEveryVideoCycle = false
case "VIDEO":
dbg.inputEveryVideoCycle = true
default:
// already caught by command line ValidateTokens()
}
}
if dbg.inputEveryVideoCycle {
mode = "VIDEO"
} else {
mode = "CPU"
}
dbg.print(console.StyleFeedback, "granularity: %s", mode)
case cmdTerse:
dbg.machineInfoVerbose = false
dbg.print(console.StyleFeedback, "verbosity: terse")
case cmdVerbose:
dbg.machineInfoVerbose = true
dbg.print(console.StyleFeedback, "verbosity: verbose")
case cmdVerbosity:
if dbg.machineInfoVerbose {
dbg.print(console.StyleFeedback, "verbosity: verbose")
} else {
dbg.print(console.StyleFeedback, "verbosity: terse")
}
case cmdDebuggerState:
_, err := dbg.parseInput("VERBOSITY; STEPMODE; ONHALT; ONSTEP", false, false)
if err != nil {
return doNothing, err
}
case cmdCartridge:
arg, ok := tokens.Get()
if ok {
switch arg {
case "ANALYSIS":
dbg.print(console.StyleFeedback, dbg.disasm.String())
}
} else {
dbg.printMachineInfo(dbg.vcs.Mem.Cart)
}
case cmdCPU:
action, present := tokens.Get()
if present {
switch strings.ToUpper(action) {
case "SET":
target, _ := tokens.Get()
var reg *register.Register
switch strings.ToUpper(target) {
case "PC":
reg = dbg.vcs.CPU.PC
case "A":
reg = dbg.vcs.CPU.A
case "X":
reg = dbg.vcs.CPU.X
case "Y":
reg = dbg.vcs.CPU.Y
case "SP":
reg = dbg.vcs.CPU.SP
}
value, _ := tokens.Get()
v, err := strconv.ParseUint(value, 0, int(reg.Size()))
if err != nil {
dbg.print(console.StyleError, "value must be a positive %dbit number", reg.Size())
}
reg.Load(v)
default:
// already caught by command line ValidateTokens()
}
} else {
dbg.printMachineInfo(dbg.vcs.CPU)
}
case cmdPeek:
// get first address token
a, present := tokens.Get()
for present {
// perform peek
ai, err := dbg.dbgmem.peek(a)
if err != nil {
dbg.print(console.StyleError, "%s", err)
} else {
dbg.print(console.StyleMachineInfo, ai.String())
}
// loop through all addresses
a, present = tokens.Get()
}
case cmdPoke:
// get address token
a, _ := tokens.Get()
// get value token
v, _ := tokens.Get()
val, err := strconv.ParseUint(v, 0, 8)
if err != nil {
dbg.print(console.StyleError, "poke value must be 8bit number (%s)", v)
return doNothing, nil
}
// perform single poke
ai, err := dbg.dbgmem.poke(a, uint8(val))
if err != nil {
dbg.print(console.StyleError, "%s", err)
} else {
dbg.print(console.StyleMachineInfo, ai.String())
}
case cmdHexLoad:
// get address token
a, _ := tokens.Get()
addr, err := strconv.ParseUint(a, 0, 16)
if err != nil {
dbg.print(console.StyleError, "hexload address must be 16bit number (%s)", a)
return doNothing, nil
}
// get (first) value token
v, present := tokens.Get()
for present {
val, err := strconv.ParseUint(v, 0, 8)
if err != nil {
dbg.print(console.StyleError, "hexload value must be 8bit number (%s)", addr)
v, present = tokens.Get()
continue // for loop (without advancing address)
}
// perform individual poke
ai, err := dbg.dbgmem.poke(uint16(addr), uint8(val))
if err != nil {
dbg.print(console.StyleError, "%s", err)
} else {
dbg.print(console.StyleMachineInfo, ai.String())
}
// loop through all values
v, present = tokens.Get()
addr++
}
case cmdRAM:
option, present := tokens.Get()
if present {
option = strings.ToUpper(option)
switch option {
case "CART":
cartRAM := dbg.vcs.Mem.Cart.RAM()
if len(cartRAM) > 0 {
// !!TODO: better presentation of cartridge RAM
dbg.print(console.StyleMachineInfo, fmt.Sprintf("%v", dbg.vcs.Mem.Cart.RAM()))
} else {
dbg.print(console.StyleFeedback, "cartridge does not contain any additional RAM")
}
}
} else {
dbg.printMachineInfo(dbg.vcs.Mem.PIA)
}
case cmdRIOT:
option, present := tokens.Get()
if present {
option = strings.ToUpper(option)
switch option {
case "TIMER":
dbg.printMachineInfo(dbg.vcs.RIOT.Timer)
default:
// already caught by command line ValidateTokens()
}
} else {
dbg.printMachineInfo(dbg.vcs.RIOT)
}
case cmdTIA:
option, present := tokens.Get()
if present {
// which machine info printing function we use depends on the
// precise argument provided
printFunc := dbg.printMachineInfoVerbose
option = strings.ToUpper(option)
switch option {
case "DELAY":
printFunc = dbg.printMachineInfo
fallthrough
case "DELAYS":
printFunc(dbg.vcs.TIA.Delay)
// for convience asking for TIA delays also prints delays for
// the sprites
printFunc(dbg.vcs.TIA.Video.Player0.Delay)
printFunc(dbg.vcs.TIA.Video.Player1.Delay)
printFunc(dbg.vcs.TIA.Video.Missile0.Delay)
printFunc(dbg.vcs.TIA.Video.Missile1.Delay)
printFunc(dbg.vcs.TIA.Video.Ball.Delay)
default:
// already caught by command line ValidateTokens()
}
} else {
dbg.printMachineInfo(dbg.vcs.TIA)
}
case cmdTV:
option, present := tokens.Get()
if present {
option = strings.ToUpper(option)
switch option {
case "SPEC":
dbg.print(console.StyleMachineInfo, dbg.gui.GetSpec().ID)
default:
// already caught by command line ValidateTokens()
}
} else {
dbg.printMachineInfo(dbg.gui)
}
// information about the machine (sprites, playfield)
case cmdPlayer:
plyr := -1
arg, _ := tokens.Get()
switch arg {
case "0":
plyr = 0
case "1":
plyr = 1
default:
tokens.Unget()
}
if dbg.machineInfoVerbose {
// arrange the two player's information side by side in order to
// save space and to allow for easy comparison
switch plyr {
case 0:
p0 := strings.Split(dbg.getMachineInfo(dbg.vcs.TIA.Video.Player0), "\n")
dbg.print(console.StyleMachineInfo, strings.Join(p0, "\n"))
case 1:
p1 := strings.Split(dbg.getMachineInfo(dbg.vcs.TIA.Video.Player1), "\n")
dbg.print(console.StyleMachineInfo, strings.Join(p1, "\n"))
default:
p0 := strings.Split(dbg.getMachineInfo(dbg.vcs.TIA.Video.Player0), "\n")
p1 := strings.Split(dbg.getMachineInfo(dbg.vcs.TIA.Video.Player1), "\n")
ml := 0
for i := range p0 {
if len(p0[i]) > ml {
ml = len(p0[i])
}
}
s := strings.Builder{}
for i := range p0 {
if p0[i] != "" {
s.WriteString(fmt.Sprintf("%s %s | %s\n", p0[i], strings.Repeat(" ", ml-len(p0[i])), p1[i]))
}
}
dbg.print(console.StyleMachineInfo, s.String())
}
} else {
switch plyr {
case 0:
dbg.printMachineInfo(dbg.vcs.TIA.Video.Player0)
case 1:
dbg.printMachineInfo(dbg.vcs.TIA.Video.Player1)
default:
dbg.printMachineInfo(dbg.vcs.TIA.Video.Player0)
dbg.printMachineInfo(dbg.vcs.TIA.Video.Player1)
}
}
case cmdMissile:
miss := -1
arg, _ := tokens.Get()
switch arg {
case "0":
miss = 0
case "1":
miss = 1
default:
tokens.Unget()
}
if dbg.machineInfoVerbose {
// arrange the two missile'sinformation side by side in order to
// save space and to allow for easy comparison
switch miss {
case 0:
p0 := strings.Split(dbg.getMachineInfo(dbg.vcs.TIA.Video.Missile0), "\n")
dbg.print(console.StyleMachineInfo, strings.Join(p0, "\n"))
case 1:
p1 := strings.Split(dbg.getMachineInfo(dbg.vcs.TIA.Video.Missile1), "\n")
dbg.print(console.StyleMachineInfo, strings.Join(p1, "\n"))
default:
p0 := strings.Split(dbg.getMachineInfo(dbg.vcs.TIA.Video.Missile0), "\n")
p1 := strings.Split(dbg.getMachineInfo(dbg.vcs.TIA.Video.Missile1), "\n")
ml := 0
for i := range p0 {
if len(p0[i]) > ml {
ml = len(p0[i])
}
}
s := strings.Builder{}
for i := range p0 {
if p0[i] != "" {
s.WriteString(fmt.Sprintf("%s %s | %s\n", p0[i], strings.Repeat(" ", ml-len(p0[i])), p1[i]))
}
}
dbg.print(console.StyleMachineInfo, s.String())
}
} else {
switch miss {
case 0:
dbg.printMachineInfo(dbg.vcs.TIA.Video.Missile0)
case 1:
dbg.printMachineInfo(dbg.vcs.TIA.Video.Missile1)
default:
dbg.printMachineInfo(dbg.vcs.TIA.Video.Missile0)
dbg.printMachineInfo(dbg.vcs.TIA.Video.Missile1)
}
}
case cmdBall:
dbg.printMachineInfo(dbg.vcs.TIA.Video.Ball)
case cmdPlayfield:
dbg.printMachineInfo(dbg.vcs.TIA.Video.Playfield)
case cmdDisplay:
var err error
action, present := tokens.Get()
if present {
action = strings.ToUpper(action)
switch action {
case "OFF":
err = dbg.gui.SetFeature(gui.ReqSetVisibility, false)
if err != nil {
return doNothing, err
}
case "DEBUG":
err = dbg.gui.SetFeature(gui.ReqToggleMasking)
if err != nil {
return doNothing, err
}
case "SCALE":
scl, present := tokens.Get()
if !present {
return doNothing, errors.NewFormattedError(errors.CommandError, fmt.Sprintf("value required for %s %s", command, action))
}
scale, err := strconv.ParseFloat(scl, 32)
if err != nil {
return doNothing, errors.NewFormattedError(errors.CommandError, fmt.Sprintf("%s %s value not valid (%s)", command, action, scl))
}
err = dbg.gui.SetFeature(gui.ReqSetScale, float32(scale))
return doNothing, err
case "DEBUGCOLORS":
err = dbg.gui.SetFeature(gui.ReqToggleAltColors)
if err != nil {
return doNothing, err
}
case "METASIGNALS":
err = dbg.gui.SetFeature(gui.ReqToggleShowMetaVideo)
if err != nil {
return doNothing, err
}
default:
// already caught by command line ValidateTokens()
}
} else {
err = dbg.gui.SetFeature(gui.ReqSetVisibility, true)
if err != nil {
return doNothing, err
}
}
case cmdStick:
var err error
stick, _ := tokens.Get()
action, _ := tokens.Get()
var event peripherals.Event
switch strings.ToUpper(action) {
case "UP":
event = peripherals.Up
case "DOWN":
event = peripherals.Down
case "LEFT":
event = peripherals.Left
case "RIGHT":
event = peripherals.Right
case "NOUP":
event = peripherals.NoUp
case "NODOWN":
event = peripherals.NoDown
case "NOLEFT":
event = peripherals.NoLeft
case "NORIGHT":
event = peripherals.NoRight
case "FIRE":
event = peripherals.Fire
case "NOFIRE":
event = peripherals.NoFire
}
n, _ := strconv.Atoi(stick)
switch n {
case 0:
err = dbg.vcs.Ports.Player0.Handle(event)
case 1:
err = dbg.vcs.Ports.Player1.Handle(event)
}
if err != nil {
return doNothing, err
}
case cmdDigest:
arg, ok := tokens.Get()
if ok {
switch arg {
case "RESET":
dbg.digest.ResetDigest()
}
} else {
dbg.print(console.StyleFeedback, dbg.digest.String())
}
}
return doNothing, nil
}