mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2025-04-02 11:02:17 -04:00
o debugger/input
- parser package renamed to input - tokeniser moved to input - command templates are now specified using a simple markup language
This commit is contained in:
parent
d744e6229a
commit
e96f052c1f
17 changed files with 403 additions and 288 deletions
|
@ -5,7 +5,10 @@
|
|||
package debugger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gopher2600/debugger/input"
|
||||
"gopher2600/debugger/ui"
|
||||
"gopher2600/errors"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
|
@ -88,21 +91,28 @@ func (bp breakpoints) list() {
|
|||
}
|
||||
}
|
||||
|
||||
func (bp *breakpoints) parseBreakpoint(tokens *tokens) error {
|
||||
func (bp *breakpoints) parseBreakpoint(tokens *input.Tokens) error {
|
||||
var tgt target
|
||||
|
||||
// resolvedTarget notes whether a target has been used correctly
|
||||
var resolvedTarget bool
|
||||
|
||||
// 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 = bp.dbg.vcs.MC.PC
|
||||
|
||||
// resolvedTarget is true to begin with so that the initial target of PC
|
||||
// can be changed immediately
|
||||
resolvedTarget = true
|
||||
|
||||
// loop over tokens. if token is a number then add the breakpoint for the
|
||||
// current target. if it is not a number, look for a keyword that changes
|
||||
// the target (or run a BREAK meta-command)
|
||||
//
|
||||
// note that this method of looping allows the user to chain break commands
|
||||
a, present := tokens.get()
|
||||
a, present := tokens.Get()
|
||||
for present {
|
||||
val, err := strconv.ParseUint(a, 0, 16)
|
||||
if err == nil {
|
||||
|
@ -118,15 +128,25 @@ func (bp *breakpoints) parseBreakpoint(tokens *tokens) error {
|
|||
if addNewBreak {
|
||||
bp.breaks = append(bp.breaks, breaker{target: tgt, value: int(val)})
|
||||
}
|
||||
resolvedTarget = true
|
||||
} else {
|
||||
tokens.unget()
|
||||
if !resolvedTarget {
|
||||
return errors.NewGopherError(errors.InputTooFewArgs, fmt.Errorf("need a value to break on (%s)", tgt.Label()))
|
||||
}
|
||||
|
||||
tokens.Unget()
|
||||
tgt, err = parseTarget(bp.dbg, tokens)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resolvedTarget = false
|
||||
}
|
||||
|
||||
a, present = tokens.get()
|
||||
a, present = tokens.Get()
|
||||
}
|
||||
|
||||
if !resolvedTarget {
|
||||
return errors.NewGopherError(errors.InputTooFewArgs, fmt.Errorf("need a value to break on (%s)", tgt.Label()))
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -3,7 +3,7 @@ package colorterm
|
|||
import (
|
||||
"bufio"
|
||||
"gopher2600/debugger/colorterm/easyterm"
|
||||
"gopher2600/debugger/parser"
|
||||
"gopher2600/debugger/input"
|
||||
"os"
|
||||
)
|
||||
|
||||
|
@ -14,7 +14,7 @@ type ColorTerminal struct {
|
|||
reader *bufio.Reader
|
||||
commandHistory []command
|
||||
|
||||
tabCompleter *parser.TabCompletion
|
||||
tabCompleter *input.TabCompletion
|
||||
}
|
||||
|
||||
type command struct {
|
||||
|
@ -43,6 +43,6 @@ func (ct *ColorTerminal) CleanUp() {
|
|||
|
||||
// RegisterTabCompleter adds an implementation of TabCompleter to the
|
||||
// ColorTerminal
|
||||
func (ct *ColorTerminal) RegisterTabCompleter(tc *parser.TabCompletion) {
|
||||
func (ct *ColorTerminal) RegisterTabCompleter(tc *input.TabCompletion) {
|
||||
ct.tabCompleter = tc
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package debugger
|
||||
|
||||
import "gopher2600/debugger/parser"
|
||||
import (
|
||||
"fmt"
|
||||
"gopher2600/debugger/input"
|
||||
)
|
||||
|
||||
// debugger keywords. not a useful data structure but we can use these to form
|
||||
// the more useful DebuggerCommands and Help structures
|
||||
|
@ -38,58 +41,8 @@ const (
|
|||
KeywordDisplay = "DISPLAY"
|
||||
KeywordScript = "SCRIPT"
|
||||
KeywordDisassemble = "DISASSEMBLE"
|
||||
|
||||
SubKeywordBreaks = "BREAKS"
|
||||
SubKeywordTraps = "TRAPS"
|
||||
SubKeywordVideo = "VIDEO"
|
||||
SubKeywordCPU = "CPU"
|
||||
)
|
||||
|
||||
// DebuggerCommands provides:
|
||||
// - the list of debugger commands (keys to the map)
|
||||
// - the tab completion method for each argument for each command
|
||||
var DebuggerCommands = parser.Commands{
|
||||
KeywordInsert: parser.CommandArgs{parser.Arg{Typ: parser.ArgFile, Req: true}},
|
||||
KeywordSymbol: parser.CommandArgs{parser.Arg{Typ: parser.ArgString, Req: true}},
|
||||
KeywordBreak: parser.CommandArgs{parser.Arg{Typ: parser.ArgTarget, Req: true}, parser.Arg{Typ: parser.ArgValue, Req: false}, parser.Arg{Typ: parser.ArgIndeterminate}},
|
||||
KeywordTrap: parser.CommandArgs{parser.Arg{Typ: parser.ArgTarget, Req: true}, parser.Arg{Typ: parser.ArgIndeterminate}},
|
||||
KeywordList: parser.CommandArgs{parser.Arg{Typ: parser.ArgKeyword, Req: true, Vals: parser.Keywords{SubKeywordBreaks, SubKeywordTraps}}},
|
||||
KeywordClear: parser.CommandArgs{parser.Arg{Typ: parser.ArgKeyword, Req: true, Vals: parser.Keywords{SubKeywordBreaks, SubKeywordTraps}}},
|
||||
KeywordOnHalt: parser.CommandArgs{parser.Arg{Typ: parser.ArgIndeterminate}},
|
||||
KeywordOnStep: parser.CommandArgs{parser.Arg{Typ: parser.ArgIndeterminate}},
|
||||
KeywordLast: parser.CommandArgs{},
|
||||
KeywordMemMap: parser.CommandArgs{},
|
||||
KeywordQuit: parser.CommandArgs{},
|
||||
KeywordReset: parser.CommandArgs{},
|
||||
KeywordRun: parser.CommandArgs{},
|
||||
KeywordStep: parser.CommandArgs{},
|
||||
KeywordStepMode: parser.CommandArgs{parser.Arg{Typ: parser.ArgKeyword, Req: false, Vals: parser.Keywords{SubKeywordCPU, SubKeywordVideo}}},
|
||||
KeywordTerse: parser.CommandArgs{},
|
||||
KeywordVerbose: parser.CommandArgs{},
|
||||
KeywordVerbosity: parser.CommandArgs{},
|
||||
KeywordDebuggerState: parser.CommandArgs{},
|
||||
KeywordCPU: parser.CommandArgs{},
|
||||
KeywordPeek: parser.CommandArgs{parser.Arg{Typ: parser.ArgValue | parser.ArgString, Req: true}, parser.Arg{Typ: parser.ArgIndeterminate}},
|
||||
KeywordRAM: parser.CommandArgs{},
|
||||
KeywordRIOT: parser.CommandArgs{},
|
||||
KeywordTIA: parser.CommandArgs{},
|
||||
KeywordTV: parser.CommandArgs{},
|
||||
KeywordPlayer: parser.CommandArgs{},
|
||||
KeywordMissile: parser.CommandArgs{},
|
||||
KeywordBall: parser.CommandArgs{},
|
||||
KeywordPlayfield: parser.CommandArgs{},
|
||||
KeywordDisplay: parser.CommandArgs{parser.Arg{Typ: parser.ArgValue, Req: false}},
|
||||
KeywordScript: parser.CommandArgs{parser.Arg{Typ: parser.ArgFile, Req: true}},
|
||||
KeywordDisassemble: parser.CommandArgs{},
|
||||
}
|
||||
|
||||
func init() {
|
||||
// add the help command. we can't add the complete definition for the
|
||||
// command in the DebuggerCommands declaration because the list of Keywords
|
||||
// refers to DebuggerCommands itself
|
||||
DebuggerCommands[KeywordHelp] = parser.CommandArgs{parser.Arg{Typ: parser.ArgKeyword, Req: false, Vals: &DebuggerCommands}}
|
||||
}
|
||||
|
||||
// Help contains the help text for the debugger's top level commands
|
||||
var Help = map[string]string{
|
||||
KeywordHelp: "Lists commands and provides help for individual debugger commands",
|
||||
|
@ -126,3 +79,51 @@ var Help = map[string]string{
|
|||
KeywordScript: "Run commands from specified file",
|
||||
KeywordDisassemble: "Print the full cartridge disassembly",
|
||||
}
|
||||
|
||||
var commandTemplate = input.CommandTemplate{
|
||||
KeywordInsert: "%F",
|
||||
KeywordSymbol: "%V",
|
||||
KeywordBreak: "%*",
|
||||
KeywordTrap: "%*",
|
||||
KeywordList: "[BREAKS|TRAPS]",
|
||||
KeywordClear: "[BREAKS|TRAPS]",
|
||||
KeywordOnHalt: "%*",
|
||||
KeywordOnStep: "%*",
|
||||
KeywordLast: "",
|
||||
KeywordMemMap: "",
|
||||
KeywordQuit: "",
|
||||
KeywordReset: "",
|
||||
KeywordRun: "",
|
||||
KeywordStep: "",
|
||||
KeywordStepMode: "[CPU|VIDEO]",
|
||||
KeywordTerse: "",
|
||||
KeywordVerbose: "",
|
||||
KeywordVerbosity: "",
|
||||
KeywordDebuggerState: "",
|
||||
KeywordCPU: "",
|
||||
KeywordPeek: "%*",
|
||||
KeywordRAM: "",
|
||||
KeywordRIOT: "",
|
||||
KeywordTIA: "",
|
||||
KeywordTV: "",
|
||||
KeywordPlayer: "",
|
||||
KeywordMissile: "",
|
||||
KeywordBall: "",
|
||||
KeywordPlayfield: "",
|
||||
KeywordDisplay: "[|OFF]",
|
||||
KeywordScript: "%F",
|
||||
KeywordDisassemble: "",
|
||||
}
|
||||
|
||||
// DebuggerCommands is the tree of valid commands
|
||||
var DebuggerCommands input.Commands
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
|
||||
// parse command template
|
||||
DebuggerCommands, err = input.CompileCommandTemplate(commandTemplate, KeywordHelp)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("error compiling command template: %s", err))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ package debugger
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"gopher2600/debugger/parser"
|
||||
"gopher2600/debugger/input"
|
||||
"gopher2600/debugger/ui"
|
||||
"gopher2600/disassembly"
|
||||
"gopher2600/errors"
|
||||
|
@ -126,7 +126,7 @@ func (dbg *Debugger) Start(interf ui.UserInterface, filename string, initScript
|
|||
}
|
||||
defer dbg.ui.CleanUp()
|
||||
|
||||
dbg.ui.RegisterTabCompleter(parser.NewTabCompletion(DebuggerCommands))
|
||||
dbg.ui.RegisterTabCompleter(input.NewTabCompletion(DebuggerCommands))
|
||||
|
||||
err = dbg.loadCartridge(filename)
|
||||
if err != nil {
|
||||
|
@ -408,15 +408,15 @@ func (dbg *Debugger) parseInput(input string) (bool, error) {
|
|||
// 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(input string) (bool, error) {
|
||||
func (dbg *Debugger) parseCommand(userInput string) (bool, error) {
|
||||
// TODO: categorise commands into script-safe and non-script-safe
|
||||
|
||||
// tokenise input
|
||||
tokens := tokeniseInput(input)
|
||||
tokens := input.TokeniseInput(userInput)
|
||||
|
||||
// check validity of input -- this allows us to catch errors early and in
|
||||
// many cases to ignore the "success" flag when calling tokens.item()
|
||||
if err := DebuggerCommands.ValidateInput(tokens.tokens); err != nil {
|
||||
if err := DebuggerCommands.ValidateInput(tokens); err != nil {
|
||||
switch err := err.(type) {
|
||||
case errors.GopherError:
|
||||
switch err.Errno {
|
||||
|
@ -431,8 +431,8 @@ func (dbg *Debugger) parseCommand(input string) (bool, error) {
|
|||
// most commands do not cause the emulator to step forward
|
||||
stepNext := false
|
||||
|
||||
tokens.reset()
|
||||
command, _ := tokens.get()
|
||||
tokens.Reset()
|
||||
command, _ := tokens.Get()
|
||||
command = strings.ToUpper(command)
|
||||
switch command {
|
||||
default:
|
||||
|
@ -440,7 +440,7 @@ func (dbg *Debugger) parseCommand(input string) (bool, error) {
|
|||
|
||||
// control of the debugger
|
||||
case KeywordHelp:
|
||||
keyword, present := tokens.get()
|
||||
keyword, present := tokens.Get()
|
||||
if present {
|
||||
s := strings.ToUpper(keyword)
|
||||
txt, prs := Help[s]
|
||||
|
@ -456,7 +456,7 @@ func (dbg *Debugger) parseCommand(input string) (bool, error) {
|
|||
}
|
||||
|
||||
case KeywordInsert:
|
||||
cart, _ := tokens.get()
|
||||
cart, _ := tokens.Get()
|
||||
err := dbg.loadCartridge(cart)
|
||||
if err != nil {
|
||||
return false, err
|
||||
|
@ -464,7 +464,7 @@ func (dbg *Debugger) parseCommand(input string) (bool, error) {
|
|||
dbg.print(ui.Feedback, "machine reset with new cartridge (%s)", cart)
|
||||
|
||||
case KeywordScript:
|
||||
script, _ := tokens.get()
|
||||
script, _ := tokens.Get()
|
||||
err := dbg.RunScript(script, false)
|
||||
if err != nil {
|
||||
return false, err
|
||||
|
@ -474,7 +474,7 @@ func (dbg *Debugger) parseCommand(input string) (bool, error) {
|
|||
dbg.print(ui.CPUStep, dbg.disasm.Dump())
|
||||
|
||||
case KeywordSymbol:
|
||||
symbol, _ := tokens.get()
|
||||
symbol, _ := tokens.Get()
|
||||
address, err := dbg.disasm.Symtable.SearchLocation(symbol)
|
||||
if err != nil {
|
||||
switch err := err.(type) {
|
||||
|
@ -501,7 +501,7 @@ func (dbg *Debugger) parseCommand(input string) (bool, error) {
|
|||
}
|
||||
|
||||
case KeywordList:
|
||||
list, _ := tokens.get()
|
||||
list, _ := tokens.Get()
|
||||
switch strings.ToUpper(list) {
|
||||
case "BREAKS":
|
||||
dbg.breakpoints.list()
|
||||
|
@ -510,7 +510,7 @@ func (dbg *Debugger) parseCommand(input string) (bool, error) {
|
|||
}
|
||||
|
||||
case KeywordClear:
|
||||
clear, _ := tokens.get()
|
||||
clear, _ := tokens.Get()
|
||||
switch strings.ToUpper(clear) {
|
||||
case "BREAKS":
|
||||
dbg.breakpoints.clear()
|
||||
|
@ -521,10 +521,10 @@ func (dbg *Debugger) parseCommand(input string) (bool, error) {
|
|||
}
|
||||
|
||||
case KeywordOnHalt:
|
||||
if tokens.remaining() == 0 {
|
||||
if tokens.Remaining() == 0 {
|
||||
dbg.commandOnHalt = dbg.commandOnHaltStored
|
||||
} else {
|
||||
option, _ := tokens.peek()
|
||||
option, _ := tokens.Peek()
|
||||
if strings.ToUpper(option) == "OFF" {
|
||||
dbg.commandOnHalt = ""
|
||||
dbg.print(ui.Feedback, "no auto-command on halt")
|
||||
|
@ -532,7 +532,7 @@ func (dbg *Debugger) parseCommand(input string) (bool, error) {
|
|||
}
|
||||
|
||||
// use remaininder of command line to form the ONHALT command sequence
|
||||
dbg.commandOnHalt = tokens.remainder()
|
||||
dbg.commandOnHalt = tokens.Remainder()
|
||||
|
||||
// we can't use semi-colons when specifying the sequence so allow use of
|
||||
// commas to act as an alternative
|
||||
|
@ -549,10 +549,10 @@ func (dbg *Debugger) parseCommand(input string) (bool, error) {
|
|||
return false, err
|
||||
|
||||
case KeywordOnStep:
|
||||
if tokens.remaining() == 0 {
|
||||
if tokens.Remaining() == 0 {
|
||||
dbg.commandOnStep = dbg.commandOnStepStored
|
||||
} else {
|
||||
option, _ := tokens.peek()
|
||||
option, _ := tokens.Peek()
|
||||
if strings.ToUpper(option) == "OFF" {
|
||||
dbg.commandOnStep = ""
|
||||
dbg.print(ui.Feedback, "no auto-command on step")
|
||||
|
@ -560,7 +560,7 @@ func (dbg *Debugger) parseCommand(input string) (bool, error) {
|
|||
}
|
||||
|
||||
// use remaininder of command line to form the ONSTEP command sequence
|
||||
dbg.commandOnStep = tokens.remainder()
|
||||
dbg.commandOnStep = tokens.Remainder()
|
||||
|
||||
// we can't use semi-colons when specifying the sequence so allow use of
|
||||
// commas to act as an alternative
|
||||
|
@ -615,7 +615,7 @@ func (dbg *Debugger) parseCommand(input string) (bool, error) {
|
|||
stepNext = true
|
||||
|
||||
case KeywordStepMode:
|
||||
mode, _ := tokens.get()
|
||||
mode, _ := tokens.Get()
|
||||
mode = strings.ToUpper(mode)
|
||||
switch mode {
|
||||
case "CPU":
|
||||
|
@ -654,7 +654,7 @@ func (dbg *Debugger) parseCommand(input string) (bool, error) {
|
|||
dbg.printMachineInfo(dbg.vcs.MC)
|
||||
|
||||
case KeywordPeek:
|
||||
a, present := tokens.get()
|
||||
a, present := tokens.Get()
|
||||
for present {
|
||||
var addr interface{}
|
||||
var msg string
|
||||
|
@ -687,7 +687,7 @@ func (dbg *Debugger) parseCommand(input string) (bool, error) {
|
|||
}
|
||||
dbg.print(ui.MachineInfo, msg)
|
||||
|
||||
a, present = tokens.get()
|
||||
a, present = tokens.Get()
|
||||
}
|
||||
|
||||
case KeywordRAM:
|
||||
|
@ -723,7 +723,7 @@ func (dbg *Debugger) parseCommand(input string) (bool, error) {
|
|||
|
||||
case KeywordDisplay:
|
||||
visibility := true
|
||||
action, present := tokens.get()
|
||||
action, present := tokens.Get()
|
||||
if present {
|
||||
switch strings.ToUpper(action) {
|
||||
case "OFF":
|
||||
|
|
52
debugger/input/compiled.go
Normal file
52
debugger/input/compiled.go
Normal file
|
@ -0,0 +1,52 @@
|
|||
package input
|
||||
|
||||
// Commands is the root of the argument "tree"
|
||||
type Commands map[string]commandArgList
|
||||
|
||||
// argType defines the expected argument type
|
||||
type argType int
|
||||
|
||||
// the possible values for argType
|
||||
const (
|
||||
argKeyword argType = iota
|
||||
argFile
|
||||
argValue
|
||||
argString
|
||||
argIndeterminate
|
||||
)
|
||||
|
||||
// commandArg specifies the type and properties of an individual argument
|
||||
type commandArg struct {
|
||||
typ argType
|
||||
required bool
|
||||
values interface{}
|
||||
}
|
||||
|
||||
// commandArgList is the list of commandArgList for each command
|
||||
type commandArgList []commandArg
|
||||
|
||||
// maximumLen returns the maximum number of arguments allowed for a given
|
||||
// command
|
||||
func (a commandArgList) maximumLen() int {
|
||||
if len(a) == 0 {
|
||||
return 0
|
||||
}
|
||||
if a[len(a)-1].typ == argIndeterminate {
|
||||
// return the maximum value allowed for an integer
|
||||
return int(^uint(0) >> 1)
|
||||
}
|
||||
return len(a)
|
||||
}
|
||||
|
||||
// requiredLen returns the number of arguments required for a given command.
|
||||
// in other words, the command may allow more but it must have at least the
|
||||
// returned numnber.
|
||||
func (a commandArgList) requiredLen() (m int) {
|
||||
for i := 0; i < len(a); i++ {
|
||||
if !a[i].required {
|
||||
return
|
||||
}
|
||||
m++
|
||||
}
|
||||
return
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package parser
|
||||
package input
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
@ -9,7 +9,7 @@ const cycleDuration = 500 * time.Millisecond
|
|||
|
||||
// TabCompletion keeps track of the most recent tab completion attempt
|
||||
type TabCompletion struct {
|
||||
baseOptions Commands
|
||||
commands Commands
|
||||
|
||||
options []string
|
||||
lastOption int
|
||||
|
@ -22,19 +22,18 @@ type TabCompletion struct {
|
|||
lastCompletionTime time.Time
|
||||
}
|
||||
|
||||
// NewTabCompletion is the preferred method of initialisation for TabCompletion
|
||||
func NewTabCompletion(baseOptions Commands) *TabCompletion {
|
||||
// NewTabCompletion initialises a new TabCompletion instance
|
||||
func NewTabCompletion(commands Commands) *TabCompletion {
|
||||
tc := new(TabCompletion)
|
||||
tc.baseOptions = baseOptions
|
||||
tc.options = make([]string, 0, len(tc.baseOptions))
|
||||
tc.commands = commands
|
||||
tc.options = make([]string, 0, len(tc.commands))
|
||||
return tc
|
||||
}
|
||||
|
||||
// GuessWord transforms the input such that the last word in the input is
|
||||
// expanded to meet the closest match in the list of allowed strings.
|
||||
func (tc *TabCompletion) GuessWord(input string) string {
|
||||
// split input into words
|
||||
p := strings.Fields(input)
|
||||
p := tokeniseInput(input)
|
||||
if len(p) == 0 {
|
||||
return input
|
||||
}
|
||||
|
@ -68,33 +67,33 @@ func (tc *TabCompletion) GuessWord(input string) string {
|
|||
tc.lastOption = 0
|
||||
|
||||
// get args for command
|
||||
var arg Arg
|
||||
var arg commandArg
|
||||
|
||||
argList, ok := tc.baseOptions[strings.ToUpper(p[0])]
|
||||
argList, ok := tc.commands[strings.ToUpper(p[0])]
|
||||
if ok && len(input) > len(p[0]) {
|
||||
if len(argList) == 0 {
|
||||
return input
|
||||
}
|
||||
arg = argList[len(p)-2]
|
||||
} else {
|
||||
arg.Typ = ArgKeyword
|
||||
arg.Vals = &tc.baseOptions
|
||||
arg.typ = argKeyword
|
||||
arg.values = &tc.commands
|
||||
}
|
||||
|
||||
switch arg.Typ {
|
||||
case ArgKeyword:
|
||||
switch arg.typ {
|
||||
case argKeyword:
|
||||
// trigger is the word we're trying to complete on
|
||||
trigger := strings.ToUpper(p[len(p)-1])
|
||||
p = p[:len(p)-1]
|
||||
|
||||
switch kw := arg.Vals.(type) {
|
||||
switch kw := arg.values.(type) {
|
||||
case *Commands:
|
||||
for k := range *kw {
|
||||
if len(trigger) <= len(k) && trigger == k[:len(trigger)] {
|
||||
tc.options = append(tc.options, k)
|
||||
}
|
||||
}
|
||||
case Keywords:
|
||||
case []string:
|
||||
for _, k := range kw {
|
||||
if len(trigger) <= len(k) && trigger == k[:len(trigger)] {
|
||||
tc.options = append(tc.options, k)
|
||||
|
@ -104,7 +103,7 @@ func (tc *TabCompletion) GuessWord(input string) string {
|
|||
tc.options = append(tc.options, "unhandled argument type")
|
||||
}
|
||||
|
||||
case ArgFile:
|
||||
case argFile:
|
||||
// TODO: filename completion
|
||||
tc.options = append(tc.options, "<TODO: file-completion>")
|
||||
}
|
75
debugger/input/template.go
Normal file
75
debugger/input/template.go
Normal file
|
@ -0,0 +1,75 @@
|
|||
package input
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// CommandTemplate is the root of the argument "tree"
|
||||
type CommandTemplate map[string]string
|
||||
|
||||
// CompileCommandTemplate creates a new instance of Commands from an instance
|
||||
// of CommandTemplate. if no help is command is required, use the empty-string
|
||||
// to for the helpKeyword argument
|
||||
func CompileCommandTemplate(template CommandTemplate, helpKeyword string) (Commands, error) {
|
||||
commands := Commands{}
|
||||
for k, v := range template {
|
||||
commands[k] = commandArgList{}
|
||||
|
||||
placeholder := false
|
||||
|
||||
for i := 0; i < len(v); i++ {
|
||||
switch v[i] {
|
||||
case '%':
|
||||
placeholder = true
|
||||
case '[':
|
||||
// find end of option list
|
||||
j := strings.Index(v[i:], "]")
|
||||
if j == -1 {
|
||||
return commands, fmt.Errorf("unclosed option list (%s)", k)
|
||||
}
|
||||
|
||||
options := strings.Split(v[i+1:j], "|")
|
||||
if len(options) == 1 {
|
||||
// note: Split() returns a slice of the input string, if
|
||||
// the seperator ("|") cannot be found. the length of an
|
||||
// empty option list is therefore 1.
|
||||
return commands, fmt.Errorf("empty option list (%s)", k)
|
||||
}
|
||||
|
||||
// decide whether the option is required
|
||||
req := true
|
||||
for m := 0; m < len(options); m++ {
|
||||
if options[m] == "" {
|
||||
req = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// add a new argument for current keyword with the options
|
||||
// we've found
|
||||
commands[k] = append(commands[k], commandArg{typ: argKeyword, required: req, values: options})
|
||||
|
||||
default:
|
||||
if placeholder {
|
||||
switch v[i] {
|
||||
case 'F':
|
||||
commands[k] = append(commands[k], commandArg{typ: argFile, required: true})
|
||||
case 'S':
|
||||
commands[k] = append(commands[k], commandArg{typ: argString, required: true})
|
||||
case 'V':
|
||||
commands[k] = append(commands[k], commandArg{typ: argValue, required: true})
|
||||
case '*':
|
||||
commands[k] = append(commands[k], commandArg{typ: argIndeterminate, required: true})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if helpKeyword != "" {
|
||||
commands[helpKeyword] = commandArgList{commandArg{typ: argKeyword, required: false, values: &commands}}
|
||||
}
|
||||
|
||||
return commands, nil
|
||||
}
|
90
debugger/input/tokeniser.go
Normal file
90
debugger/input/tokeniser.go
Normal file
|
@ -0,0 +1,90 @@
|
|||
package input
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Tokens represents tokenised input. This can be used to walk through the
|
||||
// input string (using get()) for eas(ier) parsing
|
||||
type Tokens struct {
|
||||
tokens []string
|
||||
curr int
|
||||
}
|
||||
|
||||
// Reset begins the token traversal process from the beginning
|
||||
func (tk *Tokens) Reset() {
|
||||
tk.curr = 0
|
||||
}
|
||||
|
||||
// Remainder returns the remaining tokens as a string
|
||||
func (tk Tokens) Remainder() string {
|
||||
return strings.Join(tk.tokens[tk.curr:], " ")
|
||||
}
|
||||
|
||||
// Remaining returns the count of reminaing tokens in the token list
|
||||
func (tk Tokens) Remaining() int {
|
||||
return len(tk.tokens) - tk.curr
|
||||
}
|
||||
|
||||
// Total returns the total count of tokens
|
||||
func (tk Tokens) Total() int {
|
||||
return len(tk.tokens)
|
||||
}
|
||||
|
||||
// Get returns the next token in the list, and a success boolean - if the end
|
||||
// of the token list has been reached, the function returns false instead of
|
||||
// true.
|
||||
func (tk *Tokens) Get() (string, bool) {
|
||||
if tk.curr >= len(tk.tokens) {
|
||||
return "", false
|
||||
}
|
||||
tk.curr++
|
||||
return tk.tokens[tk.curr-1], true
|
||||
}
|
||||
|
||||
// Unget walks backwards in the token list.
|
||||
func (tk *Tokens) Unget() {
|
||||
if tk.curr > 0 {
|
||||
tk.curr--
|
||||
}
|
||||
}
|
||||
|
||||
// Peek returns the next token in the list (without advancing the list), and a
|
||||
// success boolean - if the end of the token list has been reached, the
|
||||
// function returns false instead of true.
|
||||
func (tk Tokens) Peek() (string, bool) {
|
||||
if tk.curr >= len(tk.tokens) {
|
||||
return "", false
|
||||
}
|
||||
return tk.tokens[tk.curr], true
|
||||
}
|
||||
|
||||
// TokeniseInput creates and returns a new Tokens instance
|
||||
func TokeniseInput(input string) *Tokens {
|
||||
tk := new(Tokens)
|
||||
|
||||
// remove leading/trailing space
|
||||
input = strings.TrimSpace(input)
|
||||
|
||||
// divide user input into tokens
|
||||
tk.tokens = tokeniseInput(input)
|
||||
|
||||
// normalise variations in syntax
|
||||
for i := 0; i < len(tk.tokens); i++ {
|
||||
// normalise hex notation
|
||||
if tk.tokens[i][0] == '$' {
|
||||
tk.tokens[i] = fmt.Sprintf("0x%s", tk.tokens[i][1:])
|
||||
}
|
||||
}
|
||||
|
||||
return tk
|
||||
}
|
||||
|
||||
// tokeniseInput is the "raw" tokenising function (without normalisation or
|
||||
// wrapping everything up in a Tokens instance). used by the fancier
|
||||
// TokeniseInput and anywhere else where we need to divide input into tokens
|
||||
// (eg. TabCompletion.GuessWord())
|
||||
func tokeniseInput(input string) []string {
|
||||
return strings.Fields(input)
|
||||
}
|
53
debugger/input/validation.go
Normal file
53
debugger/input/validation.go
Normal file
|
@ -0,0 +1,53 @@
|
|||
package input
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gopher2600/errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ValidateInput checks whether input is correct according to the
|
||||
// command definitions
|
||||
func (options Commands) ValidateInput(newInput *Tokens) error {
|
||||
var args commandArgList
|
||||
|
||||
tokens := newInput.tokens
|
||||
|
||||
// if tokens is empty then return
|
||||
if len(tokens) == 0 {
|
||||
return errors.NewGopherError(errors.InputEmpty)
|
||||
}
|
||||
|
||||
tokens[0] = strings.ToUpper(tokens[0])
|
||||
|
||||
// basic check for whether command is recognised
|
||||
var ok bool
|
||||
if args, ok = options[tokens[0]]; !ok {
|
||||
return errors.NewGopherError(errors.InputInvalidCommand, fmt.Sprintf("%s is not a debugging command", tokens[0]))
|
||||
}
|
||||
|
||||
// too *many* arguments have been supplied
|
||||
if len(tokens)-1 > args.maximumLen() {
|
||||
return errors.NewGopherError(errors.InputTooManyArgs, fmt.Sprintf("too many arguments for %s", tokens[0]))
|
||||
}
|
||||
|
||||
// too *few* arguments have been supplied
|
||||
if len(tokens)-1 < args.requiredLen() {
|
||||
switch args[len(tokens)-1].typ {
|
||||
case argKeyword:
|
||||
return errors.NewGopherError(errors.InputTooFewArgs, fmt.Sprintf("keyword required for %s", tokens[0]))
|
||||
case argFile:
|
||||
return errors.NewGopherError(errors.InputTooFewArgs, fmt.Sprintf("filename required for %s", tokens[0]))
|
||||
case argValue:
|
||||
return errors.NewGopherError(errors.InputTooFewArgs, fmt.Sprintf("numeric argument required for %s", tokens[0]))
|
||||
case argString:
|
||||
return errors.NewGopherError(errors.InputTooFewArgs, fmt.Sprintf("string argument required for %s", tokens[0]))
|
||||
default:
|
||||
// TODO: argument types can be OR'd together. breakdown these types
|
||||
// to give more useful information
|
||||
return errors.NewGopherError(errors.InputTooFewArgs, fmt.Sprintf("too few arguments for %s", tokens[0]))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -11,11 +11,11 @@ type machineInfo interface {
|
|||
MachineInfoTerse() string
|
||||
}
|
||||
|
||||
func (dbg Debugger) printMachineInfo(mi machineInfo) {
|
||||
func (dbg *Debugger) printMachineInfo(mi machineInfo) {
|
||||
dbg.print(ui.MachineInfo, "%s", dbg.sprintMachineInfo(mi))
|
||||
}
|
||||
|
||||
func (dbg Debugger) sprintMachineInfo(mi machineInfo) string {
|
||||
func (dbg *Debugger) sprintMachineInfo(mi machineInfo) string {
|
||||
if dbg.machineInfoVerbose {
|
||||
return mi.MachineInfo()
|
||||
}
|
||||
|
|
|
@ -1,109 +0,0 @@
|
|||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gopher2600/errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ArgType defines the expected argument type
|
||||
type ArgType int
|
||||
|
||||
// the possible values for ArgType
|
||||
const (
|
||||
ArgKeyword ArgType = 1 << iota
|
||||
ArgFile
|
||||
ArgTarget
|
||||
ArgValue
|
||||
ArgString
|
||||
ArgAddress
|
||||
ArgIndeterminate
|
||||
)
|
||||
|
||||
// Commands is the root of the argument "tree"
|
||||
type Commands map[string]CommandArgs
|
||||
|
||||
// Arg specifies the type and properties of an individual argument
|
||||
type Arg struct {
|
||||
Typ ArgType
|
||||
Req bool
|
||||
Vals AllowedVals
|
||||
}
|
||||
|
||||
// CommandArgs is the list of Args for each command
|
||||
type CommandArgs []Arg
|
||||
|
||||
// AllowedVals can take a number of types, useful for tab completion
|
||||
type AllowedVals interface{}
|
||||
|
||||
// Keywords can be used for specifying a list of keywords
|
||||
// -- satisfies the AllowedVals interface
|
||||
type Keywords []string
|
||||
|
||||
func (a CommandArgs) maxLen() int {
|
||||
if len(a) == 0 {
|
||||
return 0
|
||||
}
|
||||
if a[len(a)-1].Typ == ArgIndeterminate {
|
||||
return int(^uint(0) >> 1)
|
||||
}
|
||||
return len(a)
|
||||
}
|
||||
|
||||
func (a CommandArgs) minLen() (m int) {
|
||||
for i := 0; i < len(a); i++ {
|
||||
if !a[i].Req {
|
||||
return
|
||||
}
|
||||
m++
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ValidateInput checks whether input is correct according to the
|
||||
// command definitions
|
||||
func (options Commands) ValidateInput(input []string) error {
|
||||
var args CommandArgs
|
||||
|
||||
// if input is empty then return
|
||||
if len(input) == 0 {
|
||||
return errors.NewGopherError(errors.InputEmpty)
|
||||
}
|
||||
|
||||
input[0] = strings.ToUpper(input[0])
|
||||
|
||||
// basic check for whether command is recognised
|
||||
var ok bool
|
||||
if args, ok = options[input[0]]; !ok {
|
||||
return errors.NewGopherError(errors.InputInvalidCommand, fmt.Sprintf("%s is not a debugging command", input[0]))
|
||||
}
|
||||
|
||||
// too *many* arguments have been supplied
|
||||
if len(input)-1 > args.maxLen() {
|
||||
return errors.NewGopherError(errors.InputTooManyArgs, fmt.Sprintf("too many arguments for %s", input[0]))
|
||||
}
|
||||
|
||||
// too *few* arguments have been supplied
|
||||
if len(input)-1 < args.minLen() {
|
||||
switch args[len(input)-1].Typ {
|
||||
case ArgKeyword:
|
||||
return errors.NewGopherError(errors.InputTooFewArgs, fmt.Sprintf("keyword required for %s", input[0]))
|
||||
case ArgFile:
|
||||
return errors.NewGopherError(errors.InputTooFewArgs, fmt.Sprintf("filename required for %s", input[0]))
|
||||
case ArgAddress:
|
||||
return errors.NewGopherError(errors.InputTooFewArgs, fmt.Sprintf("address required for %s", input[0]))
|
||||
case ArgTarget:
|
||||
return errors.NewGopherError(errors.InputTooFewArgs, fmt.Sprintf("emulation target required for %s", input[0]))
|
||||
case ArgValue:
|
||||
return errors.NewGopherError(errors.InputTooFewArgs, fmt.Sprintf("numeric argument required for %s", input[0]))
|
||||
case ArgString:
|
||||
return errors.NewGopherError(errors.InputTooFewArgs, fmt.Sprintf("string argument required for %s", input[0]))
|
||||
default:
|
||||
// TODO: argument types can be OR'd together. breakdown these types
|
||||
// to give more useful information
|
||||
return errors.NewGopherError(errors.InputTooFewArgs, fmt.Sprintf("too few arguments for %s", input[0]))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -9,7 +9,7 @@ import (
|
|||
// 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 ui.PrintProfile, s string, a ...interface{}) {
|
||||
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/input"
|
||||
"gopher2600/errors"
|
||||
"gopher2600/television"
|
||||
"strings"
|
||||
|
@ -41,11 +42,11 @@ func (trg genericTarget) Value() interface{} {
|
|||
}
|
||||
|
||||
// parseTarget uses a keyword to decide which part of the vcs to target
|
||||
func parseTarget(dbg *Debugger, tokens *tokens) (target, error) {
|
||||
func parseTarget(dbg *Debugger, tokens *input.Tokens) (target, error) {
|
||||
var trg target
|
||||
var err error
|
||||
|
||||
keyword, present := tokens.get()
|
||||
keyword, present := tokens.Get()
|
||||
if present {
|
||||
keyword = strings.ToUpper(keyword)
|
||||
switch keyword {
|
||||
|
@ -70,7 +71,7 @@ func parseTarget(dbg *Debugger, tokens *tokens) (target, error) {
|
|||
// help investigate a bug in the emulation. I don't think it's much use
|
||||
// but it was an instructive exercise and may come in useful one day.
|
||||
case "INSTRUCTION", "INS":
|
||||
subkey, present := tokens.get()
|
||||
subkey, present := tokens.Get()
|
||||
if present {
|
||||
subkey = strings.ToUpper(subkey)
|
||||
switch subkey {
|
||||
|
|
|
@ -1,68 +0,0 @@
|
|||
package debugger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type tokens struct {
|
||||
tokens []string
|
||||
curr int
|
||||
}
|
||||
|
||||
func (tk *tokens) reset() {
|
||||
tk.curr = 0
|
||||
}
|
||||
|
||||
func (tk tokens) remainder() string {
|
||||
return strings.Join(tk.tokens[tk.curr:], " ")
|
||||
}
|
||||
|
||||
func (tk tokens) remaining() int {
|
||||
return len(tk.tokens) - tk.curr
|
||||
}
|
||||
|
||||
func (tk tokens) num() int {
|
||||
return len(tk.tokens)
|
||||
}
|
||||
|
||||
func (tk *tokens) get() (string, bool) {
|
||||
if tk.curr >= len(tk.tokens) {
|
||||
return "", false
|
||||
}
|
||||
tk.curr++
|
||||
return tk.tokens[tk.curr-1], true
|
||||
}
|
||||
|
||||
func (tk *tokens) unget() {
|
||||
if tk.curr > 0 {
|
||||
tk.curr--
|
||||
}
|
||||
}
|
||||
|
||||
func (tk tokens) peek() (string, bool) {
|
||||
if tk.curr >= len(tk.tokens) {
|
||||
return "", false
|
||||
}
|
||||
return tk.tokens[tk.curr], true
|
||||
}
|
||||
|
||||
func tokeniseInput(input string) *tokens {
|
||||
tk := new(tokens)
|
||||
|
||||
// remove leading/trailing space
|
||||
input = strings.TrimSpace(input)
|
||||
|
||||
// divide user input into tokens
|
||||
tk.tokens = strings.Fields(input)
|
||||
|
||||
// normalise variations in syntax
|
||||
for i := 0; i < len(tk.tokens); i++ {
|
||||
// normalise hex notation
|
||||
if tk.tokens[i][0] == '$' {
|
||||
tk.tokens[i] = fmt.Sprintf("0x%s", tk.tokens[i][1:])
|
||||
}
|
||||
}
|
||||
|
||||
return tk
|
||||
}
|
|
@ -6,6 +6,7 @@ package debugger
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"gopher2600/debugger/input"
|
||||
"gopher2600/debugger/ui"
|
||||
)
|
||||
|
||||
|
@ -64,8 +65,8 @@ func (tr traps) list() {
|
|||
}
|
||||
}
|
||||
|
||||
func (tr *traps) parseTrap(tokens *tokens) error {
|
||||
_, present := tokens.peek()
|
||||
func (tr *traps) parseTrap(tokens *input.Tokens) error {
|
||||
_, present := tokens.Peek()
|
||||
for present {
|
||||
tgt, err := parseTarget(tr.dbg, tokens)
|
||||
if err != nil {
|
||||
|
@ -85,7 +86,7 @@ func (tr *traps) parseTrap(tokens *tokens) error {
|
|||
tr.traps = append(tr.traps, trapper{target: tgt, origValue: tgt.Value()})
|
||||
}
|
||||
|
||||
_, present = tokens.peek()
|
||||
_, present = tokens.Peek()
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -2,7 +2,7 @@ package ui
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"gopher2600/debugger/parser"
|
||||
"gopher2600/debugger/input"
|
||||
"os"
|
||||
)
|
||||
|
||||
|
@ -20,7 +20,7 @@ func (pt *PlainTerminal) CleanUp() {
|
|||
}
|
||||
|
||||
// RegisterTabCompleter adds an implementation of TabCompleter to the terminal
|
||||
func (pt *PlainTerminal) RegisterTabCompleter(tc *parser.TabCompletion) {
|
||||
func (pt *PlainTerminal) RegisterTabCompleter(tc *input.TabCompletion) {
|
||||
}
|
||||
|
||||
// UserPrint is the plain terminal print routine
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
package ui
|
||||
|
||||
import "gopher2600/debugger/parser"
|
||||
import "gopher2600/debugger/input"
|
||||
|
||||
// UserInterface defines the user interface operations required by the debugger
|
||||
type UserInterface interface {
|
||||
Initialise() error
|
||||
CleanUp()
|
||||
RegisterTabCompleter(*parser.TabCompletion)
|
||||
RegisterTabCompleter(*input.TabCompletion)
|
||||
UserPrint(PrintProfile, string, ...interface{})
|
||||
UserRead([]byte, string) (int, error)
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue