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