General cleanup
This commit is contained in:
parent
b1e11ece87
commit
b247f025d5
148
assembler.go
148
assembler.go
|
@ -11,6 +11,7 @@ import (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
WORD = iota // [A-Za-z_-]+
|
WORD = iota // [A-Za-z_-]+
|
||||||
|
INSTRUCTION // Instruction word
|
||||||
NUMBER // [0-9]+
|
NUMBER // [0-9]+
|
||||||
NEWLINE // \n
|
NEWLINE // \n
|
||||||
ERROR // Error
|
ERROR // Error
|
||||||
|
@ -24,6 +25,7 @@ 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
|
||||||
|
instruction int // INSTRUCTION ID
|
||||||
kind int // Kind of the token
|
kind int // Kind of the token
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
a.labels[token.value] = len(a.output)
|
||||||
|
|
||||||
token, ok = <-a.tokens
|
if token, ok = <-a.tokens; !ok {
|
||||||
if !ok {
|
|
||||||
return false, errors.New("Unexpected end of file")
|
return false, errors.New("Unexpected end of file")
|
||||||
}
|
}
|
||||||
if token.kind != WORD {
|
if token.kind != INSTRUCTION {
|
||||||
return false, errors.New("Expected word")
|
return false, errors.New("Expected instruction name after label")
|
||||||
}
|
}
|
||||||
|
fallthrough
|
||||||
// XXX: it might be better to classify this in the lexer
|
case INSTRUCTION:
|
||||||
canonical = strings.ToUpper(token.value)
|
instrHolder := instruction{id: token.instruction}
|
||||||
instr, found = instructions[canonical]
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
return false, fmt.Errorf("Unknown instruction: %s", canonical)
|
|
||||||
}
|
|
||||||
|
|
||||||
instrHolder := instruction{id: instr}
|
|
||||||
|
|
||||||
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 {
|
||||||
|
|
26
machine.go
26
machine.go
|
@ -20,40 +20,42 @@ 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:
|
default:
|
||||||
e := fmt.Sprintf("Unsupported instruction %d at %d", i, pc)
|
e := fmt.Sprintf("Unsupported instruction %d at %d", i, pc)
|
||||||
return errors.New(e)
|
return errors.New(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return errors.New("Program counter ran away")
|
return errors.New("Program counter ran away")
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue