Get it working
This commit is contained in:
parent
17279e3e6e
commit
b1e11ece87
136
assembler.go
136
assembler.go
|
@ -48,7 +48,7 @@ func isWordHead(c byte) bool {
|
||||||
if c >= 'a' && c <= 'z' {
|
if c >= 'a' && c <= 'z' {
|
||||||
c -= 32
|
c -= 32
|
||||||
}
|
}
|
||||||
return c >= 'a' && c <= 'z' || c == '_'
|
return c >= 'A' && c <= 'Z' || c == '_'
|
||||||
}
|
}
|
||||||
|
|
||||||
func isWordTail(c byte) bool {
|
func isWordTail(c byte) bool {
|
||||||
|
@ -63,9 +63,12 @@ func (t *tokenizer) send(start location, kind int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 value for io.EOF and other 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)
|
buf, err := t.reader.Peek(1)
|
||||||
|
if err != nil {
|
||||||
|
return '?', err
|
||||||
|
}
|
||||||
return buf[0], err
|
return buf[0], err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,17 +90,14 @@ func (t *tokenizer) eat() (byte, error) {
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
func (t *tokenizer) step() error {
|
func (t *tokenizer) step() error {
|
||||||
|
start := t.location
|
||||||
t.value = []byte{}
|
t.value = []byte{}
|
||||||
c, err := t.peek()
|
|
||||||
|
|
||||||
if err == io.EOF {
|
c, err := t.peek()
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
start := t.location
|
|
||||||
switch {
|
switch {
|
||||||
case isSpace(c):
|
case isSpace(c):
|
||||||
c, err = t.eat()
|
c, err = t.eat()
|
||||||
|
@ -107,10 +107,10 @@ func (t *tokenizer) step() error {
|
||||||
|
|
||||||
t.send(start, NEWLINE)
|
t.send(start, NEWLINE)
|
||||||
case isNumber(c):
|
case isNumber(c):
|
||||||
c, err = t.eat()
|
for isNumber(c) {
|
||||||
t.value = append(t.value, c)
|
c, err = t.eat()
|
||||||
|
t.value = append(t.value, c)
|
||||||
|
|
||||||
for {
|
|
||||||
c, err = t.peek()
|
c, err = t.peek()
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
break
|
break
|
||||||
|
@ -118,20 +118,13 @@ func (t *tokenizer) step() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isNumber(c) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
c, err = t.eat()
|
|
||||||
t.value = append(t.value, c)
|
|
||||||
}
|
}
|
||||||
t.send(start, NUMBER)
|
t.send(start, NUMBER)
|
||||||
case isWordHead(c):
|
case isWordHead(c):
|
||||||
c, err = t.eat()
|
for isWordTail(c) {
|
||||||
t.value = append(t.value, c)
|
c, err = t.eat()
|
||||||
|
t.value = append(t.value, c)
|
||||||
|
|
||||||
for {
|
|
||||||
c, err = t.peek()
|
c, err = t.peek()
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
break
|
break
|
||||||
|
@ -139,28 +132,23 @@ func (t *tokenizer) step() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isWordTail(c) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
c, err = t.eat()
|
|
||||||
t.value = append(t.value, c)
|
|
||||||
}
|
}
|
||||||
t.send(start, WORD)
|
t.send(start, WORD)
|
||||||
case c == '/':
|
case c == '/':
|
||||||
c, err = t.eat()
|
c, err = t.eat()
|
||||||
t.value = append(t.value, c)
|
|
||||||
|
|
||||||
c, err = t.peek()
|
c, err = t.peek()
|
||||||
|
if err == io.EOF {
|
||||||
|
return errors.New("unexpected EOF")
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if c != '/' {
|
if c != '/' {
|
||||||
return errors.New("unrecognized input")
|
return errors.New(fmt.Sprintf("unrecognized input: '%c'", c))
|
||||||
}
|
}
|
||||||
for {
|
for c != '\n' {
|
||||||
|
c, err = t.eat()
|
||||||
c, err = t.peek()
|
c, err = t.peek()
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
break
|
break
|
||||||
|
@ -168,14 +156,9 @@ func (t *tokenizer) step() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if c == '\n' {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
t.eat()
|
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return errors.New("unrecognized input")
|
return errors.New(fmt.Sprintf("unrecognized input: '%c'", c))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -208,11 +191,12 @@ const (
|
||||||
ISUBTRACT
|
ISUBTRACT
|
||||||
ISTORE
|
ISTORE
|
||||||
ILOAD
|
ILOAD
|
||||||
|
_
|
||||||
IBRANCH
|
IBRANCH
|
||||||
IBRANCH_IF_ZERO
|
IBRANCH_IF_ZERO
|
||||||
IBRANCH_IF_POSITIVE
|
IBRANCH_IF_POSITIVE
|
||||||
IINPUT
|
IINPUT
|
||||||
IOUTUT
|
IOUTPUT
|
||||||
IDATA
|
IDATA
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -227,22 +211,22 @@ var instructions = map[string]int{
|
||||||
"BRZ": IBRANCH_IF_ZERO,
|
"BRZ": IBRANCH_IF_ZERO,
|
||||||
"BRP": IBRANCH_IF_POSITIVE,
|
"BRP": IBRANCH_IF_POSITIVE,
|
||||||
"INP": IINPUT,
|
"INP": IINPUT,
|
||||||
"OUT": IOUTUT,
|
"OUT": IOUTPUT,
|
||||||
"DAT": IDATA,
|
"DAT": IDATA,
|
||||||
}
|
}
|
||||||
|
|
||||||
type instruction struct {
|
type instruction struct {
|
||||||
id int
|
id int // What instruction this is
|
||||||
target string
|
target string // Label name
|
||||||
number int
|
number int // Immediate value
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
type assembler struct {
|
type assembler struct {
|
||||||
tokens chan token
|
tokens chan token // Where tokens come from
|
||||||
output []instruction
|
output []instruction // The assembled program
|
||||||
labels map[string]int
|
labels map[string]int // Addresses of labels
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *assembler) step() (bool, error) {
|
func (a *assembler) step() (bool, error) {
|
||||||
|
@ -278,29 +262,42 @@ func (a *assembler) step() (bool, error) {
|
||||||
canonical = strings.ToUpper(token.value)
|
canonical = strings.ToUpper(token.value)
|
||||||
instr, found = instructions[canonical]
|
instr, found = instructions[canonical]
|
||||||
}
|
}
|
||||||
|
|
||||||
if !found {
|
if !found {
|
||||||
return false, fmt.Errorf("Unknown instruction: %s", canonical)
|
return false, fmt.Errorf("Unknown instruction: %s", canonical)
|
||||||
}
|
}
|
||||||
|
|
||||||
instrHolder := instruction{id: instr}
|
instrHolder := instruction{id: instr}
|
||||||
token, ok := <-a.tokens
|
|
||||||
if !ok {
|
|
||||||
// This is fine, just assume zero
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
switch token.kind {
|
token, ok := <-a.tokens
|
||||||
case WORD:
|
eol := false
|
||||||
|
switch {
|
||||||
|
case token.kind == WORD:
|
||||||
instrHolder.target = strings.ToUpper(token.value)
|
instrHolder.target = strings.ToUpper(token.value)
|
||||||
case NEWLINE:
|
case token.kind == NUMBER:
|
||||||
// This is fine, just assume zero
|
|
||||||
case NUMBER:
|
|
||||||
instrHolder.number, _ = strconv.Atoi(token.value)
|
instrHolder.number, _ = strconv.Atoi(token.value)
|
||||||
case ERROR:
|
case token.kind == ERROR:
|
||||||
return false, errors.New(token.value)
|
return false, errors.New(token.value)
|
||||||
|
case !ok:
|
||||||
|
fallthrough
|
||||||
|
case token.kind == NEWLINE:
|
||||||
|
// This is fine, just assume zero
|
||||||
|
eol = true
|
||||||
}
|
}
|
||||||
a.output = append(a.output, instrHolder)
|
a.output = append(a.output, instrHolder)
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
case NEWLINE:
|
case NEWLINE:
|
||||||
// Ignore empty lines
|
// Ignore empty lines
|
||||||
case NUMBER:
|
case NUMBER:
|
||||||
|
@ -312,7 +309,7 @@ func (a *assembler) step() (bool, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func Assemble(r io.Reader) (code []int16, err error) {
|
func Assemble(r io.Reader) (code []int16, err error) {
|
||||||
a := assembler{tokens: make(chan token)}
|
a := assembler{tokens: make(chan token), labels: make(map[string]int)}
|
||||||
go tokenize(r, a.tokens)
|
go tokenize(r, a.tokens)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
@ -325,18 +322,33 @@ func Assemble(r io.Reader) (code []int16, err error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, x := range a.output {
|
code = make([]int16, 100)
|
||||||
|
for i, x := range a.output {
|
||||||
|
if i >= len(code) {
|
||||||
|
return nil, errors.New("Program too long")
|
||||||
|
}
|
||||||
n := x.id * 100
|
n := x.id * 100
|
||||||
if len(x.target) != 0 {
|
// 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
|
||||||
if resolved, ok := a.labels[x.target]; !ok {
|
if resolved, ok := a.labels[x.target]; !ok {
|
||||||
return nil, errors.New("Unknown label")
|
return nil, errors.New("Unknown label")
|
||||||
} else {
|
} else {
|
||||||
n += resolved
|
n += resolved
|
||||||
}
|
}
|
||||||
} else {
|
default:
|
||||||
n += x.number
|
n += x.number
|
||||||
}
|
}
|
||||||
code = append(code, int16(n))
|
code[i] = int16(n)
|
||||||
}
|
}
|
||||||
return code, nil
|
return code, nil
|
||||||
}
|
}
|
||||||
|
|
58
machine.go
58
machine.go
|
@ -1,11 +1,59 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
func Run(code []int16) {
|
import (
|
||||||
// TODO: assert that the code is 100 boxes long
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
pc := 0
|
func Run(code []int16) error {
|
||||||
for pc < len(code) {
|
if len(code) != 100 {
|
||||||
|
return errors.New("Code must be exactly 100 mailboxes long")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: throw an exception
|
// for i, x := range code {
|
||||||
|
// fmt.Printf("%d: %d\n", i, x)
|
||||||
|
// }
|
||||||
|
// fmt.Println()
|
||||||
|
|
||||||
|
var accumulator int16 = 0
|
||||||
|
|
||||||
|
pc := 0
|
||||||
|
for pc >= 0 && pc < len(code) {
|
||||||
|
i := code[pc]
|
||||||
|
pc++
|
||||||
|
switch i / 100 {
|
||||||
|
case IHALT:
|
||||||
|
return nil
|
||||||
|
case IADD:
|
||||||
|
accumulator += code[i%100]
|
||||||
|
case ISUBTRACT:
|
||||||
|
accumulator -= code[i%100]
|
||||||
|
case ISTORE:
|
||||||
|
code[i%100] = accumulator
|
||||||
|
case ILOAD:
|
||||||
|
accumulator = code[i%100]
|
||||||
|
case IBRANCH:
|
||||||
|
pc = int(i % 100)
|
||||||
|
case IBRANCH_IF_ZERO:
|
||||||
|
if accumulator == 0 {
|
||||||
|
pc = int(i % 100)
|
||||||
|
}
|
||||||
|
case IBRANCH_IF_POSITIVE:
|
||||||
|
if accumulator > 0 {
|
||||||
|
pc = int(i % 100)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
switch i {
|
||||||
|
case 901:
|
||||||
|
fmt.Printf("Input: ")
|
||||||
|
fmt.Scanf("%d\n", &accumulator)
|
||||||
|
case 902:
|
||||||
|
fmt.Printf("Output: %d\n", accumulator)
|
||||||
|
default:
|
||||||
|
e := fmt.Sprintf("Unsupported instruction %d at %d", i, pc)
|
||||||
|
return errors.New(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errors.New("Program counter ran away")
|
||||||
}
|
}
|
||||||
|
|
17
main.go
17
main.go
|
@ -6,10 +6,23 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
code, err := Assemble(os.Stdin)
|
if len(os.Args) != 2 {
|
||||||
|
fmt.Printf("usage: %s file", os.Args[0])
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
file, err := os.Open(os.Args[1])
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Cannot open file: %s", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
code, err := Assemble(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Assembly failed: %s", err)
|
fmt.Printf("Assembly failed: %s", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
Run(code)
|
err = Run(code)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Runtime error: %s", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue