lmc/assembler.go

355 lines
6.8 KiB
Go
Raw Normal View History

2016-07-10 14:35:33 +02:00
package main
import (
"bufio"
2016-07-10 14:35:33 +02:00
"errors"
"fmt"
"io"
2016-07-10 14:35:33 +02:00
"strconv"
"strings"
2016-07-10 14:35:33 +02:00
)
const (
WORD = iota // [A-Za-z_-]+
NUMBER // [0-9]+
NEWLINE // \n
ERROR // Error
2016-07-10 14:35:33 +02:00
)
type location struct {
line int
2016-07-10 14:35:33 +02:00
column int
}
type token struct {
location location // Position of the token
value string // Text content of the token
kind int // Kind of the token
2016-07-10 14:35:33 +02:00
}
type tokenizer struct {
location location // Current position
value []byte // Current token string
reader *bufio.Reader // Reader
tokens chan<- token // Output token channel
2016-07-10 14:35:33 +02:00
}
// -----------------------------------------------------------------------------
func isSpace(c byte) bool {
return c == ' ' || c == '\r' || c == '\t'
}
func isNumber(c byte) bool {
return c >= '0' && c <= '9'
}
func isWordHead(c byte) bool {
if c >= 'a' && c <= 'z' {
c -= 32
}
2016-10-20 00:12:24 +02:00
return c >= 'A' && c <= 'Z' || c == '_'
2016-07-10 14:35:33 +02:00
}
func isWordTail(c byte) bool {
return isWordHead(c) || isNumber(c)
}
// -----------------------------------------------------------------------------
func (t *tokenizer) send(start location, kind int) {
t.tokens <- token{start, string(t.value), kind}
2016-07-10 14:35:33 +02:00
t.value = []byte{}
}
// XXX: the handling could probably be simplified by extending the "byte"
2016-10-20 00:12:24 +02:00
// to also include a special out-of-band value for errors
2016-07-10 14:35:33 +02:00
func (t *tokenizer) peek() (byte, error) {
buf, err := t.reader.Peek(1)
2016-10-20 00:12:24 +02:00
if err != nil {
return '?', err
}
2016-07-10 14:35:33 +02:00
return buf[0], err
}
func (t *tokenizer) eat() (byte, error) {
c, err := t.reader.ReadByte()
if err != nil {
return 0, err
}
2016-07-10 14:35:33 +02:00
if c == '\n' {
t.location.line++
t.location.column = 0
2016-07-10 14:35:33 +02:00
} else {
t.location.column++
2016-07-10 14:35:33 +02:00
}
return c, nil
}
// -----------------------------------------------------------------------------
func (t *tokenizer) step() error {
2016-10-20 00:12:24 +02:00
start := t.location
2016-07-10 14:35:33 +02:00
t.value = []byte{}
2016-10-20 00:12:24 +02:00
c, err := t.peek()
if err != nil {
return err
}
2016-07-10 14:35:33 +02:00
switch {
case isSpace(c):
c, err = t.eat()
2016-07-10 14:35:33 +02:00
case c == '\n':
c, err = t.eat()
t.value = append(t.value, c)
t.send(start, NEWLINE)
2016-07-10 14:35:33 +02:00
case isNumber(c):
2016-10-20 00:12:24 +02:00
for isNumber(c) {
c, err = t.eat()
t.value = append(t.value, c)
2016-07-10 14:35:33 +02:00
c, err = t.peek()
if err == io.EOF {
break
}
if err != nil {
return err
}
2016-07-10 14:35:33 +02:00
}
t.send(start, NUMBER)
2016-07-10 14:35:33 +02:00
case isWordHead(c):
2016-10-20 00:12:24 +02:00
for isWordTail(c) {
c, err = t.eat()
t.value = append(t.value, c)
2016-07-10 14:35:33 +02:00
c, err = t.peek()
if err == io.EOF {
break
}
if err != nil {
return err
}
2016-07-10 14:35:33 +02:00
}
t.send(start, WORD)
2016-07-10 14:35:33 +02:00
case c == '/':
c, err = t.eat()
c, err = t.peek()
2016-10-20 00:12:24 +02:00
if err == io.EOF {
return errors.New("unexpected EOF")
}
if err != nil {
return err
}
2016-07-10 14:35:33 +02:00
if c != '/' {
2016-10-20 00:12:24 +02:00
return errors.New(fmt.Sprintf("unrecognized input: '%c'", c))
2016-07-10 14:35:33 +02:00
}
2016-10-20 00:12:24 +02:00
for c != '\n' {
c, err = t.eat()
2016-07-10 14:35:33 +02:00
c, err = t.peek()
if err == io.EOF {
break
}
if err != nil {
return err
}
2016-07-10 14:35:33 +02:00
}
default:
2016-10-20 00:12:24 +02:00
return errors.New(fmt.Sprintf("unrecognized input: '%c'", c))
2016-07-10 14:35:33 +02:00
}
return nil
}
func tokenize(r io.Reader, tokens chan<- token) {
t := tokenizer{
location: location{line: 1, column: 0},
tokens: tokens,
reader: bufio.NewReader(r),
2016-07-10 14:35:33 +02:00
}
for {
err := t.step()
if err == io.EOF {
break
}
if err != nil {
t.tokens <- token{t.location, fmt.Sprintf("line %d, column %d: %s",
t.location.line, t.location.column, err.Error()), ERROR}
2016-07-10 14:35:33 +02:00
break
}
}
close(tokens)
}
// -----------------------------------------------------------------------------
const (
IHALT = iota
IADD
ISUBTRACT
ISTORE
ILOAD
2016-10-20 00:12:24 +02:00
_
2016-07-10 14:35:33 +02:00
IBRANCH
IBRANCH_IF_ZERO
IBRANCH_IF_POSITIVE
IINPUT
2016-10-20 00:12:24 +02:00
IOUTPUT
2016-07-10 14:35:33 +02:00
IDATA
)
var instructions = map[string]int{
2016-07-10 14:35:33 +02:00
"HLT": IHALT,
"COB": IHALT,
"ADD": IADD,
"SUB": ISUBTRACT,
"STA": ISTORE,
"LDA": ILOAD,
"BRA": IBRANCH,
"BRZ": IBRANCH_IF_ZERO,
"BRP": IBRANCH_IF_POSITIVE,
"INP": IINPUT,
2016-10-20 00:12:24 +02:00
"OUT": IOUTPUT,
2016-07-10 14:35:33 +02:00
"DAT": IDATA,
}
type instruction struct {
2016-10-20 00:12:24 +02:00
id int // What instruction this is
target string // Label name
number int // Immediate value
2016-07-10 14:35:33 +02:00
}
// -----------------------------------------------------------------------------
type assembler struct {
2016-10-20 00:12:24 +02:00
tokens chan token // Where tokens come from
output []instruction // The assembled program
labels map[string]int // Addresses of labels
2016-07-10 14:35:33 +02:00
}
func (a *assembler) step() (bool, error) {
token, ok := <-a.tokens
if !ok {
return false, nil
}
2016-07-10 14:35:33 +02:00
// 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)
}
instrHolder := instruction{id: instr}
2016-10-20 00:12:24 +02:00
token, ok := <-a.tokens
eol := false
switch {
case token.kind == WORD:
2016-07-10 14:35:33 +02:00
instrHolder.target = strings.ToUpper(token.value)
2016-10-20 00:12:24 +02:00
case token.kind == NUMBER:
2016-07-10 14:35:33 +02:00
instrHolder.number, _ = strconv.Atoi(token.value)
2016-10-20 00:12:24 +02:00
case token.kind == ERROR:
2016-07-10 14:35:33 +02:00
return false, errors.New(token.value)
2016-10-20 00:12:24 +02:00
case !ok:
fallthrough
case token.kind == NEWLINE:
// This is fine, just assume zero
eol = true
2016-07-10 14:35:33 +02:00
}
a.output = append(a.output, instrHolder)
2016-10-20 00:12:24 +02:00
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")
}
}
2016-07-10 14:35:33 +02:00
case NEWLINE:
// Ignore empty lines
case NUMBER:
return false, errors.New("Unexpected number")
case ERROR:
return false, errors.New(token.value)
}
return true, nil
}
func Assemble(r io.Reader) (code []int16, err error) {
2016-10-20 00:12:24 +02:00
a := assembler{tokens: make(chan token), labels: make(map[string]int)}
2016-07-10 14:35:33 +02:00
go tokenize(r, a.tokens)
for {
cont, err := a.step()
if err != nil {
return nil, err
}
if !cont {
break
}
}
2016-10-20 00:12:24 +02:00
code = make([]int16, 100)
for i, x := range a.output {
if i >= len(code) {
return nil, errors.New("Program too long")
}
2016-07-10 14:35:33 +02:00
n := x.id * 100
2016-10-20 00:12:24 +02:00
// 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
2016-07-10 14:35:33 +02:00
if resolved, ok := a.labels[x.target]; !ok {
return nil, errors.New("Unknown label")
} else {
n += resolved
}
2016-10-20 00:12:24 +02:00
default:
2016-07-10 14:35:33 +02:00
n += x.number
}
2016-10-20 00:12:24 +02:00
code[i] = int16(n)
2016-07-10 14:35:33 +02:00
}
return code, nil
}