diff --git a/assembler.go b/assembler.go index 7efce84..5221040 100644 --- a/assembler.go +++ b/assembler.go @@ -10,10 +10,11 @@ import ( ) const ( - WORD = iota // [A-Za-z_-]+ - NUMBER // [0-9]+ - NEWLINE // \n - ERROR // Error + WORD = iota // [A-Za-z_-]+ + INSTRUCTION // Instruction word + NUMBER // [0-9]+ + NEWLINE // \n + ERROR // Error ) type location struct { @@ -22,9 +23,10 @@ type location struct { } type token struct { - location location // Position of the token - value string // Text content of the token - kind int // Kind of the token + location location // Position of the token + value string // Text content of the token + instruction int // INSTRUCTION ID + kind int // Kind of the token } type tokenizer struct { @@ -57,19 +59,62 @@ func isWordTail(c byte) bool { // ----------------------------------------------------------------------------- +const ( + IHALT = iota * 100 + IADD + ISUBTRACT + ISTORE + _ + ILOAD + IBRANCH + IBRANCH_IF_ZERO + IBRANCH_IF_POSITIVE + IIO +) + +const ( + _ = iota + IO_INPUT + IO_OUTPUT +) + +var instructions = map[string]int{ + "HLT": IHALT, + "COB": IHALT, + "ADD": IADD, + "SUB": ISUBTRACT, + "STA": ISTORE, + "LDA": ILOAD, + "BRA": IBRANCH, + "BRZ": IBRANCH_IF_ZERO, + "BRP": IBRANCH_IF_POSITIVE, + "INP": IIO + IO_INPUT, + "OUT": IIO + IO_OUTPUT, + "DAT": 0, +} + +// ----------------------------------------------------------------------------- + func (t *tokenizer) send(start location, kind int) { - t.tokens <- token{start, string(t.value), kind} + tok := token{start, strings.ToUpper(string(t.value)), 0, kind} + if kind == WORD { + if instr, found := instructions[tok.value]; found { + tok.kind = INSTRUCTION + tok.instruction = instr + } + } + t.tokens <- tok t.value = []byte{} } // XXX: the handling could probably be simplified by extending the "byte" // 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 { + if buf, err := t.reader.Peek(1); err != nil { return '?', err + } else { + return buf[0], nil } - return buf[0], err } func (t *tokenizer) eat() (byte, error) { @@ -170,13 +215,11 @@ func tokenize(r io.Reader, tokens chan<- token) { reader: bufio.NewReader(r), } for { - err := t.step() - if err == io.EOF { + if err := t.step(); err == io.EOF { break - } - if err != nil { + } else if err != nil { t.tokens <- token{t.location, fmt.Sprintf("line %d, column %d: %s", - t.location.line, t.location.column, err.Error()), ERROR} + t.location.line, t.location.column, err.Error()), 0, ERROR} break } } @@ -185,44 +228,12 @@ func tokenize(r io.Reader, tokens chan<- token) { // ----------------------------------------------------------------------------- -const ( - IHALT = iota - IADD - ISUBTRACT - ISTORE - ILOAD - _ - IBRANCH - IBRANCH_IF_ZERO - IBRANCH_IF_POSITIVE - IINPUT - IOUTPUT - IDATA -) - -var instructions = map[string]int{ - "HLT": IHALT, - "COB": IHALT, - "ADD": IADD, - "SUB": ISUBTRACT, - "STA": ISTORE, - "LDA": ILOAD, - "BRA": IBRANCH, - "BRZ": IBRANCH_IF_ZERO, - "BRP": IBRANCH_IF_POSITIVE, - "INP": IINPUT, - "OUT": IOUTPUT, - "DAT": IDATA, -} - type instruction struct { id int // What instruction this is target string // Label name number int // Immediate value } -// ----------------------------------------------------------------------------- - type assembler struct { tokens chan token // Where tokens come from output []instruction // The assembled program @@ -236,37 +247,22 @@ func (a *assembler) step() (bool, error) { } // TODO: add token location information to returned errors - switch token.kind { case WORD: - canonical := strings.ToUpper(token.value) - instr, found := instructions[canonical] - - // Not found in the instruction list - // Assume it is a label - if !found { - if _, dup := a.labels[canonical]; dup { - return false, fmt.Errorf("Duplicate label: %s", canonical) - } - a.labels[canonical] = len(a.output) - - token, ok = <-a.tokens - if !ok { - return false, errors.New("Unexpected end of file") - } - if token.kind != WORD { - return false, errors.New("Expected word") - } - - // XXX: it might be better to classify this in the lexer - canonical = strings.ToUpper(token.value) - instr, found = instructions[canonical] - } - if !found { - return false, fmt.Errorf("Unknown instruction: %s", canonical) + if _, dup := a.labels[token.value]; dup { + return false, fmt.Errorf("Duplicate label: %s", token.value) } + a.labels[token.value] = len(a.output) - instrHolder := instruction{id: instr} + if token, ok = <-a.tokens; !ok { + return false, errors.New("Unexpected end of file") + } + if token.kind != INSTRUCTION { + return false, errors.New("Expected instruction name after label") + } + fallthrough + case INSTRUCTION: + instrHolder := instruction{id: token.instruction} token, ok := <-a.tokens eol := false @@ -274,6 +270,7 @@ func (a *assembler) step() (bool, error) { case token.kind == WORD: instrHolder.target = strings.ToUpper(token.value) case token.kind == NUMBER: + // TODO: we should check the number instrHolder.number, _ = strconv.Atoi(token.value) case token.kind == ERROR: return false, errors.New(token.value) @@ -289,9 +286,7 @@ func (a *assembler) step() (bool, error) { token, ok := <-a.tokens switch { case !ok: - break case token.kind == NEWLINE: - break case token.kind == ERROR: return false, errors.New(token.value) default: @@ -313,11 +308,9 @@ func Assemble(r io.Reader) (code []int16, err error) { go tokenize(r, a.tokens) for { - cont, err := a.step() - if err != nil { + if cont, err := a.step(); err != nil { return nil, err - } - if !cont { + } else if !cont { break } } @@ -327,17 +320,10 @@ func Assemble(r io.Reader) (code []int16, err error) { if i >= len(code) { return nil, errors.New("Program too long") } - n := x.id * 100 - // XXX: this also stinks - if x.id == IDATA { - n = 0 - } - // XXX: we should be able to handle the strange INP and OUT better + n := x.id switch { - case x.id == IINPUT: - n = 901 - case x.id == IOUTPUT: - n = 902 + case x.id%100 != 0: + // TODO: we could complain that arguments aren't allowed case len(x.target) != 0: // Resolve targets to code locations if resolved, ok := a.labels[x.target]; !ok { diff --git a/machine.go b/machine.go index cca2aa2..c43ccae 100644 --- a/machine.go +++ b/machine.go @@ -20,39 +20,41 @@ func Run(code []int16) error { pc := 0 for pc >= 0 && pc < len(code) { i := code[pc] + arg := i % 100 pc++ - switch i / 100 { + + switch i - arg { case IHALT: return nil case IADD: - accumulator += code[i%100] + accumulator += code[arg] case ISUBTRACT: - accumulator -= code[i%100] + accumulator -= code[arg] case ISTORE: - code[i%100] = accumulator + code[arg] = accumulator case ILOAD: - accumulator = code[i%100] + accumulator = code[arg] case IBRANCH: pc = int(i % 100) case IBRANCH_IF_ZERO: if accumulator == 0 { - pc = int(i % 100) + pc = int(arg) } case IBRANCH_IF_POSITIVE: if accumulator > 0 { - pc = int(i % 100) + pc = int(arg) } - default: - switch i { - case 901: + case IIO: + switch arg { + case IO_INPUT: fmt.Printf("Input: ") fmt.Scanf("%d\n", &accumulator) - case 902: + case IO_OUTPUT: fmt.Printf("Output: %d\n", accumulator) - default: - e := fmt.Sprintf("Unsupported instruction %d at %d", i, pc) - return errors.New(e) } + default: + e := fmt.Sprintf("Unsupported instruction %d at %d", i, pc) + return errors.New(e) } } return errors.New("Program counter ran away")