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' { if c >= 'a' && c <= 'z' {
c -= 32 c -= 32
} }
return c >= 'a' && c <= 'z' || c == '_' return c >= 'A' && c <= 'Z' || c == '_'
} }
func isWordTail(c byte) bool { 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" // 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) { func (t *tokenizer) peek() (byte, error) {
buf, err := t.reader.Peek(1) buf, err := t.reader.Peek(1)
if err != nil {
return '?', err
}
return buf[0], err return buf[0], err
} }
@ -87,17 +90,14 @@ func (t *tokenizer) eat() (byte, error) {
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
func (t *tokenizer) step() error { func (t *tokenizer) step() error {
start := t.location
t.value = []byte{} t.value = []byte{}
c, err := t.peek()
if err == io.EOF { c, err := t.peek()
return nil
}
if err != nil { if err != nil {
return err return err
} }
start := t.location
switch { switch {
case isSpace(c): case isSpace(c):
c, err = t.eat() c, err = t.eat()
@ -107,10 +107,10 @@ func (t *tokenizer) step() error {
t.send(start, NEWLINE) t.send(start, NEWLINE)
case isNumber(c): case isNumber(c):
c, err = t.eat() for isNumber(c) {
t.value = append(t.value, c) c, err = t.eat()
t.value = append(t.value, c)
for {
c, err = t.peek() c, err = t.peek()
if err == io.EOF { if err == io.EOF {
break break
@ -118,20 +118,13 @@ func (t *tokenizer) step() error {
if err != nil { if err != nil {
return err return err
} }
if !isNumber(c) {
break
}
c, err = t.eat()
t.value = append(t.value, c)
} }
t.send(start, NUMBER) t.send(start, NUMBER)
case isWordHead(c): case isWordHead(c):
c, err = t.eat() for isWordTail(c) {
t.value = append(t.value, c) c, err = t.eat()
t.value = append(t.value, c)
for {
c, err = t.peek() c, err = t.peek()
if err == io.EOF { if err == io.EOF {
break break
@ -139,28 +132,23 @@ func (t *tokenizer) step() error {
if err != nil { if err != nil {
return err return err
} }
if !isWordTail(c) {
break
}
c, err = t.eat()
t.value = append(t.value, c)
} }
t.send(start, WORD) t.send(start, WORD)
case c == '/': case c == '/':
c, err = t.eat() c, err = t.eat()
t.value = append(t.value, c)
c, err = t.peek() c, err = t.peek()
if err == io.EOF {
return errors.New("unexpected EOF")
}
if err != nil { if err != nil {
return err return err
} }
if c != '/' { 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() c, err = t.peek()
if err == io.EOF { if err == io.EOF {
break break
@ -168,14 +156,9 @@ func (t *tokenizer) step() error {
if err != nil { if err != nil {
return err return err
} }
if c == '\n' {
break
}
t.eat()
} }
default: default:
return errors.New("unrecognized input") return errors.New(fmt.Sprintf("unrecognized input: '%c'", c))
} }
return nil return nil
} }
@ -208,11 +191,12 @@ const (
ISUBTRACT ISUBTRACT
ISTORE ISTORE
ILOAD ILOAD
_
IBRANCH IBRANCH
IBRANCH_IF_ZERO IBRANCH_IF_ZERO
IBRANCH_IF_POSITIVE IBRANCH_IF_POSITIVE
IINPUT IINPUT
IOUTUT IOUTPUT
IDATA IDATA
) )
@ -227,22 +211,22 @@ var instructions = map[string]int{
"BRZ": IBRANCH_IF_ZERO, "BRZ": IBRANCH_IF_ZERO,
"BRP": IBRANCH_IF_POSITIVE, "BRP": IBRANCH_IF_POSITIVE,
"INP": IINPUT, "INP": IINPUT,
"OUT": IOUTUT, "OUT": IOUTPUT,
"DAT": IDATA, "DAT": IDATA,
} }
type instruction struct { type instruction struct {
id int id int // What instruction this is
target string target string // Label name
number int number int // Immediate value
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
type assembler struct { type assembler struct {
tokens chan token tokens chan token // Where tokens come from
output []instruction output []instruction // The assembled program
labels map[string]int labels map[string]int // Addresses of labels
} }
func (a *assembler) step() (bool, error) { func (a *assembler) step() (bool, error) {
@ -278,29 +262,42 @@ func (a *assembler) step() (bool, error) {
canonical = strings.ToUpper(token.value) canonical = strings.ToUpper(token.value)
instr, found = instructions[canonical] instr, found = instructions[canonical]
} }
if !found { if !found {
return false, fmt.Errorf("Unknown instruction: %s", canonical) return false, fmt.Errorf("Unknown instruction: %s", canonical)
} }
instrHolder := instruction{id: instr} instrHolder := instruction{id: instr}
token, ok := <-a.tokens
if !ok {
// This is fine, just assume zero
break
}
switch token.kind { token, ok := <-a.tokens
case WORD: eol := false
switch {
case token.kind == WORD:
instrHolder.target = strings.ToUpper(token.value) instrHolder.target = strings.ToUpper(token.value)
case NEWLINE: case token.kind == NUMBER:
// This is fine, just assume zero
case NUMBER:
instrHolder.number, _ = strconv.Atoi(token.value) instrHolder.number, _ = strconv.Atoi(token.value)
case ERROR: case token.kind == ERROR:
return false, errors.New(token.value) 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) 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: case NEWLINE:
// Ignore empty lines // Ignore empty lines
case NUMBER: case NUMBER:
@ -312,7 +309,7 @@ func (a *assembler) step() (bool, error) {
} }
func Assemble(r io.Reader) (code []int16, err 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) go tokenize(r, a.tokens)
for { 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 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 { if resolved, ok := a.labels[x.target]; !ok {
return nil, errors.New("Unknown label") return nil, errors.New("Unknown label")
} else { } else {
n += resolved n += resolved
} }
} else { default:
n += x.number n += x.number
} }
code = append(code, int16(n)) code[i] = int16(n)
} }
return code, nil return code, nil
} }

View File

@ -1,11 +1,59 @@
package main package main
func Run(code []int16) { import (
// TODO: assert that the code is 100 boxes long "errors"
"fmt"
)
pc := 0 func Run(code []int16) error {
for pc < len(code) { 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() { 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 { if err != nil {
fmt.Printf("Assembly failed: %s", err) fmt.Printf("Assembly failed: %s", err)
os.Exit(1) os.Exit(1)
} }
Run(code) err = Run(code)
if err != nil {
fmt.Printf("Runtime error: %s", err)
os.Exit(1)
}
} }