General cleanup
This commit is contained in:
parent
b1e11ece87
commit
b247f025d5
168
assembler.go
168
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 {
|
||||
|
|
30
machine.go
30
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")
|
||||
|
|
Loading…
Reference in New Issue