General cleanup

This commit is contained in:
Přemysl Eric Janouch 2016-10-20 01:31:15 +02:00
parent b1e11ece87
commit b247f025d5
Signed by: p
GPG Key ID: B715679E3A361BE6
2 changed files with 93 additions and 105 deletions

View File

@ -10,10 +10,11 @@ import (
) )
const ( const (
WORD = iota // [A-Za-z_-]+ WORD = iota // [A-Za-z_-]+
NUMBER // [0-9]+ INSTRUCTION // Instruction word
NEWLINE // \n NUMBER // [0-9]+
ERROR // Error NEWLINE // \n
ERROR // Error
) )
type location struct { type location struct {
@ -22,9 +23,10 @@ type location struct {
} }
type token struct { type token struct {
location location // Position of the token location location // Position of the token
value string // Text content of the token value string // Text content of the token
kind int // Kind of the token instruction int // INSTRUCTION ID
kind int // Kind of the token
} }
type tokenizer struct { 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) { 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{} t.value = []byte{}
} }
// 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 out-of-band value for 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) if buf, err := t.reader.Peek(1); err != nil {
if err != nil {
return '?', err return '?', err
} else {
return buf[0], nil
} }
return buf[0], err
} }
func (t *tokenizer) eat() (byte, error) { func (t *tokenizer) eat() (byte, error) {
@ -170,13 +215,11 @@ func tokenize(r io.Reader, tokens chan<- token) {
reader: bufio.NewReader(r), reader: bufio.NewReader(r),
} }
for { for {
err := t.step() if err := t.step(); err == io.EOF {
if err == io.EOF {
break break
} } else if err != nil {
if err != nil {
t.tokens <- token{t.location, fmt.Sprintf("line %d, column %d: %s", 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 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 { type instruction struct {
id int // What instruction this is id int // What instruction this is
target string // Label name target string // Label name
number int // Immediate value number int // Immediate value
} }
// -----------------------------------------------------------------------------
type assembler struct { type assembler struct {
tokens chan token // Where tokens come from tokens chan token // Where tokens come from
output []instruction // The assembled program output []instruction // The assembled program
@ -236,37 +247,22 @@ func (a *assembler) step() (bool, error) {
} }
// TODO: add token location information to returned errors // TODO: add token location information to returned errors
switch token.kind { switch token.kind {
case WORD: case WORD:
canonical := strings.ToUpper(token.value) if _, dup := a.labels[token.value]; dup {
instr, found := instructions[canonical] return false, fmt.Errorf("Duplicate label: %s", token.value)
// 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)
} }
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 token, ok := <-a.tokens
eol := false eol := false
@ -274,6 +270,7 @@ func (a *assembler) step() (bool, error) {
case token.kind == WORD: case token.kind == WORD:
instrHolder.target = strings.ToUpper(token.value) instrHolder.target = strings.ToUpper(token.value)
case token.kind == NUMBER: case token.kind == NUMBER:
// TODO: we should check the number
instrHolder.number, _ = strconv.Atoi(token.value) instrHolder.number, _ = strconv.Atoi(token.value)
case token.kind == ERROR: case token.kind == ERROR:
return false, errors.New(token.value) return false, errors.New(token.value)
@ -289,9 +286,7 @@ func (a *assembler) step() (bool, error) {
token, ok := <-a.tokens token, ok := <-a.tokens
switch { switch {
case !ok: case !ok:
break
case token.kind == NEWLINE: case token.kind == NEWLINE:
break
case token.kind == ERROR: case token.kind == ERROR:
return false, errors.New(token.value) return false, errors.New(token.value)
default: default:
@ -313,11 +308,9 @@ func Assemble(r io.Reader) (code []int16, err error) {
go tokenize(r, a.tokens) go tokenize(r, a.tokens)
for { for {
cont, err := a.step() if cont, err := a.step(); err != nil {
if err != nil {
return nil, err return nil, err
} } else if !cont {
if !cont {
break break
} }
} }
@ -327,17 +320,10 @@ func Assemble(r io.Reader) (code []int16, err error) {
if i >= len(code) { if i >= len(code) {
return nil, errors.New("Program too long") return nil, errors.New("Program too long")
} }
n := x.id * 100 n := x.id
// XXX: this also stinks
if x.id == IDATA {
n = 0
}
// XXX: we should be able to handle the strange INP and OUT better
switch { switch {
case x.id == IINPUT: case x.id%100 != 0:
n = 901 // TODO: we could complain that arguments aren't allowed
case x.id == IOUTPUT:
n = 902
case len(x.target) != 0: case len(x.target) != 0:
// Resolve targets to code locations // Resolve targets to code locations
if resolved, ok := a.labels[x.target]; !ok { if resolved, ok := a.labels[x.target]; !ok {

View File

@ -20,39 +20,41 @@ func Run(code []int16) error {
pc := 0 pc := 0
for pc >= 0 && pc < len(code) { for pc >= 0 && pc < len(code) {
i := code[pc] i := code[pc]
arg := i % 100
pc++ pc++
switch i / 100 {
switch i - arg {
case IHALT: case IHALT:
return nil return nil
case IADD: case IADD:
accumulator += code[i%100] accumulator += code[arg]
case ISUBTRACT: case ISUBTRACT:
accumulator -= code[i%100] accumulator -= code[arg]
case ISTORE: case ISTORE:
code[i%100] = accumulator code[arg] = accumulator
case ILOAD: case ILOAD:
accumulator = code[i%100] accumulator = code[arg]
case IBRANCH: case IBRANCH:
pc = int(i % 100) pc = int(i % 100)
case IBRANCH_IF_ZERO: case IBRANCH_IF_ZERO:
if accumulator == 0 { if accumulator == 0 {
pc = int(i % 100) pc = int(arg)
} }
case IBRANCH_IF_POSITIVE: case IBRANCH_IF_POSITIVE:
if accumulator > 0 { if accumulator > 0 {
pc = int(i % 100) pc = int(arg)
} }
default: case IIO:
switch i { switch arg {
case 901: case IO_INPUT:
fmt.Printf("Input: ") fmt.Printf("Input: ")
fmt.Scanf("%d\n", &accumulator) fmt.Scanf("%d\n", &accumulator)
case 902: case IO_OUTPUT:
fmt.Printf("Output: %d\n", accumulator) 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") return errors.New("Program counter ran away")