Get it working

This commit is contained in:
Přemysl Eric Janouch 2016-10-20 00:12:24 +02:00
parent 17279e3e6e
commit b1e11ece87
Signed by: p
GPG Key ID: B715679E3A361BE6
3 changed files with 142 additions and 69 deletions

View File

@ -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
}

View File

@ -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")
}

17
main.go
View File

@ -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)
}
}