mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2025-04-02 11:02:17 -04:00
- renamed FormattedError to AtariError - renamed NewFormattedError to New - implemented Is() and IsAny() for more convenient testing + changed all error testing to use Is() and IsAny() - renamed FatalError to PanicError to better indicate the intent
284 lines
7.6 KiB
Go
284 lines
7.6 KiB
Go
// breakpoints are used to halt execution when a target is *changed to* a
|
|
// specific value. compare to traps which are used to halt execution when the
|
|
// target *changes from* its current value *to* any other value.
|
|
|
|
package debugger
|
|
|
|
import (
|
|
"fmt"
|
|
"gopher2600/debugger/commandline"
|
|
"gopher2600/debugger/console"
|
|
"gopher2600/errors"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// breakpoints keeps track of all the currently defined breakers
|
|
type breakpoints struct {
|
|
dbg *Debugger
|
|
breaks []breaker
|
|
}
|
|
|
|
// breaker defines a specific break condition
|
|
type breaker struct {
|
|
target target
|
|
value interface{}
|
|
ignoreValue interface{}
|
|
|
|
// basic linked list to implement AND-conditions
|
|
next *breaker
|
|
}
|
|
|
|
func (bk breaker) String() string {
|
|
s := strings.Builder{}
|
|
s.WriteString(fmt.Sprintf("%s->%s", bk.target.ShortLabel(), bk.target.FormatValue(bk.value)))
|
|
n := bk.next
|
|
for n != nil {
|
|
s.WriteString(fmt.Sprintf(" & %s->%s", n.target.ShortLabel(), n.target.FormatValue(n.value)))
|
|
n = n.next
|
|
}
|
|
return s.String()
|
|
}
|
|
|
|
// breaker.check checks the specific break condition with the current value of
|
|
// the break target
|
|
func (bk *breaker) check() bool {
|
|
currVal := bk.target.Value()
|
|
m := currVal == bk.value
|
|
if !m {
|
|
bk.ignoreValue = nil
|
|
return false
|
|
}
|
|
|
|
if currVal == bk.ignoreValue {
|
|
return false
|
|
}
|
|
|
|
if bk.next != nil {
|
|
if !bk.next.check() {
|
|
return false
|
|
}
|
|
}
|
|
|
|
bk.ignoreValue = currVal
|
|
|
|
return true
|
|
}
|
|
|
|
// breaker.add links a new breaker object to an existing breaker object
|
|
func (bk *breaker) add(nbk *breaker) {
|
|
n := &bk.next
|
|
for *n != nil {
|
|
*n = (*n).next
|
|
}
|
|
*n = nbk
|
|
}
|
|
|
|
// newBreakpoints is the preferred method of initialisation for breakpoins
|
|
func newBreakpoints(dbg *Debugger) *breakpoints {
|
|
bp := new(breakpoints)
|
|
bp.dbg = dbg
|
|
bp.clear()
|
|
return bp
|
|
}
|
|
|
|
func (bp *breakpoints) clear() {
|
|
bp.breaks = make([]breaker, 0, 10)
|
|
}
|
|
|
|
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))
|
|
}
|
|
|
|
h := bp.breaks[:num]
|
|
t := bp.breaks[num+1:]
|
|
bp.breaks = make([]breaker, len(h)+len(t), cap(bp.breaks))
|
|
copy(bp.breaks, h)
|
|
copy(bp.breaks[len(h):], t)
|
|
|
|
return nil
|
|
}
|
|
|
|
// breakpoints.check compares the current state of the emulation with every
|
|
// break condition. it returns a string listing every condition that applies
|
|
func (bp *breakpoints) check(previousResult string) string {
|
|
checkString := strings.Builder{}
|
|
checkString.WriteString(previousResult)
|
|
for i := range bp.breaks {
|
|
// check current value of target with the requested value
|
|
if bp.breaks[i].check() {
|
|
checkString.WriteString(fmt.Sprintf("break on %s\n", bp.breaks[i]))
|
|
}
|
|
}
|
|
return checkString.String()
|
|
}
|
|
|
|
func (bp breakpoints) list() {
|
|
if len(bp.breaks) == 0 {
|
|
bp.dbg.print(console.StyleFeedback, "no breakpoints")
|
|
} else {
|
|
bp.dbg.print(console.StyleFeedback, "breakpoints")
|
|
for i := range bp.breaks {
|
|
bp.dbg.print(console.StyleFeedback, "% 2d: %s", i, bp.breaks[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
// parseBreakpoints consumes tokens and adds new conditions to the list of
|
|
// breakpoints. For example:
|
|
//
|
|
// PC 0xf000
|
|
// adds a new breakpoint to the PC
|
|
//
|
|
// 0xf000
|
|
// is the same, because we assume a target of PC if none is given
|
|
//
|
|
// X 10 11
|
|
// adds two new breakpoints to X - we've changed targets so the second value
|
|
// is assumed to be for the previously selected target
|
|
//
|
|
// X 10 11 Y 12
|
|
// add three breakpoints; 2 to X and 1 to Y
|
|
//
|
|
// SL 100 & HP 0
|
|
// add one AND-condition
|
|
//
|
|
// SL 100 & HP 0 | X 10
|
|
// add two conditions; one AND-condition and one condition on X
|
|
//
|
|
// note that this is a very simple parser and we can do unusual things: the &
|
|
// and | symbols simply switch "modes", with unusual consequences. for example,
|
|
// the last example above could be written:
|
|
//
|
|
// & SL 100 HP 0 | X 10
|
|
//
|
|
// !!TODO: more sophisticated breakpoints parser
|
|
func (bp *breakpoints) parseBreakpoint(tokens *commandline.Tokens) error {
|
|
andBreaks := false
|
|
|
|
// default target of CPU PC. meaning that "BREAK n" will cause a breakpoint
|
|
// being set on the PC. breaking on PC is probably the most common type of
|
|
// breakpoint. the target will change value when the input string sees
|
|
// something appropriate
|
|
tgt := target(bp.dbg.vcs.CPU.PC)
|
|
|
|
// resolvedTarget keeps track of whether we have specified a target but not
|
|
// given any values for that target. we set it to true initially because
|
|
// we want to be able to change the default target
|
|
resolvedTarget := true
|
|
|
|
// we don't add new breakpoints to the main list straight away. we append
|
|
// them to newBreaks first and then check that we aren't adding duplicates
|
|
newBreaks := make([]breaker, 0, 10)
|
|
|
|
// loop over tokens:
|
|
// o if token is a valid type value then add the breakpoint for the current target
|
|
// o if it is not a valid type value, try to change the target
|
|
tok, present := tokens.Get()
|
|
for present {
|
|
var val interface{}
|
|
var err error
|
|
|
|
// try to interpret the token depending on the type of value the target
|
|
// expects
|
|
switch tgt.Value().(type) {
|
|
case int:
|
|
var v int64
|
|
v, err = strconv.ParseInt(tok, 0, 32)
|
|
if err == nil {
|
|
val = int(v)
|
|
}
|
|
case bool:
|
|
switch strings.ToLower(tok) {
|
|
case "true":
|
|
val = true
|
|
case "false":
|
|
val = false
|
|
default:
|
|
err = errors.New(errors.CommandError, fmt.Sprintf("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.Value(), tgt.Label()))
|
|
}
|
|
|
|
if err == nil {
|
|
if andBreaks {
|
|
if len(newBreaks) == 0 {
|
|
newBreaks = append(newBreaks, breaker{target: tgt, value: val})
|
|
} else {
|
|
newBreaks[len(newBreaks)-1].add(&breaker{target: tgt, value: val})
|
|
}
|
|
resolvedTarget = true
|
|
} else {
|
|
newBreaks = append(newBreaks, breaker{target: tgt, value: val})
|
|
resolvedTarget = true
|
|
}
|
|
|
|
} else {
|
|
// make sure we've not left a previous target dangling without a value
|
|
if !resolvedTarget {
|
|
return errors.New(errors.CommandError, err)
|
|
}
|
|
|
|
// possibly switch composition mode
|
|
if tok == "&" {
|
|
andBreaks = true
|
|
} else if tok == "|" {
|
|
andBreaks = false
|
|
} else {
|
|
// token is not a number or a composition symbol so try to
|
|
// parse a new target
|
|
tokens.Unget()
|
|
tgt, err = parseTarget(bp.dbg, tokens)
|
|
if err != nil {
|
|
return errors.New(errors.CommandError, err)
|
|
}
|
|
resolvedTarget = false
|
|
}
|
|
}
|
|
|
|
tok, present = tokens.Get()
|
|
}
|
|
|
|
if !resolvedTarget {
|
|
return errors.New(errors.CommandError, fmt.Sprintf("need a value (%T) to break on (%s)", tgt.Value(), tgt.Label()))
|
|
}
|
|
|
|
return bp.checkNewBreakpoints(newBreaks)
|
|
}
|
|
|
|
func (bp *breakpoints) checkNewBreakpoints(newBreaks []breaker) error {
|
|
// don't add breakpoints that already exist
|
|
for _, nb := range newBreaks {
|
|
for _, ob := range bp.breaks {
|
|
and := &nb
|
|
oand := &ob
|
|
|
|
// start with assuming this is a duplicate
|
|
duplicate := true
|
|
|
|
// continue comparison until we reach the end of one of the lists
|
|
// or if a non-duplicate condition has been found
|
|
for duplicate && and != nil && oand != nil {
|
|
// note that this method of duplication detection only works if
|
|
// targets are ANDed in the same order.
|
|
//
|
|
// !!TODO: sort conditions before comparison
|
|
duplicate = duplicate && (oand.target.Label() == and.target.Label() && oand.value == and.value)
|
|
|
|
and = and.next
|
|
oand = oand.next
|
|
}
|
|
|
|
// fail if this is a duplicate and if both lists were of the same length
|
|
if duplicate && and == nil && oand == nil {
|
|
return errors.New(errors.CommandError, fmt.Sprintf("breakpoint already exists (%s)", ob))
|
|
}
|
|
}
|
|
|
|
bp.breaks = append(bp.breaks, nb)
|
|
}
|
|
|
|
return nil
|
|
}
|