mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2025-04-02 11:02:17 -04:00
o debugger
- refactored user interface types and functions - now in ui package - sketched in tab completion
This commit is contained in:
parent
9919016ef0
commit
9d3d9d10ac
14 changed files with 352 additions and 299 deletions
|
@ -2,6 +2,7 @@ package debugger
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"gopher2600/debugger/ui"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
|
@ -64,7 +65,7 @@ func (bp *breakpoints) check() bool {
|
|||
// make sure that we're not breaking on an ignore state
|
||||
bv, prs := bp.ignoredBreakerStates[bp.breaks[i].target]
|
||||
if !prs || prs && bp.breaks[i].target.ToInt() != bv {
|
||||
bp.dbg.print(Feedback, "break on %s=%d", bp.breaks[i].target.ShortLabel(), bp.breaks[i].value)
|
||||
bp.dbg.print(ui.Feedback, "break on %s=%d", bp.breaks[i].target.ShortLabel(), bp.breaks[i].value)
|
||||
broken = true
|
||||
}
|
||||
}
|
||||
|
@ -86,10 +87,10 @@ func (bp *breakpoints) check() bool {
|
|||
|
||||
func (bp breakpoints) list() {
|
||||
if len(bp.breaks) == 0 {
|
||||
bp.dbg.print(Feedback, "no breakpoints")
|
||||
bp.dbg.print(ui.Feedback, "no breakpoints")
|
||||
} else {
|
||||
for i := range bp.breaks {
|
||||
bp.dbg.print(Feedback, "%s->%d", bp.breaks[i].target.ShortLabel(), bp.breaks[i].value)
|
||||
bp.dbg.print(ui.Feedback, "%s->%d", bp.breaks[i].target.ShortLabel(), bp.breaks[i].value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -121,7 +122,7 @@ func (bp *breakpoints) parseBreakpoint(parts []string) error {
|
|||
for _, mv := range bp.breaks {
|
||||
if mv.target == tgt && mv.value == int(val) {
|
||||
addNewBreak = false
|
||||
bp.dbg.print(Feedback, "breakpoint already exists")
|
||||
bp.dbg.print(ui.Feedback, "breakpoint already exists")
|
||||
break // for loop
|
||||
}
|
||||
}
|
||||
|
@ -140,7 +141,7 @@ func (bp *breakpoints) parseBreakpoint(parts []string) error {
|
|||
switch parts[i] {
|
||||
case "CLEAR":
|
||||
bp.clear()
|
||||
bp.dbg.print(Feedback, "breakpoints cleared")
|
||||
bp.dbg.print(ui.Feedback, "breakpoints cleared")
|
||||
return nil
|
||||
case "LIST":
|
||||
bp.list()
|
||||
|
|
|
@ -2,12 +2,9 @@ package colorterm
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"gopher2600/debugger"
|
||||
"gopher2600/debugger/colorterm/ansi"
|
||||
"gopher2600/debugger/colorterm/easyterm"
|
||||
"gopher2600/debugger/ui"
|
||||
"os"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// ColorTerminal implements debugger UI interface with a basic ANSI terminal
|
||||
|
@ -16,6 +13,8 @@ type ColorTerminal struct {
|
|||
|
||||
reader *bufio.Reader
|
||||
commandHistory []command
|
||||
|
||||
tabCompleter ui.TabCompleter
|
||||
}
|
||||
|
||||
type command struct {
|
||||
|
@ -42,217 +41,8 @@ func (ct *ColorTerminal) CleanUp() {
|
|||
ct.Terminal.CleanUp()
|
||||
}
|
||||
|
||||
// UserPrint implementation for debugger.UI interface
|
||||
func (ct *ColorTerminal) UserPrint(pp debugger.PrintProfile, s string, a ...interface{}) {
|
||||
if pp != debugger.Input {
|
||||
ct.Print("\r")
|
||||
}
|
||||
|
||||
switch pp {
|
||||
case debugger.CPUStep:
|
||||
ct.Print(ansi.PenColor["yellow"])
|
||||
case debugger.VideoStep:
|
||||
ct.Print(ansi.DimPens["yellow"])
|
||||
case debugger.MachineInfo:
|
||||
ct.Print(ansi.PenColor["cyan"])
|
||||
case debugger.Error:
|
||||
ct.Print(ansi.PenColor["red"])
|
||||
ct.Print(ansi.PenColor["bold"])
|
||||
ct.Print("* ")
|
||||
ct.Print(ansi.NormalPen)
|
||||
ct.Print(ansi.PenColor["red"])
|
||||
case debugger.Feedback:
|
||||
ct.Print(ansi.DimPens["white"])
|
||||
case debugger.Script:
|
||||
ct.Print("> ")
|
||||
case debugger.Prompt:
|
||||
ct.Print(ansi.PenStyles["bold"])
|
||||
}
|
||||
|
||||
ct.Print(s, a...)
|
||||
ct.Print(ansi.NormalPen)
|
||||
|
||||
// add a newline if print profile is anything other than prompt
|
||||
if pp != debugger.Prompt && pp != debugger.Input {
|
||||
ct.Print("\n")
|
||||
}
|
||||
}
|
||||
|
||||
// UserRead implementation for debugger.UI interface
|
||||
func (ct *ColorTerminal) UserRead(input []byte, prompt string) (int, error) {
|
||||
ct.RawMode()
|
||||
defer ct.CanonicalMode()
|
||||
|
||||
// er is used to store encoded runes (length of 4 should be enough)
|
||||
er := make([]byte, 4)
|
||||
|
||||
n := 0
|
||||
cursor := 0
|
||||
history := len(ct.commandHistory)
|
||||
|
||||
// buffInput is used to store the latest input when we scroll through
|
||||
// history - we don't want to lose what we've typed in case the user wants
|
||||
// to resume where we left off
|
||||
buffInput := make([]byte, cap(input))
|
||||
buffN := 0
|
||||
|
||||
// the method for cursor placement is as follows:
|
||||
// 1. for each iteration in the loop
|
||||
// 2. store current cursor position
|
||||
// 3. clear the current line
|
||||
// 4. output the prompt
|
||||
// 5. output the input buffer
|
||||
// 6. restore the cursor position
|
||||
//
|
||||
// for this to work we need to place the cursor in it's initial position,
|
||||
ct.Print("\r%s", ansi.CursorMove(len(prompt)))
|
||||
|
||||
for {
|
||||
ct.Print(ansi.CursorStore)
|
||||
ct.UserPrint(debugger.Prompt, "%s%s", ansi.ClearLine, prompt)
|
||||
ct.UserPrint(debugger.Input, string(input[:n]))
|
||||
ct.Print(ansi.CursorRestore)
|
||||
|
||||
r, _, err := ct.reader.ReadRune()
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
switch r {
|
||||
case easyterm.KeyCtrlC:
|
||||
// CTRL-C
|
||||
ct.Print("\n")
|
||||
return n + 1, &debugger.UserInterrupt{Message: "user interrupt: CTRL-C"}
|
||||
|
||||
case easyterm.KeyTab:
|
||||
|
||||
case easyterm.KeyCarriageReturn:
|
||||
// CARRIAGE RETURN
|
||||
|
||||
// check to see if input is the same as the last history entry
|
||||
newEntry := false
|
||||
if n > 0 {
|
||||
newEntry = true
|
||||
if len(ct.commandHistory) > 0 {
|
||||
lastHistoryEntry := ct.commandHistory[len(ct.commandHistory)-1].input
|
||||
if len(lastHistoryEntry) == n {
|
||||
newEntry = false
|
||||
for i := 0; i < n; i++ {
|
||||
if input[i] != lastHistoryEntry[i] {
|
||||
newEntry = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if input is not the same as the last history entry then append a
|
||||
// new entry to the history list
|
||||
if newEntry {
|
||||
nh := make([]byte, n)
|
||||
copy(nh, input[:n])
|
||||
ct.commandHistory = append(ct.commandHistory, command{input: nh})
|
||||
}
|
||||
|
||||
ct.Print("\n")
|
||||
return n + 1, nil
|
||||
|
||||
case easyterm.KeyEsc:
|
||||
// ESCAPE SEQUENCE BEGIN
|
||||
r, _, err := ct.reader.ReadRune()
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
switch r {
|
||||
case easyterm.EscCursor:
|
||||
// CURSOR KEY
|
||||
r, _, err := ct.reader.ReadRune()
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
switch r {
|
||||
case easyterm.CursorUp:
|
||||
// move up through command history
|
||||
if len(ct.commandHistory) > 0 {
|
||||
// if we're at the end of the command history then store
|
||||
// the current input in buffInput for possible later editing
|
||||
if history == len(ct.commandHistory) {
|
||||
copy(buffInput, input[:n])
|
||||
buffN = n
|
||||
}
|
||||
|
||||
if history > 0 {
|
||||
history--
|
||||
copy(input, ct.commandHistory[history].input)
|
||||
n = len(ct.commandHistory[history].input)
|
||||
ct.Print(ansi.CursorMove(n - cursor))
|
||||
cursor = n
|
||||
}
|
||||
}
|
||||
case easyterm.CursorDown:
|
||||
// move down through command history
|
||||
if len(ct.commandHistory) > 0 {
|
||||
if history < len(ct.commandHistory)-1 {
|
||||
history++
|
||||
copy(input, ct.commandHistory[history].input)
|
||||
n = len(ct.commandHistory[history].input)
|
||||
ct.Print(ansi.CursorMove(n - cursor))
|
||||
cursor = n
|
||||
} else if history == len(ct.commandHistory)-1 {
|
||||
history++
|
||||
copy(input, buffInput)
|
||||
n = buffN
|
||||
ct.Print(ansi.CursorMove(n - cursor))
|
||||
cursor = n
|
||||
}
|
||||
}
|
||||
case easyterm.CursorForward:
|
||||
// move forward through current command input
|
||||
if cursor < n {
|
||||
ct.Print(ansi.CursorForwardOne)
|
||||
cursor++
|
||||
}
|
||||
case easyterm.CursorBackward:
|
||||
// move backward through current command input
|
||||
if cursor > 0 {
|
||||
ct.Print(ansi.CursorBackwardOne)
|
||||
cursor--
|
||||
}
|
||||
|
||||
case easyterm.EscDelete:
|
||||
// DELETE
|
||||
if cursor < n {
|
||||
copy(input[cursor:], input[cursor+1:])
|
||||
cursor--
|
||||
n--
|
||||
history = len(ct.commandHistory)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case easyterm.KeyBackspace:
|
||||
// BACKSPACE
|
||||
if cursor > 0 {
|
||||
copy(input[cursor-1:], input[cursor:])
|
||||
ct.Print(ansi.CursorBackwardOne)
|
||||
cursor--
|
||||
n--
|
||||
history = len(ct.commandHistory)
|
||||
}
|
||||
|
||||
default:
|
||||
if unicode.IsLetter(r) || unicode.IsDigit(r) || unicode.IsSpace(r) {
|
||||
ct.Print("%c", r)
|
||||
m := utf8.EncodeRune(er, r)
|
||||
copy(input[cursor+m:], input[cursor:])
|
||||
copy(input[cursor:], er[:m])
|
||||
cursor++
|
||||
n += m
|
||||
history = len(ct.commandHistory)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
// RegisterTabCompleter adds an implementation of TabCompleter to the
|
||||
// ColorTerminal
|
||||
func (ct *ColorTerminal) RegisterTabCompleter(tc ui.TabCompleter) {
|
||||
ct.tabCompleter = tc
|
||||
}
|
||||
|
|
193
debugger/colorterm/input.go
Normal file
193
debugger/colorterm/input.go
Normal file
|
@ -0,0 +1,193 @@
|
|||
package colorterm
|
||||
|
||||
import (
|
||||
"gopher2600/debugger/colorterm/ansi"
|
||||
"gopher2600/debugger/colorterm/easyterm"
|
||||
"gopher2600/debugger/ui"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// UserRead is the top level input function
|
||||
func (ct *ColorTerminal) UserRead(input []byte, prompt string) (int, error) {
|
||||
ct.RawMode()
|
||||
defer ct.CanonicalMode()
|
||||
|
||||
// er is used to store encoded runes (length of 4 should be enough)
|
||||
er := make([]byte, 4)
|
||||
|
||||
n := 0
|
||||
cursor := 0
|
||||
history := len(ct.commandHistory)
|
||||
|
||||
// buffInput is used to store the latest input when we scroll through
|
||||
// history - we don't want to lose what we've typed in case the user wants
|
||||
// to resume where we left off
|
||||
buffInput := make([]byte, cap(input))
|
||||
buffN := 0
|
||||
|
||||
// the method for cursor placement is as follows:
|
||||
// 1. for each iteration in the loop
|
||||
// 2. store current cursor position
|
||||
// 3. clear the current line
|
||||
// 4. output the prompt
|
||||
// 5. output the input buffer
|
||||
// 6. restore the cursor position
|
||||
//
|
||||
// for this to work we need to place the cursor in it's initial position,
|
||||
ct.Print("\r%s", ansi.CursorMove(len(prompt)))
|
||||
|
||||
for {
|
||||
ct.Print(ansi.CursorStore)
|
||||
ct.UserPrint(ui.Prompt, "%s%s", ansi.ClearLine, prompt)
|
||||
ct.UserPrint(ui.Input, string(input[:n]))
|
||||
ct.Print(ansi.CursorRestore)
|
||||
|
||||
r, _, err := ct.reader.ReadRune()
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
switch r {
|
||||
case easyterm.KeyCtrlC:
|
||||
// CTRL-C
|
||||
ct.Print("\n")
|
||||
return n + 1, &ui.UserInterrupt{Message: "user interrupt: CTRL-C"}
|
||||
|
||||
case easyterm.KeyTab:
|
||||
if ct.tabCompleter != nil {
|
||||
s := ct.tabCompleter.GuessWord(string(input[:n]))
|
||||
n = len(s)
|
||||
copy(input, []byte(s))
|
||||
}
|
||||
|
||||
case easyterm.KeyCarriageReturn:
|
||||
// CARRIAGE RETURN
|
||||
|
||||
// check to see if input is the same as the last history entry
|
||||
newEntry := false
|
||||
if n > 0 {
|
||||
newEntry = true
|
||||
if len(ct.commandHistory) > 0 {
|
||||
lastHistoryEntry := ct.commandHistory[len(ct.commandHistory)-1].input
|
||||
if len(lastHistoryEntry) == n {
|
||||
newEntry = false
|
||||
for i := 0; i < n; i++ {
|
||||
if input[i] != lastHistoryEntry[i] {
|
||||
newEntry = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if input is not the same as the last history entry then append a
|
||||
// new entry to the history list
|
||||
if newEntry {
|
||||
nh := make([]byte, n)
|
||||
copy(nh, input[:n])
|
||||
ct.commandHistory = append(ct.commandHistory, command{input: nh})
|
||||
}
|
||||
|
||||
ct.Print("\n")
|
||||
return n + 1, nil
|
||||
|
||||
case easyterm.KeyEsc:
|
||||
// ESCAPE SEQUENCE BEGIN
|
||||
r, _, err := ct.reader.ReadRune()
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
switch r {
|
||||
case easyterm.EscCursor:
|
||||
// CURSOR KEY
|
||||
r, _, err := ct.reader.ReadRune()
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
switch r {
|
||||
case easyterm.CursorUp:
|
||||
// move up through command history
|
||||
if len(ct.commandHistory) > 0 {
|
||||
// if we're at the end of the command history then store
|
||||
// the current input in buffInput for possible later editing
|
||||
if history == len(ct.commandHistory) {
|
||||
copy(buffInput, input[:n])
|
||||
buffN = n
|
||||
}
|
||||
|
||||
if history > 0 {
|
||||
history--
|
||||
copy(input, ct.commandHistory[history].input)
|
||||
n = len(ct.commandHistory[history].input)
|
||||
ct.Print(ansi.CursorMove(n - cursor))
|
||||
cursor = n
|
||||
}
|
||||
}
|
||||
case easyterm.CursorDown:
|
||||
// move down through command history
|
||||
if len(ct.commandHistory) > 0 {
|
||||
if history < len(ct.commandHistory)-1 {
|
||||
history++
|
||||
copy(input, ct.commandHistory[history].input)
|
||||
n = len(ct.commandHistory[history].input)
|
||||
ct.Print(ansi.CursorMove(n - cursor))
|
||||
cursor = n
|
||||
} else if history == len(ct.commandHistory)-1 {
|
||||
history++
|
||||
copy(input, buffInput)
|
||||
n = buffN
|
||||
ct.Print(ansi.CursorMove(n - cursor))
|
||||
cursor = n
|
||||
}
|
||||
}
|
||||
case easyterm.CursorForward:
|
||||
// move forward through current command input
|
||||
if cursor < n {
|
||||
ct.Print(ansi.CursorForwardOne)
|
||||
cursor++
|
||||
}
|
||||
case easyterm.CursorBackward:
|
||||
// move backward through current command input
|
||||
if cursor > 0 {
|
||||
ct.Print(ansi.CursorBackwardOne)
|
||||
cursor--
|
||||
}
|
||||
|
||||
case easyterm.EscDelete:
|
||||
// DELETE
|
||||
if cursor < n {
|
||||
copy(input[cursor:], input[cursor+1:])
|
||||
cursor--
|
||||
n--
|
||||
history = len(ct.commandHistory)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case easyterm.KeyBackspace:
|
||||
// BACKSPACE
|
||||
if cursor > 0 {
|
||||
copy(input[cursor-1:], input[cursor:])
|
||||
ct.Print(ansi.CursorBackwardOne)
|
||||
cursor--
|
||||
n--
|
||||
history = len(ct.commandHistory)
|
||||
}
|
||||
|
||||
default:
|
||||
if unicode.IsLetter(r) || unicode.IsDigit(r) || unicode.IsSpace(r) {
|
||||
ct.Print("%c", r)
|
||||
m := utf8.EncodeRune(er, r)
|
||||
copy(input[cursor+m:], input[cursor:])
|
||||
copy(input[cursor:], er[:m])
|
||||
cursor++
|
||||
n += m
|
||||
history = len(ct.commandHistory)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
42
debugger/colorterm/output.go
Normal file
42
debugger/colorterm/output.go
Normal file
|
@ -0,0 +1,42 @@
|
|||
package colorterm
|
||||
|
||||
import (
|
||||
"gopher2600/debugger/colorterm/ansi"
|
||||
"gopher2600/debugger/ui"
|
||||
)
|
||||
|
||||
// UserPrint is the top level output function
|
||||
func (ct *ColorTerminal) UserPrint(pp ui.PrintProfile, s string, a ...interface{}) {
|
||||
if pp != ui.Input {
|
||||
ct.Print("\r")
|
||||
}
|
||||
|
||||
switch pp {
|
||||
case ui.CPUStep:
|
||||
ct.Print(ansi.PenColor["yellow"])
|
||||
case ui.VideoStep:
|
||||
ct.Print(ansi.DimPens["yellow"])
|
||||
case ui.MachineInfo:
|
||||
ct.Print(ansi.PenColor["cyan"])
|
||||
case ui.Error:
|
||||
ct.Print(ansi.PenColor["red"])
|
||||
ct.Print(ansi.PenColor["bold"])
|
||||
ct.Print("* ")
|
||||
ct.Print(ansi.NormalPen)
|
||||
ct.Print(ansi.PenColor["red"])
|
||||
case ui.Feedback:
|
||||
ct.Print(ansi.DimPens["white"])
|
||||
case ui.Script:
|
||||
ct.Print("> ")
|
||||
case ui.Prompt:
|
||||
ct.Print(ansi.PenStyles["bold"])
|
||||
}
|
||||
|
||||
ct.Print(s, a...)
|
||||
ct.Print(ansi.NormalPen)
|
||||
|
||||
// add a newline if print profile is anything other than prompt
|
||||
if pp != ui.Prompt && pp != ui.Input {
|
||||
ct.Print("\n")
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ package debugger
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"gopher2600/debugger/ui"
|
||||
"gopher2600/hardware"
|
||||
"gopher2600/hardware/cpu"
|
||||
"gopher2600/television"
|
||||
|
@ -39,7 +40,7 @@ type Debugger struct {
|
|||
inputloopVideoClock bool // step mode
|
||||
|
||||
// user interface
|
||||
ui UserInterface
|
||||
ui ui.UserInterface
|
||||
uiSilent bool // controls whether UI is to remain silent
|
||||
}
|
||||
|
||||
|
@ -73,15 +74,15 @@ func NewDebugger() (*Debugger, error) {
|
|||
}
|
||||
|
||||
// Start the main debugger sequence
|
||||
func (dbg *Debugger) Start(ui UserInterface, filename string) error {
|
||||
func (dbg *Debugger) Start(interf ui.UserInterface, filename string) error {
|
||||
// prepare user interface
|
||||
if ui == nil {
|
||||
dbg.ui = new(PlainTerminal)
|
||||
if interf == nil {
|
||||
dbg.ui = new(ui.PlainTerminal)
|
||||
if dbg.ui == nil {
|
||||
return fmt.Errorf("error allocationg memory for UI")
|
||||
}
|
||||
} else {
|
||||
dbg.ui = ui
|
||||
dbg.ui = interf
|
||||
}
|
||||
|
||||
err := dbg.ui.Initialise()
|
||||
|
@ -90,6 +91,8 @@ func (dbg *Debugger) Start(ui UserInterface, filename string) error {
|
|||
}
|
||||
defer dbg.ui.CleanUp()
|
||||
|
||||
dbg.ui.RegisterTabCompleter(dbg)
|
||||
|
||||
err = dbg.vcs.AttachCartridge(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -123,7 +126,7 @@ func (dbg *Debugger) Start(ui UserInterface, filename string) error {
|
|||
// videoCycleInputLoop is a wrapper function to be used when calling vcs.Step()
|
||||
func (dbg *Debugger) videoCycleInputLoop(result *cpu.InstructionResult) error {
|
||||
if dbg.inputloopVideoClock {
|
||||
dbg.print(VideoStep, "%v", result)
|
||||
dbg.print(ui.VideoStep, "%v", result)
|
||||
}
|
||||
return dbg.inputLoop(false)
|
||||
}
|
||||
|
@ -182,8 +185,8 @@ func (dbg *Debugger) inputLoop(mainLoop bool) error {
|
|||
n, err := dbg.ui.UserRead(dbg.input, prompt)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case *UserInterrupt:
|
||||
dbg.print(Feedback, err.Error())
|
||||
case *ui.UserInterrupt:
|
||||
dbg.print(ui.Feedback, err.Error())
|
||||
return nil
|
||||
default:
|
||||
return err
|
||||
|
@ -193,7 +196,7 @@ func (dbg *Debugger) inputLoop(mainLoop bool) error {
|
|||
// parse user input
|
||||
dbg.inputloopNext, err = dbg.parseInput(string(dbg.input[:n-1]))
|
||||
if err != nil {
|
||||
dbg.print(Error, "%s", err)
|
||||
dbg.print(ui.Error, "%s", err)
|
||||
}
|
||||
|
||||
// prepare for next loop
|
||||
|
@ -218,7 +221,7 @@ func (dbg *Debugger) inputLoop(mainLoop bool) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dbg.print(CPUStep, "%v", result)
|
||||
dbg.print(ui.CPUStep, "%v", result)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
@ -302,7 +305,7 @@ func (dbg *Debugger) parseCommand(input string) (bool, error) {
|
|||
} else {
|
||||
if parts[1] == "OFF" {
|
||||
dbg.commandOnHalt = ""
|
||||
dbg.print(Feedback, "no auto-command on halt")
|
||||
dbg.print(ui.Feedback, "no auto-command on halt")
|
||||
return false, nil
|
||||
}
|
||||
|
||||
|
@ -320,10 +323,10 @@ func (dbg *Debugger) parseCommand(input string) (bool, error) {
|
|||
dbg.commandOnHaltStored = dbg.commandOnHalt
|
||||
}
|
||||
|
||||
dbg.print(Feedback, "auto-command on halt: %s", dbg.commandOnHalt)
|
||||
dbg.print(ui.Feedback, "auto-command on halt: %s", dbg.commandOnHalt)
|
||||
|
||||
case "MEMMAP":
|
||||
dbg.print(MachineInfo, "%v", dbg.vcs.Mem.MemoryMap())
|
||||
dbg.print(ui.MachineInfo, "%v", dbg.vcs.Mem.MemoryMap())
|
||||
|
||||
case "QUIT":
|
||||
dbg.running = false
|
||||
|
@ -333,7 +336,7 @@ func (dbg *Debugger) parseCommand(input string) (bool, error) {
|
|||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
dbg.print(Feedback, "machine reset")
|
||||
dbg.print(ui.Feedback, "machine reset")
|
||||
|
||||
case "RUN":
|
||||
dbg.runUntilHalt = true
|
||||
|
@ -367,21 +370,21 @@ func (dbg *Debugger) parseCommand(input string) (bool, error) {
|
|||
} else {
|
||||
stepMode = "cpu"
|
||||
}
|
||||
dbg.print(Feedback, "step mode: %s", stepMode)
|
||||
dbg.print(ui.Feedback, "step mode: %s", stepMode)
|
||||
|
||||
case "TERSE":
|
||||
dbg.machineInfoVerbose = false
|
||||
dbg.print(Feedback, "verbosity: terse")
|
||||
dbg.print(ui.Feedback, "verbosity: terse")
|
||||
|
||||
case "VERBOSE":
|
||||
dbg.machineInfoVerbose = true
|
||||
dbg.print(Feedback, "verbosity: verbose")
|
||||
dbg.print(ui.Feedback, "verbosity: verbose")
|
||||
|
||||
case "VERBOSITY":
|
||||
if dbg.machineInfoVerbose {
|
||||
dbg.print(Feedback, "verbosity: verbose")
|
||||
dbg.print(ui.Feedback, "verbosity: verbose")
|
||||
} else {
|
||||
dbg.print(Feedback, "verbosity: terse")
|
||||
dbg.print(ui.Feedback, "verbosity: terse")
|
||||
}
|
||||
|
||||
case "DEBUGGERSTATE":
|
||||
|
@ -403,14 +406,14 @@ func (dbg *Debugger) parseCommand(input string) (bool, error) {
|
|||
for i := 1; i < len(parts); i++ {
|
||||
addr, err := strconv.ParseUint(parts[i], 0, 16)
|
||||
if err != nil {
|
||||
dbg.print(Error, "bad argument to PEEK (%s)", parts[i])
|
||||
dbg.print(ui.Error, "bad argument to PEEK (%s)", parts[i])
|
||||
continue
|
||||
}
|
||||
|
||||
// peform peek
|
||||
val, mappedAddress, areaName, addressLabel, err := dbg.vcs.Mem.Peek(uint16(addr))
|
||||
if err != nil {
|
||||
dbg.print(Error, "%s", err)
|
||||
dbg.print(ui.Error, "%s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -423,7 +426,7 @@ func (dbg *Debugger) parseCommand(input string) (bool, error) {
|
|||
if addressLabel != "" {
|
||||
s = fmt.Sprintf("%s [%s]", s, addressLabel)
|
||||
}
|
||||
dbg.print(MachineInfo, s)
|
||||
dbg.print(ui.MachineInfo, s)
|
||||
}
|
||||
|
||||
case "RIOT":
|
||||
|
@ -458,3 +461,8 @@ func (dbg *Debugger) parseCommand(input string) (bool, error) {
|
|||
|
||||
return stepNext, nil
|
||||
}
|
||||
|
||||
// GuessWord implements tabcompletion.TabCompleter interface
|
||||
func (dbg *Debugger) GuessWord(input string) string {
|
||||
return input
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package debugger
|
||||
|
||||
import "gopher2600/debugger/ui"
|
||||
|
||||
// types that satisfy machineInfo return information about the state of the
|
||||
// emulated machine. String() should return verbose info, while StringTerse()
|
||||
// the more terse equivalent.
|
||||
|
@ -10,7 +12,7 @@ type machineInfo interface {
|
|||
}
|
||||
|
||||
func (dbg Debugger) printMachineInfo(mi machineInfo) {
|
||||
dbg.print(MachineInfo, "%s", dbg.sprintMachineInfo(mi))
|
||||
dbg.print(ui.MachineInfo, "%s", dbg.sprintMachineInfo(mi))
|
||||
}
|
||||
|
||||
func (dbg Debugger) sprintMachineInfo(mi machineInfo) string {
|
||||
|
|
|
@ -1,38 +1,16 @@
|
|||
package debugger
|
||||
|
||||
import (
|
||||
"gopher2600/debugger/ui"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// PrintProfile specifies the printing mode
|
||||
type PrintProfile int
|
||||
|
||||
// enumeration of print profile types
|
||||
const (
|
||||
// the following profiles ar generated as a result of the emulation
|
||||
CPUStep PrintProfile = iota
|
||||
VideoStep
|
||||
MachineInfo
|
||||
|
||||
// the following profiles are generated by the debugger in response to user
|
||||
// input
|
||||
Feedback
|
||||
Prompt
|
||||
Script
|
||||
|
||||
// user input (not used by all user interface types [eg. echoing terminals])
|
||||
Input
|
||||
|
||||
// errors can be generated by the emulation or the debugger
|
||||
Error
|
||||
)
|
||||
|
||||
// wrapper function for UserPrint(). useful for normalising the input string
|
||||
// before passing to the real UserPrint. it also allows us to easily obey
|
||||
// directives such as the silent directive without passing the burden onto UI
|
||||
// implementors
|
||||
func (dbg Debugger) print(pp PrintProfile, s string, a ...interface{}) {
|
||||
if dbg.uiSilent && pp != Error {
|
||||
func (dbg Debugger) print(pp ui.PrintProfile, s string, a ...interface{}) {
|
||||
if dbg.uiSilent && pp != ui.Error {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ package debugger
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"gopher2600/debugger/ui"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
@ -62,14 +63,14 @@ func (dbg *Debugger) RunScript(scriptfile string, silent bool) error {
|
|||
for i := 0; i < len(lines); i++ {
|
||||
if strings.Trim(lines[i], " ") != "" {
|
||||
if !silent {
|
||||
dbg.print(Script, lines[i])
|
||||
dbg.print(ui.Script, lines[i])
|
||||
}
|
||||
next, err := dbg.parseInput(lines[i])
|
||||
if err != nil {
|
||||
dbg.print(Error, fmt.Sprintf("script error (%s): %s", scriptfile, err.Error()))
|
||||
dbg.print(ui.Error, fmt.Sprintf("script error (%s): %s", scriptfile, err.Error()))
|
||||
}
|
||||
if next {
|
||||
dbg.print(Error, fmt.Sprintf("script error (%s): use of '%s' is not recommended in scripts", scriptfile, lines[i]))
|
||||
dbg.print(ui.Error, fmt.Sprintf("script error (%s): use of '%s' is not recommended in scripts", scriptfile, lines[i]))
|
||||
|
||||
// make sure run state is still sane
|
||||
dbg.runUntilHalt = false
|
||||
|
|
|
@ -2,6 +2,7 @@ package debugger
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"gopher2600/debugger/ui"
|
||||
)
|
||||
|
||||
// traps keeps track of all the currently defined trappers
|
||||
|
@ -37,7 +38,7 @@ func (tr *traps) check() bool {
|
|||
ntr := tr.traps[i].target.ToInt() != tr.traps[i].origValue
|
||||
if ntr {
|
||||
tr.traps[i].origValue = tr.traps[i].target.ToInt()
|
||||
tr.dbg.print(Feedback, "trap on %s", tr.traps[i].target.ShortLabel())
|
||||
tr.dbg.print(ui.Feedback, "trap on %s", tr.traps[i].target.ShortLabel())
|
||||
}
|
||||
trapped = ntr || trapped
|
||||
}
|
||||
|
@ -47,7 +48,7 @@ func (tr *traps) check() bool {
|
|||
|
||||
func (tr traps) list() {
|
||||
if len(tr.traps) == 0 {
|
||||
tr.dbg.print(Feedback, "no traps")
|
||||
tr.dbg.print(ui.Feedback, "no traps")
|
||||
} else {
|
||||
s := ""
|
||||
sep := ""
|
||||
|
@ -55,7 +56,7 @@ func (tr traps) list() {
|
|||
s = fmt.Sprintf("%s%s%s", s, sep, tr.traps[i].target.ShortLabel())
|
||||
sep = ", "
|
||||
}
|
||||
tr.dbg.print(Feedback, s)
|
||||
tr.dbg.print(ui.Feedback, s)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -70,7 +71,7 @@ func (tr *traps) parseTrap(parts []string) error {
|
|||
switch parts[i] {
|
||||
case "CLEAR":
|
||||
tr.clear()
|
||||
tr.dbg.print(Feedback, "traps cleared")
|
||||
tr.dbg.print(ui.Feedback, "traps cleared")
|
||||
return nil
|
||||
case "LIST":
|
||||
tr.list()
|
||||
|
|
|
@ -1,29 +1,10 @@
|
|||
package debugger
|
||||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// UserInterface defines the user interface operations required by the debugger
|
||||
type UserInterface interface {
|
||||
Initialise() error
|
||||
CleanUp()
|
||||
UserPrint(PrintProfile, string, ...interface{})
|
||||
UserRead([]byte, string) (int, error)
|
||||
}
|
||||
|
||||
// UserInterrupt can be returned by UserRead() when user has cause an
|
||||
// interrupt (ie. CTRL-C)
|
||||
type UserInterrupt struct {
|
||||
Message string
|
||||
}
|
||||
|
||||
// implement Error interface for UserInterrupt
|
||||
func (intr UserInterrupt) Error() string {
|
||||
return intr.Message
|
||||
}
|
||||
|
||||
// PlainTerminal is the default, most basic terminal interface
|
||||
type PlainTerminal struct {
|
||||
}
|
||||
|
@ -37,6 +18,10 @@ func (pt *PlainTerminal) Initialise() error {
|
|||
func (pt *PlainTerminal) CleanUp() {
|
||||
}
|
||||
|
||||
// RegisterTabCompleter adds an implementation of TabCompleter to the terminal
|
||||
func (pt *PlainTerminal) RegisterTabCompleter(tc TabCompleter) {
|
||||
}
|
||||
|
||||
// UserPrint is the plain terminal print routine
|
||||
func (pt PlainTerminal) UserPrint(pp PrintProfile, s string, a ...interface{}) {
|
||||
switch pp {
|
24
debugger/ui/printprofile.go
Normal file
24
debugger/ui/printprofile.go
Normal file
|
@ -0,0 +1,24 @@
|
|||
package ui
|
||||
|
||||
// PrintProfile specifies the printing mode
|
||||
type PrintProfile int
|
||||
|
||||
// enumeration of print profile types
|
||||
const (
|
||||
// the following profiles ar generated as a result of the emulation
|
||||
CPUStep PrintProfile = iota
|
||||
VideoStep
|
||||
MachineInfo
|
||||
|
||||
// the following profiles are generated by the debugger in response to user
|
||||
// input
|
||||
Feedback
|
||||
Prompt
|
||||
Script
|
||||
|
||||
// user input (not used by all user interface types [eg. echoing terminals])
|
||||
Input
|
||||
|
||||
// errors can be generated by the emulation or the debugger
|
||||
Error
|
||||
)
|
6
debugger/ui/tabcompletion.go
Normal file
6
debugger/ui/tabcompletion.go
Normal file
|
@ -0,0 +1,6 @@
|
|||
package ui
|
||||
|
||||
// TabCompleter defines the types that can be used for tab completion
|
||||
type TabCompleter interface {
|
||||
GuessWord(string) string
|
||||
}
|
21
debugger/ui/ui.go
Normal file
21
debugger/ui/ui.go
Normal file
|
@ -0,0 +1,21 @@
|
|||
package ui
|
||||
|
||||
// UserInterface defines the user interface operations required by the debugger
|
||||
type UserInterface interface {
|
||||
Initialise() error
|
||||
CleanUp()
|
||||
RegisterTabCompleter(TabCompleter)
|
||||
UserPrint(PrintProfile, string, ...interface{})
|
||||
UserRead([]byte, string) (int, error)
|
||||
}
|
||||
|
||||
// UserInterrupt can be returned by UserRead() when user has cause an
|
||||
// interrupt (ie. CTRL-C)
|
||||
type UserInterrupt struct {
|
||||
Message string
|
||||
}
|
||||
|
||||
// implement Error interface for UserInterrupt
|
||||
func (intr UserInterrupt) Error() string {
|
||||
return intr.Message
|
||||
}
|
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
"gopher2600/debugger"
|
||||
"gopher2600/debugger/colorterm"
|
||||
"gopher2600/debugger/ui"
|
||||
"gopher2600/hardware"
|
||||
"gopher2600/television"
|
||||
"os"
|
||||
|
@ -41,7 +42,7 @@ func main() {
|
|||
}
|
||||
|
||||
// start debugger with choice of interface and cartridge
|
||||
var term debugger.UserInterface
|
||||
var term ui.UserInterface
|
||||
|
||||
switch strings.ToUpper(*termType) {
|
||||
case "COLOR":
|
||||
|
@ -50,7 +51,7 @@ func main() {
|
|||
fmt.Printf("! unknown terminal type (%s) defaulting to plain\n", *termType)
|
||||
fallthrough
|
||||
case "PLAIN":
|
||||
term = new(debugger.PlainTerminal)
|
||||
term = new(ui.PlainTerminal)
|
||||
}
|
||||
|
||||
err = dbg.Start(term, cartridgeFile)
|
||||
|
|
Loading…
Add table
Reference in a new issue