Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

dcpu-emu: Removes emulator keybindings from opengl window.

Prepares to implement command-line interface. This will make the console
output interactive by allowing emulator control commands to be supplied
through stdin. This will give us much better control of the emulator,
as we can add commands with arguments. These are needed to set/unset
things like breakpoints and memory watchers for specific address
offsets.
  • Loading branch information...
commit 6e6b5dd474c9edb26d064582b6069f7ac18814d8 1 parent d9de727
@jteeuwen authored
View
2  ENV.md
@@ -1,6 +1,6 @@
## Environment variables
-Bfore using any of the DCPU tools in this project, we must set the `$DCPUROOT`
+Before using any of the DCPU tools in this project, we must set the `$DCPUROOT`
environment variable to a path where all our DCPU code will live. For example:
export DCPUROOT=$HOME/dcpu
View
1  README.md
@@ -104,7 +104,6 @@ The emulator depends on the following packages:
go get github.com/go-gl/gl
go get github.com/go-gl/glfw
go get github.com/go-gl/glh
- go get github.com/jteeuwen/keyboard/glfw
### License
View
183 dcpu-emu/command.go
@@ -0,0 +1,183 @@
+// This file is subject to a 1-clause BSD license.
+// Its contents can be found in the enclosed LICENSE file.
+
+package main
+
+import (
+ "regexp"
+ "strconv"
+ "strings"
+)
+
+var (
+ regAny = regexp.MustCompile(`^.+$`)
+ regDecimal = regexp.MustCompile(`^[+-]?[0-9]+$`)
+ regOctal = regexp.MustCompile(`^[+-]?0[0-7]+$`)
+ regBinary = regexp.MustCompile(`^[+-]?0[bB][0-1]+$`)
+ regHexadecimal = regexp.MustCompile(`^[+-]?0[xX][0-9a-fA-F]+$`)
+)
+
+type CommandHandler func(*Command)
+
+// A command represents a known command we can accept through stdin.
+type Command struct {
+ Name string // Command name.
+ Description string // Command description.
+ Params []*Param // List of parameters for this command.
+ Handler CommandHandler // Command execution handler.
+}
+
+// Add adds the given parameter.
+func (c *Command) Add(name, description string, patterns ...*regexp.Regexp) {
+ c.Params = append(c.Params, &Param{
+ Name: name,
+ Description: description,
+ Patterns: patterns,
+ })
+}
+
+// Param represents a command parameter.
+type Param struct {
+ Name string // Name of the parameter.
+ Description string // Parameter description.
+ Value string // Current value of the parameter.
+ Patterns []*regexp.Regexp // List of validation patterns.
+}
+
+// Valid returns true if the parameter value matches any of the patterns.
+func (p *Param) Valid() bool {
+ for _, pat := range p.Patterns {
+ if pat.MatchString(p.Value) {
+ return true
+ }
+ }
+
+ return false
+}
+
+func (p *Param) B() (bool, error) {
+ v, err := strconv.ParseBool(p.Value)
+
+ if err != nil {
+ return false, err
+ }
+
+ return v, nil
+}
+
+func (p *Param) I8() (int8, error) {
+ value, base := p.number()
+ v, err := strconv.ParseInt(value, base, 8)
+
+ if err != nil {
+ return 0, err
+ }
+
+ return int8(v), nil
+}
+
+func (p *Param) U8() (uint8, error) {
+ value, base := p.number()
+ v, err := strconv.ParseUint(value, base, 8)
+
+ if err != nil {
+ return 0, err
+ }
+
+ return uint8(v), nil
+}
+
+func (p *Param) I16() (int16, error) {
+ value, base := p.number()
+ v, err := strconv.ParseInt(value, base, 16)
+
+ if err != nil {
+ return 0, err
+ }
+
+ return int16(v), nil
+}
+
+func (p *Param) U16() (uint16, error) {
+ value, base := p.number()
+ v, err := strconv.ParseUint(value, base, 16)
+
+ if err != nil {
+ return 0, err
+ }
+
+ return uint16(v), nil
+}
+
+func (p *Param) I32() (int32, error) {
+ value, base := p.number()
+ v, err := strconv.ParseInt(value, base, 32)
+
+ if err != nil {
+ return 0, err
+ }
+
+ return int32(v), nil
+}
+
+func (p *Param) U32() (uint32, error) {
+ value, base := p.number()
+ v, err := strconv.ParseUint(value, base, 32)
+
+ if err != nil {
+ return 0, err
+ }
+
+ return uint32(v), nil
+}
+
+func (p *Param) I64() (int64, error) {
+ value, base := p.number()
+ v, err := strconv.ParseInt(value, base, 64)
+
+ if err != nil {
+ return 0, err
+ }
+
+ return v, nil
+}
+
+func (p *Param) U64() (uint64, error) {
+ value, base := p.number()
+ v, err := strconv.ParseUint(value, base, 64)
+
+ if err != nil {
+ return 0, err
+ }
+
+ return v, nil
+}
+
+// number returns a parsable number string, along with its base.
+func (p *Param) number() (string, int) {
+ var sign string
+ value := strings.ToLower(p.Value)
+
+ if len(value) == 0 {
+ return "", 0
+ }
+
+ if value[0] == '-' || value[0] == '+' {
+ sign = value[:1]
+ value = value[1:]
+ }
+
+ if strings.HasPrefix(value, "0b") {
+ return sign + value[2:], 2
+ }
+
+ if strings.HasPrefix(value, "0x") {
+ return sign + value[2:], 16
+ }
+
+ if strings.HasPrefix(value, "0") {
+ return sign + value, 8
+ }
+
+ return sign + value, 10
+}
View
252 dcpu-emu/emu.go
@@ -4,28 +4,21 @@
package main
import (
- "fmt"
"github.com/go-gl/gl"
"github.com/go-gl/glfw"
"github.com/jteeuwen/dcpu/cpu"
- kb "github.com/jteeuwen/keyboard/glfw"
- "io"
- "log"
- "os"
"sync/atomic"
- "time"
)
// Emu represents a single emulator instance.
type Emu struct {
source SourceCache
fc FrameCounter
+ input *InputHandler
disp Display
cpu *cpu.CPU
font *Font
cycles uint64
- debug bool
- trace bool
vsync bool
}
@@ -33,10 +26,10 @@ type Emu struct {
func NewEmu(display string, c *cpu.CPU) *Emu {
e := new(Emu)
e.cpu = c
- e.debug = false
- e.trace = true
e.vsync = true
e.source = make(SourceCache)
+ e.input = NewInputHandler()
+ e.registerCommands()
switch display {
case "lem1802":
@@ -55,25 +48,6 @@ func NewEmu(display string, c *cpu.CPU) *Emu {
if op == cpu.SUB && a == 0x1c && b == 0x22 {
e.cpu.Stop()
}
-
- if !e.trace {
- return
- }
-
- line, ok := e.source.Line(pc)
- if ok {
- fmt.Fprintf(
- os.Stdout,
- "%04x: %04x %04x %04x | %04x %04x %04x %04x %04x %04x %04x %04x | %04x %04x %04x | %s\n",
- pc, op, a, b, s.A, s.B, s.C, s.X, s.Y, s.Z, s.I, s.J, s.SP, s.EX, s.IA, line,
- )
- } else {
- fmt.Fprintf(
- os.Stdout,
- "%04x: %04x %04x %04x | %04x %04x %04x %04x %04x %04x %04x %04x | %04x %04x %04x\n",
- pc, op, a, b, s.A, s.B, s.C, s.X, s.Y, s.Z, s.I, s.J, s.SP, s.EX, s.IA,
- )
- }
}
return e
@@ -87,81 +61,11 @@ func (e *Emu) Render() {
gl.LoadIdentity()
e.disp.Render()
- e.renderDebug()
glfw.SwapBuffers()
}
}
-// renderDebug renders debug information.
-func (e *Emu) renderDebug() {
- if !e.debug {
- return
- }
-
- fw, fh := e.font.Advance()
- width, height := glfw.WindowSize()
-
- gl.PushAttrib(gl.TRANSFORM_BIT | gl.ENABLE_BIT | gl.CURRENT_BIT)
- gl.Disable(gl.DEPTH_TEST)
- gl.Disable(gl.TEXTURE_2D)
- gl.Enable(gl.COLOR_MATERIAL)
- gl.Enable(gl.BLEND)
- gl.LoadIdentity()
-
- gl.Color4f(0, 0, 0, 0.5)
- gl.Recti(0, 0, width, height)
-
- gl.Color4f(1, 1, 1, 1)
- e.font.Printf(fw, 0*fh, "FPS: %d (vsync: %v)", e.fc.Fps(), e.vsync)
- e.font.Printf(fw, 2*fh, "CPU Clock Speed: %s", prettySpeed(e.cpu.ClockSpeed))
- e.font.Printf(fw, 3*fh, "CPU Cycles: %d", atomic.LoadUint64(&e.cycles))
- e.font.Printf(fw, 4*fh, "CPU Registers:")
-
- s := e.cpu.Store
- str := fmt.Sprintf(
- "%04x %04x %04x %04x %04x %04x %04x %04x %04x %04x %04x %04x",
- s.PC, s.A, s.B, s.C, s.X, s.Y, s.Z, s.I, s.J, s.SP, s.EX, s.IA,
- )
-
- e.font.Printf(fw*2, 6*fh, " PC A B C X Y Z I J SP EX IA")
- e.font.Printf(fw*2, 7*fh, str)
- gl.PopAttrib()
-}
-
-func prettySpeed(v time.Duration) string {
- if v == 0 {
- return "0 Hz"
- }
-
- var unit int
- var str string
-
- v = time.Second / v
-
- for v >= 1000 {
- v /= 1000
- unit++
- }
-
- switch unit {
- case 0:
- str = "Hz"
- case 1:
- str = "KHz"
- case 2:
- str = "MHz"
- case 3:
- str = "GHz"
- case 4:
- str = "THz"
- case 5:
- str = "PHz"
- }
-
- return fmt.Sprintf("%d %s", v, str)
-}
-
func (e *Emu) Init() (err error) {
err = initGL(e.cpu, e.disp.Width(), e.disp.Height())
if err != nil {
@@ -185,94 +89,7 @@ func (e *Emu) Init() (err error) {
e.font = NewFont()
e.font.Load(DefaultFont(), 2)
-
- kb := kb.New()
-
- // Exit emulator.
- kb.Bind(func() {
- glfw.CloseWindow()
- }, "escape")
-
- // Toggle debug display.
- kb.Bind(func() {
- e.debug = !e.debug
- }, "alt+F1")
-
- // Toggle vsync.
- kb.Bind(func() {
- e.vsync = !e.vsync
-
- if e.vsync {
- glfw.SetSwapInterval(1)
- } else {
- glfw.SetSwapInterval(0)
- }
- }, "alt+F2")
-
- // Toggle runtime tracing of CPU instructions to stdout.
- kb.Bind(func() {
- e.trace = !e.trace
- }, "alt+F3")
-
- // Reset the CPU to its starting state.
- kb.Bind(func() {
- e.cycles = 0
- e.cpu.Stop()
- e.cpu.Store.Clear()
- e.source.Clear()
-
- mem := e.cpu.Store.Mem
-
- for i := range mem {
- mem[i] = 0
- }
-
- loadBinary(e.cpu)
- }, "alt+F5")
-
- // Start CPU simulation.
- kb.Bind(func() {
- go func() {
- err := e.cpu.Run(e.cpu.Store.PC)
- if err != nil && err != io.EOF {
- log.Printf("%s", err)
- }
- }()
- }, "alt+q")
-
- // Stop CPU simulation.
- kb.Bind(func() {
- e.cpu.Stop()
- }, "alt+w")
-
- // Perform a single CPU simulation step.
- kb.Bind(func() {
- err := e.cpu.Step()
- if err != nil && err != io.EOF {
- log.Printf("%s", err)
- }
- }, "alt+e")
-
- // Increase CPU clock speed.
- kb.Bind(func() {
- cs := e.cpu.ClockSpeed / 10
- if cs < 1 {
- cs = 1
- }
- e.cpu.ClockSpeed = cs
- }, "alt+=")
-
- // Decrease CPU clock speed.
- kb.Bind(func() {
- cs := e.cpu.ClockSpeed * 10
- if cs > 1e9 {
- cs = 1e9
- }
- e.cpu.ClockSpeed = cs
- }, "alt+-")
-
return nil
- return
}
func (e *Emu) Release() {
@@ -292,6 +109,69 @@ func (e *Emu) Release() {
e.disp = nil
}
+ if e.input != nil {
+ e.input.Release()
+ e.input = nil
+ }
+
e.cpu = nil
glfw.Terminate()
}
+
+// registerCommands registers all commands with the input handler.
+func (e *Emu) registerCommands() {
+ c := new(Command)
+ c.Name = "load"
+ c.Description = `Load the given package binary. This assembles and links the target if necessary.`
+ c.Add("package", "Import path for target package", regAny)
+ e.input.Add(c)
+
+ c = new(Command)
+ c.Name = "q"
+ c.Description = `Start the CPU simulation.`
+ e.input.Add(c)
+
+ c = new(Command)
+ c.Name = "w"
+ c.Description = `Stop the CPU simulation.`
+ e.input.Add(c)
+
+ c = new(Command)
+ c.Name = "e"
+ c.Description = `Perform a single CPU simulation step.`
+ e.input.Add(c)
+
+ c = new(Command)
+ c.Name = "b"
+ c.Description = `Toggle a breakpoint at the given instruction.`
+ c.Add("address", "Address offset at which to define the breakpoint.",
+ regBinary, regHexadecimal, regOctal, regDecimal)
+ e.input.Add(c)
+
+ c = new(Command)
+ c.Name = "l"
+ c.Description = `List all defined breakpoints.`
+ e.input.Add(c)
+
+ c = new(Command)
+ c.Name = "+"
+ c.Description = `Increase CPU clock speed by 10x.`
+ e.input.Add(c)
+
+ c = new(Command)
+ c.Name = "-"
+ c.Description = `Decrease CPU clock speed by 10x.`
+ e.input.Add(c)
+
+ c = new(Command)
+ c.Name = "trace"
+ c.Description = `Toggle runtime trace output for instructions as they are executed.`
+ e.input.Add(c)
+
+ c = new(Command)
+ c.Name = "m"
+ c.Description = `Toggle watcher for given memory location.`
+ c.Add("address", "Address offset for which to define a watcher.",
+ regBinary, regHexadecimal, regOctal, regDecimal)
+ e.input.Add(c)
+}
View
53 dcpu-emu/input.go
@@ -0,0 +1,53 @@
+// This file is subject to a 1-clause BSD license.
+// Its contents can be found in the enclosed LICENSE file.
+
+package main
+
+import (
+ "bufio"
+ "fmt"
+ "os"
+ "strings"
+)
+
+// InputHandler polls for input through stdin.
+// It parses input into known commands.
+type InputHandler struct {
+ C chan *Command // Channel yielding commands being entered.
+ commands []*Command // List of registered commands.
+}
+
+// NewInputHandler creates a new input handler and goes into
+// the polling loop.
+func NewInputHandler() *InputHandler {
+ i := new(InputHandler)
+ i.C = make(chan *Command)
+ go i.poll()
+ return i
+}
+
+// Release releases input handler resources.
+func (ih *InputHandler) Release() {
+ ih.commands = nil
+}
+
+// Add adds the given command.
+func (ih *InputHandler) Add(c *Command) {
+ ih.commands = append(ih.commands, c)
+}
+
+// poll reads input from stdin.
+func (ih *InputHandler) poll() {
+ r := bufio.NewReader(os.Stdin)
+
+ for {
+ line, err := r.ReadString('\n')
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Input error: %v\n", err)
+ return
+ }
+
+ line = strings.TrimSpace(line)
+ println(">>>", line)
+ }
+}
View
43 dcpu-emu/misc.go
@@ -0,0 +1,43 @@
+// This file is subject to a 1-clause BSD license.
+// Its contents can be found in the enclosed LICENSE file.
+
+package main
+
+import (
+ "fmt"
+ "time"
+)
+
+// prettySpeed returns a human-friendly version of the given clock speed.
+func prettySpeed(v time.Duration) string {
+ if v == 0 {
+ return "0 Hz"
+ }
+
+ var unit int
+ var str string
+
+ v = time.Second / v
+
+ for v >= 1000 {
+ v /= 1000
+ unit++
+ }
+
+ switch unit {
+ case 0:
+ str = "Hz"
+ case 1:
+ str = "KHz"
+ case 2:
+ str = "MHz"
+ case 3:
+ str = "GHz"
+ case 4:
+ str = "THz"
+ case 5:
+ str = "PHz"
+ }
+
+ return fmt.Sprintf("%d %s", v, str)
+}
Please sign in to comment.
Something went wrong with that request. Please try again.