269 lines
4.2 KiB
Go
269 lines
4.2 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"io"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
NOP = iota
|
|
LDA
|
|
STA
|
|
ADD
|
|
SUB
|
|
BRZ
|
|
JMP
|
|
STP
|
|
)
|
|
|
|
var opcodes = map[string]int{
|
|
"NOP": NOP,
|
|
"LDA": LDA,
|
|
"STA": STA,
|
|
"ADD": ADD,
|
|
"SUB": SUB,
|
|
"BRZ": BRZ,
|
|
"JMP": JMP,
|
|
"STP": STP,
|
|
}
|
|
|
|
var reIns = regexp.MustCompile(
|
|
`\s*(?:(\w+):)?\s*` + // label
|
|
`(NOP|LDA|STA|ADD|SUB|BRZ|JMP|STP)?\s*` + // opcode
|
|
`(\w+)?\s*` + // argument
|
|
`(?:;([\w\s]+))?`) // comment
|
|
|
|
type ByteCode [32]int
|
|
|
|
type instruction struct {
|
|
Label string
|
|
Opcode string
|
|
Arg string
|
|
}
|
|
|
|
type Program struct {
|
|
Instructions []instruction
|
|
Labels map[string]int
|
|
}
|
|
|
|
func newInstruction(line string) (*instruction, error) {
|
|
match := reIns.FindStringSubmatch(line)
|
|
if match == nil {
|
|
return nil, fmt.Errorf("syntax error: '%s'", line)
|
|
}
|
|
return &instruction{Label: match[1], Opcode: match[2], Arg: match[3]}, nil
|
|
}
|
|
|
|
func Parse(asm io.Reader) (*Program, error) {
|
|
var p Program
|
|
p.Labels = make(map[string]int, 32)
|
|
scanner := bufio.NewScanner(asm)
|
|
lineNumber := 0
|
|
|
|
for scanner.Scan() {
|
|
if instruction, err := newInstruction(scanner.Text()); err != nil {
|
|
return &p, err
|
|
} else {
|
|
if instruction.Label != "" {
|
|
p.Labels[instruction.Label] = lineNumber
|
|
}
|
|
p.Instructions = append(p.Instructions, *instruction)
|
|
lineNumber++
|
|
}
|
|
}
|
|
|
|
if err := scanner.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &p, nil
|
|
}
|
|
|
|
func (p *Program) Compile() (ByteCode, error) {
|
|
var bytecode ByteCode
|
|
var arg int
|
|
for i, ins := range p.Instructions {
|
|
if ins.Arg == "" {
|
|
arg = 0
|
|
} else if addr, err := strconv.Atoi(ins.Arg); err == nil {
|
|
arg = addr
|
|
} else if addr, ok := p.Labels[ins.Arg]; ok {
|
|
arg = addr
|
|
} else {
|
|
return bytecode, fmt.Errorf("unknown label %v", ins.Arg)
|
|
}
|
|
|
|
if opcode, ok := opcodes[ins.Opcode]; ok {
|
|
bytecode[i] = opcode<<5 | arg
|
|
} else {
|
|
bytecode[i] = arg
|
|
}
|
|
}
|
|
return bytecode, nil
|
|
}
|
|
|
|
func floorMod(a, b int) int {
|
|
return ((a % b) + b) % b
|
|
}
|
|
|
|
func Run(bytecode ByteCode) (int, error) {
|
|
acc := 0
|
|
pc := 0
|
|
mem := bytecode
|
|
|
|
var op int
|
|
var arg int
|
|
|
|
loop:
|
|
for pc < 32 {
|
|
op = mem[pc] >> 5
|
|
arg = mem[pc] & 31
|
|
pc++
|
|
|
|
switch op {
|
|
case NOP:
|
|
continue
|
|
case LDA:
|
|
acc = mem[arg]
|
|
case STA:
|
|
mem[arg] = acc
|
|
case ADD:
|
|
acc = floorMod(acc+mem[arg], 256)
|
|
case SUB:
|
|
acc = floorMod(acc-mem[arg], 256)
|
|
case BRZ:
|
|
if acc == 0 {
|
|
pc = arg
|
|
}
|
|
case JMP:
|
|
pc = arg
|
|
case STP:
|
|
break loop
|
|
default:
|
|
return acc, fmt.Errorf("runtime error: %v %v", op, arg)
|
|
}
|
|
}
|
|
|
|
return acc, nil
|
|
}
|
|
|
|
func Execute(asm string) (int, error) {
|
|
program, err := Parse(strings.NewReader(asm))
|
|
if err != nil {
|
|
return 0, fmt.Errorf("assembly error: %v", err)
|
|
}
|
|
|
|
bytecode, err := program.Compile()
|
|
if err != nil {
|
|
return 0, fmt.Errorf("compilation error: %v", err)
|
|
}
|
|
|
|
result, err := Run(bytecode)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func main() {
|
|
examples := []string{
|
|
`LDA x
|
|
ADD y ; accumulator = x + y
|
|
STP
|
|
x: 2
|
|
y: 2`,
|
|
`loop: LDA prodt
|
|
ADD x
|
|
STA prodt
|
|
LDA y
|
|
SUB one
|
|
STA y
|
|
BRZ done
|
|
JMP loop
|
|
done: LDA prodt ; to display it
|
|
STP
|
|
x: 8
|
|
y: 7
|
|
prodt: 0
|
|
one: 1`,
|
|
`loop: LDA n
|
|
STA temp
|
|
ADD m
|
|
STA n
|
|
LDA temp
|
|
STA m
|
|
LDA count
|
|
SUB one
|
|
BRZ done
|
|
STA count
|
|
JMP loop
|
|
done: LDA n ; to display it
|
|
STP
|
|
m: 1
|
|
n: 1
|
|
temp: 0
|
|
count: 8 ; valid range: 1-11
|
|
one: 1`,
|
|
`start: LDA load
|
|
ADD car ; head of list
|
|
STA ldcar
|
|
ADD one
|
|
STA ldcdr ; next CONS cell
|
|
ldcar: NOP
|
|
STA value
|
|
ldcdr: NOP
|
|
BRZ done ; 0 stands for NIL
|
|
STA car
|
|
JMP start
|
|
done: LDA value ; CAR of last CONS
|
|
STP
|
|
load: LDA 0
|
|
value: 0
|
|
car: 28
|
|
one: 1
|
|
; order of CONS cells
|
|
; in memory
|
|
; does not matter
|
|
6
|
|
0 ; 0 stands for NIL
|
|
2 ; (CADR ls)
|
|
26 ; (CDDR ls) -- etc.
|
|
5
|
|
20
|
|
3
|
|
30
|
|
1 ; value of (CAR ls)
|
|
22 ; points to (CDR ls)
|
|
4
|
|
24`,
|
|
`LDA 3
|
|
SUB 4
|
|
STP 0
|
|
0
|
|
255`,
|
|
`LDA 3
|
|
SUB 4
|
|
STP 0
|
|
0
|
|
1`,
|
|
`LDA 3
|
|
ADD 4
|
|
STP 0
|
|
1
|
|
255`,
|
|
}
|
|
|
|
for _, asm := range examples {
|
|
if result, err := Execute(asm); err == nil {
|
|
fmt.Println(result)
|
|
} else {
|
|
fmt.Println(err)
|
|
}
|
|
}
|
|
}
|