From b1e11ece87104f25cac546ece7fd7aa88437cb31 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?P=C5=99emysl=20Janouch?=
Date: Thu, 20 Oct 2016 00:12:24 +0200
Subject: [PATCH] Get it working
---
assembler.go | 136 ++++++++++++++++++++++++++++-----------------------
machine.go | 58 ++++++++++++++++++++--
main.go | 17 ++++++-
3 files changed, 142 insertions(+), 69 deletions(-)
diff --git a/assembler.go b/assembler.go
index d800500..7efce84 100644
--- a/assembler.go
+++ b/assembler.go
@@ -48,7 +48,7 @@ func isWordHead(c byte) bool {
if c >= 'a' && c <= 'z' {
c -= 32
}
- return c >= 'a' && c <= 'z' || c == '_'
+ return c >= 'A' && c <= 'Z' || c == '_'
}
func isWordTail(c byte) bool {
@@ -63,9 +63,12 @@ func (t *tokenizer) send(start location, kind int) {
}
// XXX: the handling could probably be simplified by extending the "byte"
-// to also include a special value for io.EOF and other errors
+// to also include a special out-of-band value for errors
func (t *tokenizer) peek() (byte, error) {
buf, err := t.reader.Peek(1)
+ if err != nil {
+ return '?', err
+ }
return buf[0], err
}
@@ -87,17 +90,14 @@ func (t *tokenizer) eat() (byte, error) {
// -----------------------------------------------------------------------------
func (t *tokenizer) step() error {
+ start := t.location
t.value = []byte{}
- c, err := t.peek()
- if err == io.EOF {
- return nil
- }
+ c, err := t.peek()
if err != nil {
return err
}
- start := t.location
switch {
case isSpace(c):
c, err = t.eat()
@@ -107,10 +107,10 @@ func (t *tokenizer) step() error {
t.send(start, NEWLINE)
case isNumber(c):
- c, err = t.eat()
- t.value = append(t.value, c)
+ for isNumber(c) {
+ c, err = t.eat()
+ t.value = append(t.value, c)
- for {
c, err = t.peek()
if err == io.EOF {
break
@@ -118,20 +118,13 @@ func (t *tokenizer) step() error {
if err != nil {
return err
}
-
- if !isNumber(c) {
- break
- }
-
- c, err = t.eat()
- t.value = append(t.value, c)
}
t.send(start, NUMBER)
case isWordHead(c):
- c, err = t.eat()
- t.value = append(t.value, c)
+ for isWordTail(c) {
+ c, err = t.eat()
+ t.value = append(t.value, c)
- for {
c, err = t.peek()
if err == io.EOF {
break
@@ -139,28 +132,23 @@ func (t *tokenizer) step() error {
if err != nil {
return err
}
-
- if !isWordTail(c) {
- break
- }
-
- c, err = t.eat()
- t.value = append(t.value, c)
}
t.send(start, WORD)
case c == '/':
c, err = t.eat()
- t.value = append(t.value, c)
-
c, err = t.peek()
+ if err == io.EOF {
+ return errors.New("unexpected EOF")
+ }
if err != nil {
return err
}
if c != '/' {
- return errors.New("unrecognized input")
+ return errors.New(fmt.Sprintf("unrecognized input: '%c'", c))
}
- for {
+ for c != '\n' {
+ c, err = t.eat()
c, err = t.peek()
if err == io.EOF {
break
@@ -168,14 +156,9 @@ func (t *tokenizer) step() error {
if err != nil {
return err
}
-
- if c == '\n' {
- break
- }
- t.eat()
}
default:
- return errors.New("unrecognized input")
+ return errors.New(fmt.Sprintf("unrecognized input: '%c'", c))
}
return nil
}
@@ -208,11 +191,12 @@ const (
ISUBTRACT
ISTORE
ILOAD
+ _
IBRANCH
IBRANCH_IF_ZERO
IBRANCH_IF_POSITIVE
IINPUT
- IOUTUT
+ IOUTPUT
IDATA
)
@@ -227,22 +211,22 @@ var instructions = map[string]int{
"BRZ": IBRANCH_IF_ZERO,
"BRP": IBRANCH_IF_POSITIVE,
"INP": IINPUT,
- "OUT": IOUTUT,
+ "OUT": IOUTPUT,
"DAT": IDATA,
}
type instruction struct {
- id int
- target string
- number int
+ id int // What instruction this is
+ target string // Label name
+ number int // Immediate value
}
// -----------------------------------------------------------------------------
type assembler struct {
- tokens chan token
- output []instruction
- labels map[string]int
+ tokens chan token // Where tokens come from
+ output []instruction // The assembled program
+ labels map[string]int // Addresses of labels
}
func (a *assembler) step() (bool, error) {
@@ -278,29 +262,42 @@ func (a *assembler) step() (bool, error) {
canonical = strings.ToUpper(token.value)
instr, found = instructions[canonical]
}
-
if !found {
return false, fmt.Errorf("Unknown instruction: %s", canonical)
}
instrHolder := instruction{id: instr}
- token, ok := <-a.tokens
- if !ok {
- // This is fine, just assume zero
- break
- }
- switch token.kind {
- case WORD:
+ token, ok := <-a.tokens
+ eol := false
+ switch {
+ case token.kind == WORD:
instrHolder.target = strings.ToUpper(token.value)
- case NEWLINE:
- // This is fine, just assume zero
- case NUMBER:
+ case token.kind == NUMBER:
instrHolder.number, _ = strconv.Atoi(token.value)
- case ERROR:
+ case token.kind == ERROR:
return false, errors.New(token.value)
+ case !ok:
+ fallthrough
+ case token.kind == NEWLINE:
+ // This is fine, just assume zero
+ eol = true
}
a.output = append(a.output, instrHolder)
+
+ if !eol {
+ token, ok := <-a.tokens
+ switch {
+ case !ok:
+ break
+ case token.kind == NEWLINE:
+ break
+ case token.kind == ERROR:
+ return false, errors.New(token.value)
+ default:
+ return false, errors.New("Expected end of line")
+ }
+ }
case NEWLINE:
// Ignore empty lines
case NUMBER:
@@ -312,7 +309,7 @@ func (a *assembler) step() (bool, error) {
}
func Assemble(r io.Reader) (code []int16, err error) {
- a := assembler{tokens: make(chan token)}
+ a := assembler{tokens: make(chan token), labels: make(map[string]int)}
go tokenize(r, a.tokens)
for {
@@ -325,18 +322,33 @@ func Assemble(r io.Reader) (code []int16, err error) {
}
}
- for _, x := range a.output {
+ code = make([]int16, 100)
+ for i, x := range a.output {
+ if i >= len(code) {
+ return nil, errors.New("Program too long")
+ }
n := x.id * 100
- if len(x.target) != 0 {
+ // XXX: this also stinks
+ if x.id == IDATA {
+ n = 0
+ }
+ // XXX: we should be able to handle the strange INP and OUT better
+ switch {
+ case x.id == IINPUT:
+ n = 901
+ case x.id == IOUTPUT:
+ n = 902
+ case len(x.target) != 0:
+ // Resolve targets to code locations
if resolved, ok := a.labels[x.target]; !ok {
return nil, errors.New("Unknown label")
} else {
n += resolved
}
- } else {
+ default:
n += x.number
}
- code = append(code, int16(n))
+ code[i] = int16(n)
}
return code, nil
}
diff --git a/machine.go b/machine.go
index a4b188d..cca2aa2 100644
--- a/machine.go
+++ b/machine.go
@@ -1,11 +1,59 @@
package main
-func Run(code []int16) {
- // TODO: assert that the code is 100 boxes long
+import (
+ "errors"
+ "fmt"
+)
- pc := 0
- for pc < len(code) {
+func Run(code []int16) error {
+ if len(code) != 100 {
+ return errors.New("Code must be exactly 100 mailboxes long")
}
- // TODO: throw an exception
+ // for i, x := range code {
+ // fmt.Printf("%d: %d\n", i, x)
+ // }
+ // fmt.Println()
+
+ var accumulator int16 = 0
+
+ pc := 0
+ for pc >= 0 && pc < len(code) {
+ i := code[pc]
+ pc++
+ switch i / 100 {
+ case IHALT:
+ return nil
+ case IADD:
+ accumulator += code[i%100]
+ case ISUBTRACT:
+ accumulator -= code[i%100]
+ case ISTORE:
+ code[i%100] = accumulator
+ case ILOAD:
+ accumulator = code[i%100]
+ case IBRANCH:
+ pc = int(i % 100)
+ case IBRANCH_IF_ZERO:
+ if accumulator == 0 {
+ pc = int(i % 100)
+ }
+ case IBRANCH_IF_POSITIVE:
+ if accumulator > 0 {
+ pc = int(i % 100)
+ }
+ default:
+ switch i {
+ case 901:
+ fmt.Printf("Input: ")
+ fmt.Scanf("%d\n", &accumulator)
+ case 902:
+ fmt.Printf("Output: %d\n", accumulator)
+ default:
+ e := fmt.Sprintf("Unsupported instruction %d at %d", i, pc)
+ return errors.New(e)
+ }
+ }
+ }
+ return errors.New("Program counter ran away")
}
diff --git a/main.go b/main.go
index a891931..6fbd3f9 100644
--- a/main.go
+++ b/main.go
@@ -6,10 +6,23 @@ import (
)
func main() {
- code, err := Assemble(os.Stdin)
+ if len(os.Args) != 2 {
+ fmt.Printf("usage: %s file", os.Args[0])
+ os.Exit(1)
+ }
+ file, err := os.Open(os.Args[1])
+ if err != nil {
+ fmt.Printf("Cannot open file: %s", err)
+ os.Exit(1)
+ }
+ code, err := Assemble(file)
if err != nil {
fmt.Printf("Assembly failed: %s", err)
os.Exit(1)
}
- Run(code)
+ err = Run(code)
+ if err != nil {
+ fmt.Printf("Runtime error: %s", err)
+ os.Exit(1)
+ }
}