⚠️ Warning: This is a draft ⚠️

This means it might contain formatting issues, incorrect code, conceptual problems, or other severe issues.

If you want to help to improve and eventually enable this page, please fork RosettaGit's repository and open a merge request on GitHub.

A much longer Go version for [[Subleq#Go]].

This version would be more appropriate if this was the start of a more complicated virtual machine or if multiple different virtual machines were to be supported. E.g. perhaps via a simple interface like:

type VirtualMachine interface {
	Run()  error
	Step() error
	Err()  error
}
package main

import (
	"bufio"
	"errors"
	"flag"
	"io"
	"log"
	"os"
	"strconv"
)

type word int // or int8, int64, whatever

// A Subleq represents the entire state of a virtual Subleq machine.
type Subleq struct {
	mem    []word // memory contents
	ip     int    // instruction pointer
	Stdin  io.ByteReader
	Stdout io.Writer // or io.ByteWriter, but using bufio.Writer requires flushing
	err    error
}

// Errors produced by Step or Run methods.
var (
	ErrHalt = errors.New("halted")
)

// New returns a new Subleq machine instance with the specified
// program loaded into memory.
// The machine's Stdin and Stdout are set to os.Stdin and os.Stdout.
func New(prog []word) *Subleq {
	return &Subleq{
		mem:    prog,
		Stdin:  bufio.NewReader(os.Stdin),
		Stdout: os.Stdout,
	}
}

func NewFrom(r io.Reader) (*Subleq, error) {
	var prog []word
	scan := bufio.NewScanner(r)
	scan.Split(bufio.ScanWords)
	for scan.Scan() {
		v, err := strconv.Atoi(scan.Text())
		if err != nil {
			return nil, err
		}
		prog = append(prog, word(v))
	}
	if err := scan.Err(); err != nil {
		return nil, err
	}
	return New(prog), nil
}

func (s Subleq) Err() error { return s.err }
func (s *Subleq) Run() error {
	for s.Step() == nil {
	}
	return s.Err()
}

func (s *Subleq) Step() (err error) {
	defer func() {
		// In particular, turn runtime "index out of range"
		// panics into plain errors.
		if r := recover(); r != nil {
			if e, ok := r.(error); ok && err == nil {
				err = e
			} else {
				panic(r)
			}
		}
		// save errors other than halt
		if s.err == nil && err != nil && err != ErrHalt {
			s.err = err
		}
	}()
	if s.err != nil {
		return s.err
	}
	if s.ip < 0 {
		return ErrHalt
	}
	op := s.ReadOp()
	switch {
	case op.a == -1:
		var c byte
		c, err = s.Stdin.ReadByte()
		if err == nil {
			s.mem[op.b] = word(c)
		}
	case op.b == -1:
		//err = s.Stdout.WriteByte(byte(s.mem[op.a]))
		_, err = s.Stdout.Write([]byte{byte(s.mem[op.a])})
	default:
		b := s.mem[op.b] - s.mem[op.a]
		s.mem[op.b] = b
		if b <= 0 {
			s.ip = int(op.c)
		}
	}
	return err
}

func (s *Subleq) ReadOp() operands {
	op := operands{
		a: s.mem[s.ip],
		b: s.mem[s.ip+1],
		c: s.mem[s.ip+2],
	}
	s.ip += 3
	return op
}

type operands struct{ a, b, c word }

func main() {
	// default program
	var prog = []word{
		15, 17, -1,
		17, -1, -1,
		16, 1, -1,
		16, 3, -1,
		15, 15, 0,

		0, -1, 72, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 33, 10, 0,
		// or:
		// 0, -1,
		// 'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\n',
		// 0,
	}
	var s *Subleq
	flag.Parse()
	switch flag.NArg() {
	case 1:
		filename := flag.Arg(0)
		f, err := os.Open(filename)
		if err != nil {
			log.Fatal(err)
		}
		s, err = NewFrom(f)
		f.Close()
		if err != nil {
			log.Fatal(err)
		}
	default:
		prog = prog[:0]
		for _, arg := range flag.Args() {
			v, err := strconv.Atoi(arg)
			if err != nil {
				log.Fatal(err)
			}
			prog = append(prog, word(v))
		}
		fallthrough
	case 0:
		s = New(prog)
	}
	if err := s.Run(); err != nil {
		log.Fatalln("error:", err)
	}
}