Skip to content

Commit

Permalink
cmd/retro: implement raw IO, refactor error handling
Browse files Browse the repository at this point in the history
vm: refactor options, add error handling on Instance creation
  • Loading branch information
db47h committed Aug 3, 2016
1 parent d0c5e41 commit ed94b36
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 66 deletions.
56 changes: 36 additions & 20 deletions cmd/retro/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,49 +27,65 @@ import (
"github.com/pkg/errors"
)

func handleErr(err error) {
if err != nil {
switch errors.Cause(err) {
case io.EOF: // stdin or stdout closed
default:
func main() {
// check exit condition
var err error
defer func() {
if err != nil {
err = errors.Cause(err)
fmt.Fprintf(os.Stderr, "%+v\n", err)
os.Exit(1)
}
}
}
}()

func main() {
var fileName = flag.String("image", "retroImage", "Use `filename` as the image to load")
var withFile = flag.String("with", "", "Add `filename` to the input stack")
var shrink = flag.Bool("shrink", true, "When saving, don't save unused cells")
var size = flag.Int("size", 50000, "image size in cells")
flag.Parse()

var interactive bool
fn, err := setRawIO()
if err == nil {
interactive = true
defer fn()
}

// default options
var optlist = []vm.Option{
vm.OptShrinkImage(*shrink),
var opts = []vm.Option{
vm.Shrink(*shrink),
// buffered io is faster
vm.OptOutput(bufio.NewWriter(os.Stdout)),
vm.OptInput(bufio.NewReader(os.Stdin)),
vm.Output(bufio.NewWriter(os.Stdout)),
}

// buffer input if not interactive
if interactive {
opts = append(opts, vm.Input(os.Stdin))
} else {
opts = append(opts, vm.Input(bufio.NewReader(os.Stdin)))
}

// append withFile to the input stack
if len(*withFile) > 0 {
f, err := os.Open(*withFile)
var f *os.File
f, err = os.Open(*withFile)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
return
}
optlist = append(optlist, vm.OptInput(f))
opts = append(opts, vm.Input(bufio.NewReader(f)))
}

img, err := vm.Load(*fileName, *size)
if err != nil {
handleErr(err)
return
}
proc := vm.New(img, *fileName, optlist...)
err = proc.Run(len(proc.Image))
proc, err := vm.New(img, *fileName, opts...)
if err != nil {
handleErr(err)
return
}
err = proc.Run(len(proc.Image))
// filter out EOF
if e := errors.Cause(err); e == io.EOF {
err = nil
}
}
25 changes: 25 additions & 0 deletions cmd/retro/term.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// This file is part of ngaro - https://github.com/db47h/ngaro
//
// Copyright 2016 Denis Bernard <db047h@gmail.com>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//+build !linux

package main

// setRawIO() attempts to set stdin to raw IO and returns a function
// to restore IO settings as they were before
func setRawIO() (func(), error) {
return nil, nil
}
46 changes: 46 additions & 0 deletions cmd/retro/term_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// This file is part of ngaro - https://github.com/db47h/ngaro
//
// Copyright 2016 Denis Bernard <db047h@gmail.com>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
"syscall"

"github.com/pkg/term/termios"
)

func setRawIO() (func(), error) {
var tios syscall.Termios
err := termios.Tcgetattr(0, &tios)
if err != nil {
return nil, err
}
a := tios
a.Iflag &^= syscall.BRKINT | syscall.ISTRIP | syscall.IXON | syscall.IXOFF
a.Iflag |= syscall.IGNBRK | syscall.IGNPAR
a.Lflag &^= syscall.ICANON | syscall.ISIG | syscall.IEXTEN | syscall.ECHO
a.Cc[syscall.VMIN] = 1
a.Cc[syscall.VTIME] = 0
err = termios.Tcsetattr(0, termios.TCSANOW, &a)
if err != nil {
// well, try to restore as it was if it errors
termios.Tcsetattr(0, termios.TCSANOW, &tios)
return nil, err
}
return func() {
termios.Tcsetattr(0, termios.TCSANOW, &tios)
}, nil
}
2 changes: 1 addition & 1 deletion vm/run.go → vm/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ func (i *Instance) Run(toPC int) (err error) {
i.ports[port] = i.data[i.sp-1]
i.sp -= 2
if port == 3 {
if o, ok := i.output.(flusher); ok {
if o, ok := i.output.(Flusher); ok {
o.Flush()
}
}
Expand Down
13 changes: 9 additions & 4 deletions vm/io.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,14 @@ import (
"github.com/pkg/errors"
)

// flusher wraps the Flush method.
type flusher interface {
Flush() error
// Flusher is the interface that adds the Flush method to the basic io.Writer
// interface. If a Flusher is provided as a VM instance's output, its Flush
// method will be called in order to write any buffered data. This is required
// in interactive I/O mode with buffered writers where all buffered output must
// be written before prompting the user for input.
type Flusher interface {
io.Writer
Flush() error // Flush writes any buffered data to the underlying io.Writer.
}

// readWriter wraps the WriteRune method. Works the same ad bufio.Writer.WriteRune.
Expand Down Expand Up @@ -63,7 +68,7 @@ func (w *runeWriterWrapper) WriteRune(r rune) (size int, err error) {

// Flush is only a proxy for the wrapped reader's own Flush method if implemented.
func (w *runeWriterWrapper) Flush() error {
if f, ok := w.Writer.(flusher); ok {
if f, ok := w.Writer.(Flusher); ok {
return f.Flush()
}
return nil
Expand Down
55 changes: 22 additions & 33 deletions vm/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

// Package vm blah.
// TODO:
// - switch to raw stdio
// - port i/o hooks
// - complete file i/o
// - add a reset func: clear stacks/reset ip to 0, accept Options (input / output may need to be reset as well)
// - add a disasm func
Expand Down Expand Up @@ -75,44 +75,31 @@ const (
)

// Option interface
type Option interface {
set(p *Instance)
}

type optionFunc func(p *Instance)

func (f optionFunc) set(p *Instance) {
f(p)
}
type Option func(*Instance) error

// OptDataSize sets the data stack size.
func OptDataSize(size int) Option {
var f optionFunc = func(i *Instance) { i.data = make([]Cell, size) }
return f
// DataSize sets the data stack size.
func DataSize(size int) Option {
return func(i *Instance) error { i.data = make([]Cell, size); return nil }
}

// OptAddressSize sets the address stack size.
func OptAddressSize(size int) Option {
var f optionFunc = func(i *Instance) { i.address = make([]Cell, size) }
return f
// AddressSize sets the address stack size.
func AddressSize(size int) Option {
return func(i *Instance) error { i.address = make([]Cell, size); return nil }
}

// OptInput adds the given RuneReader to the list of inputs.
func OptInput(r io.Reader) Option {
var f optionFunc = func(i *Instance) { i.PushInput(r) }
return f
// Input pushes the given RuneReader on top of the input stack.
func Input(r io.Reader) Option {
return func(i *Instance) error { i.PushInput(r); return nil }
}

// OptOutput sets the output Writer.
func OptOutput(w io.Writer) Option {
var f optionFunc = func(i *Instance) { i.output = newWriter(w) }
return f
// Output sets the output Writer.
func Output(w io.Writer) Option {
return func(i *Instance) error { i.output = newWriter(w); return nil }
}

// OptShrinkImage enables or disables image shrinking when saving it.
func OptShrinkImage(shrink bool) Option {
var f optionFunc = func(i *Instance) { i.shrink = shrink }
return f
// Shrink enables or disables image shrinking when saving it.
func Shrink(shrink bool) Option {
return func(i *Instance) error { i.shrink = shrink; return nil }
}

// Instance represents an ngaro VM instance.
Expand All @@ -132,7 +119,7 @@ type Instance struct {
}

// New creates a new Ngaro Virtual Machine instance.
func New(image Image, imageFile string, opts ...Option) *Instance {
func New(image Image, imageFile string, opts ...Option) (*Instance, error) {
i := &Instance{
PC: 0,
sp: -1,
Expand All @@ -142,15 +129,17 @@ func New(image Image, imageFile string, opts ...Option) *Instance {
imageFile: imageFile,
}
for _, opt := range opts {
opt.set(i)
if err := opt(i); err != nil {
return nil, err
}
}
if i.data == nil {
i.data = make([]Cell, 1024)
}
if i.address == nil {
i.address = make([]Cell, 1024)
}
return i
return i, nil
}

// Data returns the data stack. Note that value changes will be reflected in the
Expand Down
25 changes: 17 additions & 8 deletions vm/vm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ import (
type C []vm.Cell

func setup(code, stack, rstack C) *vm.Instance {
i := vm.New(vm.Image(code), "")
i, err := vm.New(vm.Image(code), "")
if err != nil {
panic(err)
}
for _, v := range stack {
i.Push(v)
}
Expand Down Expand Up @@ -125,14 +128,18 @@ func ExampleInstance_Run() {

// Setup the VM instance with os.Stdin as first reader, and we push another
// reader with some custom init code that will include and run the retro core tests.
i := vm.New(img, imageFile,
vm.OptInput(os.Stdin),
vm.OptInput(strings.NewReader("\"testdata/core.rx\" :include\n")),
vm.OptOutput(output))
i, err := vm.New(img, imageFile,
vm.Input(os.Stdin),
vm.Input(strings.NewReader("\"testdata/core.rx\" :include\n")),
vm.Output(output))

// run it
err = i.Run(len(i.Image))
if err == nil {
err = i.Run(len(i.Image))
}
if err != nil {
// in interactive use, err may be io.EOF if any of the IO channels gets closed
// in which case this would be a normal exit condition
panic(err)
}

Expand Down Expand Up @@ -162,8 +169,10 @@ func BenchmarkRun(b *testing.B) {
b.Fatalf("%+v\n", err)
}
input.Seek(0, 0)
proc := vm.New(img, imageFile,
vm.OptInput(input))
proc, err := vm.New(img, imageFile, vm.Input(input))
if err != nil {
panic(err)
}

n := time.Now()
b.StartTimer()
Expand Down

0 comments on commit ed94b36

Please sign in to comment.