Compare commits

5 Commits

Author SHA1 Message Date
Sasha Koshka
c7d948cdcd Added some helpful comments to the creature command 2022-09-08 18:46:59 -04:00
Sasha Koshka
dcd8c56c0d Added basic implementation that executes text files 2022-09-08 16:02:36 -04:00
Sasha Koshka
a52e5d3426 Ran gofmt 2022-09-08 14:43:19 -04:00
Sasha Koshka
b0068336cd Programs are now loaded with a function and not a member var 2022-09-08 14:41:50 -04:00
Sasha Koshka
3be08736b4 Downgraded to go 1.18 so it works on my laptop 2022-09-08 14:36:28 -04:00
9 changed files with 217 additions and 82 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/cmd/creature/creature

View File

@@ -0,0 +1,10 @@
---
0 0
F
0 1
F
0 1
0 0
E

Binary file not shown.

112
cmd/creature/main.go Normal file
View File

@@ -0,0 +1,112 @@
package main
import "io"
import "os"
import "fmt"
import "bufio"
import "strconv"
import cre "git.tebibyte.media/sashakoshka/creature"
func main() {
if len(os.Args) != 2 {
logLine("ERR file unspecified")
os.Exit(1)
}
file, err := os.Open(os.Args[1])
if err != nil {
logLine("ERR could not open file: " + err.Error())
os.Exit(1)
}
program, block, err := readFile(file)
if err != nil {
logLine("ERR could not read file: " + err.Error())
os.Exit(1)
}
machine := cre.Machine[int]{}
machine.LoadProgram(program)
machine.LoadMemory(block)
machine.Register(0, read)
machine.Register(1, write)
err = machine.Execute(0)
if err != nil {
logLine("XXX machine failed: " + err.Error())
os.Exit(1)
}
}
// read implements a basic input function for the creature. It waits until it
// has recieved one byte of user input, and then pushes that byte onto the
// stack.
func read(machine *cre.Machine[int]) (stop bool) {
ch := []byte{0}
os.Stdin.Read(ch)
machine.Push(int(ch[0]))
return
}
// write implements a basic output function for the creature. It pops one byte
// off of the stack, and writes it to stdout.
func write(machine *cre.Machine[int]) (stop bool) {
print(string(rune(machine.Pop())))
return
}
// logLine prints a message to stderr.
func logLine(message ...any) {
fmt.Fprintln(os.Stderr, message...)
}
// readFile reads data from an io.Reader into a program slice and a block slice.
// Data in the file is represented by signed hexidecimal numbers, separated by
// whitespace. Three dashes (---) divide the block data from the program data.
// See examples/echo.
func readFile(reader io.Reader) (program []int, block []int, err error) {
scanner := bufio.NewScanner(reader)
scanner.Split(bufio.ScanWords)
blockLen := 0
block = make([]int, 8)
for scanner.Scan() {
if scanner.Text() == "---" {
break
}
if blockLen >= len(block) {
newSlice := make([]int, len(block)*2)
copy(newSlice, block)
block = newSlice
}
var number int64
number, err = strconv.ParseInt(scanner.Text(), 16, 64)
if err != nil {
return
}
block[blockLen] = int(number)
blockLen++
}
block = block[:blockLen]
programLen := 0
program = make([]int, 8)
for scanner.Scan() {
if programLen >= len(program) {
newSlice := make([]int, len(program)*2)
copy(newSlice, program)
program = newSlice
}
var number int64
number, err = strconv.ParseInt(scanner.Text(), 16, 64)
if err != nil {
return
}
program[programLen] = int(number)
programLen++
}
program = program[:programLen]
return
}

View File

@@ -2,19 +2,16 @@ package creature
// Word is a type constraint defining possible word types for the Machine. // Word is a type constraint defining possible word types for the Machine.
type Word interface { type Word interface {
int8 | int16 | int32 | int64 | int8 | int16 | int32 | int64 |
uint8 | uint16 | uint32 | uint64 | uint8 | uint16 | uint32 | uint64 |
int | uint int | uint
} }
// Machine is a stack machine. It contains an array of integers as its program // Machine is a stack machine. It contains an array of integers as its program
// data, and provides methods to run this program data, as well as interact with // data, and provides methods to run this program data, as well as interact with
// it. // it.
type Machine [WORD Word] struct { type Machine[WORD Word] struct {
// Program is not modified by the machine, and can be freely set before program []WORD
// the machine is started.
Program []WORD
stack []WORD stack []WORD
block []WORD block []WORD
counter WORD counter WORD
@@ -25,7 +22,7 @@ type Machine [WORD Word] struct {
// MachineFunction is a function that can extend the functionality of the stack // MachineFunction is a function that can extend the functionality of the stack
// machine. It is passed a pointer to the machine that is calling it, and the // machine. It is passed a pointer to the machine that is calling it, and the
// machine will halt execution if true is returned. // machine will halt execution if true is returned.
type MachineFunction [WORD Word] func(machine *Machine[WORD]) (stop bool) type MachineFunction[WORD Word] func(machine *Machine[WORD]) (stop bool)
// All supported opcodes // All supported opcodes
const ( const (
@@ -87,7 +84,7 @@ func (machine *Machine[WORD]) Reset() {
func (machine *Machine[WORD]) Execute(offset WORD) (err error) { func (machine *Machine[WORD]) Execute(offset WORD) (err error) {
machine.counter = offset machine.counter = offset
for int(machine.counter) < len(machine.Program) { for int(machine.counter) < len(machine.program) {
switch machine.instruction() { switch machine.instruction() {
case PUSH: case PUSH:
machine.counter++ machine.counter++
@@ -106,22 +103,22 @@ func (machine *Machine[WORD]) Execute(offset WORD) (err error) {
case ADD: case ADD:
right := machine.Pop() right := machine.Pop()
left := machine.Pop() left := machine.Pop()
machine.Push(left + right) machine.Push(left + right)
case SUB: case SUB:
right := machine.Pop() right := machine.Pop()
left := machine.Pop() left := machine.Pop()
machine.Push(left - right) machine.Push(left - right)
case MUL: case MUL:
right := machine.Pop() right := machine.Pop()
left := machine.Pop() left := machine.Pop()
machine.Push(left * right) machine.Push(left * right)
case DIV: case DIV:
right := machine.Pop() right := machine.Pop()
left := machine.Pop() left := machine.Pop()
if right == 0 { if right == 0 {
err = ErrorDivideByZero err = ErrorDivideByZero
return return
@@ -130,7 +127,7 @@ func (machine *Machine[WORD]) Execute(offset WORD) (err error) {
case EQ: case EQ:
right := machine.Pop() right := machine.Pop()
left := machine.Pop() left := machine.Pop()
equal := 0 equal := 0
if left == right { if left == right {
equal = 1 equal = 1
@@ -139,7 +136,7 @@ func (machine *Machine[WORD]) Execute(offset WORD) (err error) {
case GT: case GT:
right := machine.Pop() right := machine.Pop()
left := machine.Pop() left := machine.Pop()
greater := 0 greater := 0
if left > right { if left > right {
greater = 1 greater = 1
@@ -148,7 +145,7 @@ func (machine *Machine[WORD]) Execute(offset WORD) (err error) {
case LT: case LT:
right := machine.Pop() right := machine.Pop()
left := machine.Pop() left := machine.Pop()
less := 0 less := 0
if left < right { if left < right {
less = 1 less = 1
@@ -157,7 +154,7 @@ func (machine *Machine[WORD]) Execute(offset WORD) (err error) {
case NEQ: case NEQ:
right := machine.Pop() right := machine.Pop()
left := machine.Pop() left := machine.Pop()
notEqual := 0 notEqual := 0
if left != right { if left != right {
notEqual = 1 notEqual = 1
@@ -166,7 +163,7 @@ func (machine *Machine[WORD]) Execute(offset WORD) (err error) {
case MOD: case MOD:
right := machine.Pop() right := machine.Pop()
left := machine.Pop() left := machine.Pop()
machine.Push(left % right) machine.Push(left % right)
case HALT: case HALT:
@@ -202,7 +199,7 @@ func (machine *Machine[WORD]) Execute(offset WORD) (err error) {
// Instruction returns the current instruction in program memory. // Instruction returns the current instruction in program memory.
func (machine *Machine[WORD]) instruction() (instruction WORD) { func (machine *Machine[WORD]) instruction() (instruction WORD) {
instruction = machine.Program[machine.counter] instruction = machine.program[machine.counter]
return return
} }
@@ -231,7 +228,7 @@ func (machine *Machine[WORD]) Pop() (word WORD) {
if int(machine.pointer) <= 0 || int(machine.pointer) >= len(machine.stack) { if int(machine.pointer) <= 0 || int(machine.pointer) >= len(machine.stack) {
return return
} }
word = machine.stack[machine.pointer] word = machine.stack[machine.pointer]
machine.pointer-- machine.pointer--
@@ -262,7 +259,7 @@ func (machine *Machine[WORD]) Poke(address WORD, word WORD) {
// Register registers a function at the specified ID. If there is already a // Register registers a function at the specified ID. If there is already a
// function registered at that ID, this method will return an error. // function registered at that ID, this method will return an error.
func (machine *Machine[WORD]) Register ( func (machine *Machine[WORD]) Register(
id WORD, id WORD,
function MachineFunction[WORD], function MachineFunction[WORD],
) ( ) (
@@ -291,6 +288,12 @@ func (machine *Machine[WORD]) Unregister(id WORD) {
delete(machine.functions, id) delete(machine.functions, id)
} }
// LoadProgram loads the contents of program into the machine's program memory.
func (machine *Machine[WORD]) LoadProgram(program []WORD) {
machine.program = make([]WORD, len(program))
copy(machine.program, program)
}
// LoadMemory loads the contents of block into the machine's memory. // LoadMemory loads the contents of block into the machine's memory.
func (machine *Machine[WORD]) LoadMemory(block []WORD) { func (machine *Machine[WORD]) LoadMemory(block []WORD) {
machine.block = make([]WORD, len(block)) machine.block = make([]WORD, len(block))

View File

@@ -2,14 +2,15 @@ package creature
import "testing" import "testing"
func runMachineTest ( func runMachineTest(
program []int, program []int,
memory []int, memory []int,
test *testing.T, test *testing.T,
) ( ) (
machine *Machine[int], machine *Machine[int],
) { ) {
machine = &Machine[int] { Program: program } machine = &Machine[int]{}
machine.LoadProgram(program)
if memory != nil { if memory != nil {
machine.LoadMemory(memory) machine.LoadMemory(memory)
} }
@@ -24,12 +25,12 @@ func runMachineTest (
} }
func TestPush(test *testing.T) { func TestPush(test *testing.T) {
machine := runMachineTest ([]int { machine := runMachineTest([]int{
PUSH, 3, PUSH, 3,
POP, POP,
PUSH, 654, PUSH, 654,
}, nil, test) }, nil, test)
result := machine.Pop() result := machine.Pop()
test.Log("popped:", result) test.Log("popped:", result)
@@ -40,23 +41,23 @@ func TestPush(test *testing.T) {
} }
func TestArithmetic(test *testing.T) { func TestArithmetic(test *testing.T) {
machine := runMachineTest([]int { machine := runMachineTest([]int{
PUSH, 3, PUSH, 3,
PUSH, 2, PUSH, 2,
ADD, ADD,
PUSH, 10, PUSH, 10,
PUSH, 4, PUSH, 4,
SUB, SUB,
PUSH, 2, PUSH, 2,
PUSH, 7, PUSH, 7,
MUL, MUL,
PUSH, 12, PUSH, 12,
PUSH, 3, PUSH, 3,
DIV, DIV,
PUSH, 8, PUSH, 8,
PUSH, 6, PUSH, 6,
MOD, MOD,
@@ -101,23 +102,22 @@ func TestArithmetic(test *testing.T) {
} }
func TestComparison(test *testing.T) { func TestComparison(test *testing.T) {
machine := runMachineTest([]int { machine := runMachineTest([]int{
PUSH, 6, PUSH, 6,
PUSH, 6, PUSH, 6,
EQ, EQ,
PUSH, 324, PUSH, 324,
PUSH, 4, PUSH, 4,
GT, GT,
PUSH, 4, PUSH, 4,
PUSH, 324, PUSH, 324,
LT, LT,
PUSH, 54, PUSH, 54,
PUSH, 6, PUSH, 6,
NEQ, NEQ,
}, nil, test) }, nil, test)
neqResult := machine.Pop() neqResult := machine.Pop()
@@ -152,7 +152,7 @@ func TestComparison(test *testing.T) {
} }
func TestPeekPoke(test *testing.T) { func TestPeekPoke(test *testing.T) {
machine := runMachineTest ([]int { machine := runMachineTest([]int{
PUSH, 0, PUSH, 0,
PEEK, PEEK,
@@ -162,14 +162,14 @@ func TestPeekPoke(test *testing.T) {
PUSH, 1, PUSH, 1,
PEEK, PEEK,
}, []int { }, []int{
632, 632,
13, 13,
}, test) }, test)
secondResult := machine.Pop() secondResult := machine.Pop()
firstResult := machine.Pop() firstResult := machine.Pop()
test.Log("first:", firstResult) test.Log("first:", firstResult)
test.Log("second:", secondResult) test.Log("second:", secondResult)
@@ -185,12 +185,12 @@ func TestPeekPoke(test *testing.T) {
} }
func TestHalt(test *testing.T) { func TestHalt(test *testing.T) {
machine := runMachineTest ([]int { machine := runMachineTest([]int{
PUSH, 32, PUSH, 32,
HALT, HALT,
PUSH, 3, PUSH, 3,
}, nil, test) }, nil, test)
result := machine.Pop() result := machine.Pop()
test.Log("popped:", result) test.Log("popped:", result)
@@ -201,11 +201,11 @@ func TestHalt(test *testing.T) {
} }
func TestJump(test *testing.T) { func TestJump(test *testing.T) {
machine := runMachineTest ([]int { machine := runMachineTest([]int{
PUSH, 1, PUSH, 1,
PUSH, 8, PUSH, 8,
JMP, JMP,
PUSH, 3, PUSH, 3,
HALT, HALT,
@@ -218,7 +218,7 @@ func TestJump(test *testing.T) {
PUSH, 5, PUSH, 5,
}, nil, test) }, nil, test)
result := machine.Pop() result := machine.Pop()
test.Log("popped:", result) test.Log("popped:", result)
@@ -230,7 +230,8 @@ func TestJump(test *testing.T) {
func TestRegister(test *testing.T) { func TestRegister(test *testing.T) {
output := "" output := ""
machine := &Machine[int] { Program: []int { machine := &Machine[int]{}
machine.LoadProgram([]int{
PUSH, int('h'), PUSH, int('h'),
PUSH, 4, PUSH, 4,
CAL, CAL,
@@ -258,7 +259,7 @@ func TestRegister(test *testing.T) {
PUSH, int('!'), PUSH, int('!'),
PUSH, 4, PUSH, 4,
CAL, CAL,
}} })
machine.Register(4, func(machine *Machine[int]) (stop bool) { machine.Register(4, func(machine *Machine[int]) (stop bool) {
output += string(rune(machine.Pop())) output += string(rune(machine.Pop()))
return return

View File

@@ -1,3 +1,4 @@
//go:build ignore
// +build ignore // +build ignore
package main package main
@@ -5,11 +6,12 @@ package main
import "os" import "os"
import cre "git.tebibyte.media/sashakoshka/creature" import cre "git.tebibyte.media/sashakoshka/creature"
func main () { func main() {
// this is a simple echo program. it will take in input indefinetly and // this is a simple echo program. it will take in input indefinetly and
// repeat it. due to line buffering in the terminal however, it will // repeat it. due to line buffering in the terminal however, it will
// only print output once you have pressed enter. // only print output once you have pressed enter.
machine := cre.Machine[int] { Program: []int { machine := cre.Machine[int]{}
machine.LoadProgram([]int{
cre.PUSH, 0, cre.PUSH, 0,
cre.CAL, cre.CAL,
@@ -19,22 +21,24 @@ func main () {
cre.PUSH, 1, cre.PUSH, 1,
cre.PUSH, 0, cre.PUSH, 0,
cre.JMP, cre.JMP,
}} })
machine.Register (0, read) machine.Register(0, read)
machine.Register (1, write) machine.Register(1, write)
err := machine.Execute(0) err := machine.Execute(0)
if err != nil { panic(err.Error()) } if err != nil {
panic(err.Error())
}
} }
func read (machine *cre.Machine[int]) (stop bool) { func read(machine *cre.Machine[int]) (stop bool) {
ch := []byte { 0 } ch := []byte{0}
os.Stdin.Read(ch) os.Stdin.Read(ch)
machine.Push(int(ch[0])) machine.Push(int(ch[0]))
return return
} }
func write (machine *cre.Machine[int]) (stop bool) { func write(machine *cre.Machine[int]) (stop bool) {
print(string(rune(machine.Pop()))) print(string(rune(machine.Pop())))
return return
} }

View File

@@ -1,3 +1,4 @@
//go:build ignore
// +build ignore // +build ignore
package main package main
@@ -5,7 +6,7 @@ package main
import "os" import "os"
import cre "git.tebibyte.media/sashakoshka/creature" import cre "git.tebibyte.media/sashakoshka/creature"
func main () { func main() {
// this is an "ai psychiatrist" program. when it starts, it asks the // this is an "ai psychiatrist" program. when it starts, it asks the
// user "What brings you in today?", and no matter what the user types, // user "What brings you in today?", and no matter what the user types,
// the program will respond with "And how does that make you feel?". // the program will respond with "And how does that make you feel?".
@@ -13,14 +14,15 @@ func main () {
// point. // point.
// constant address values // constant address values
x := 0 x := 0
introStart := 1 introStart := 1
introLoopStart := 5 introLoopStart := 5
readLoopStart := 36 readLoopStart := 36
responseStart := 71 responseStart := 71
responseLoopStart := 50 responseLoopStart := 50
machine := cre.Machine[int] { Program: []int { machine := cre.Machine[int]{}
machine.LoadProgram([]int{
// reset x // reset x
cre.PUSH, introStart, cre.PUSH, introStart,
cre.PUSH, x, cre.PUSH, x,
@@ -37,7 +39,7 @@ func main () {
cre.EQ, cre.EQ,
cre.PUSH, readLoopStart, cre.PUSH, readLoopStart,
cre.JMP, cre.JMP,
// get char // get char
cre.PUSH, x, cre.PUSH, x,
cre.PEEK, cre.PEEK,
@@ -86,7 +88,7 @@ func main () {
cre.EQ, cre.EQ,
cre.PUSH, readLoopStart, cre.PUSH, readLoopStart,
cre.JMP, cre.JMP,
// get char // get char
cre.PUSH, x, cre.PUSH, x,
cre.PEEK, cre.PEEK,
@@ -108,35 +110,37 @@ func main () {
cre.PUSH, 1, cre.PUSH, 1,
cre.PUSH, responseLoopStart, cre.PUSH, responseLoopStart,
cre.JMP, cre.JMP,
}} })
stringData := []byte ( stringData := []byte(
"\x00" + "\x00" +
"== Artificial Intelligence Psychiatrist ==\n" + "== Artificial Intelligence Psychiatrist ==\n" +
"What brings you in today?\n\x00" + "What brings you in today?\n\x00" +
"And how does that make you feel?\n\x00") "And how does that make you feel?\n\x00")
block := make([]int, len(stringData)) block := make([]int, len(stringData))
for index, char := range stringData { for index, char := range stringData {
block[index] = int(char) block[index] = int(char)
} }
machine.LoadMemory(block) machine.LoadMemory(block)
machine.Register (0, read) machine.Register(0, read)
machine.Register (1, write) machine.Register(1, write)
err := machine.Execute(0) err := machine.Execute(0)
if err != nil { panic(err.Error()) } if err != nil {
panic(err.Error())
}
} }
func read (machine *cre.Machine[int]) (stop bool) { func read(machine *cre.Machine[int]) (stop bool) {
ch := []byte { 0 } ch := []byte{0}
os.Stdin.Read(ch) os.Stdin.Read(ch)
machine.Push(int(ch[0])) machine.Push(int(ch[0]))
return return
} }
func write (machine *cre.Machine[int]) (stop bool) { func write(machine *cre.Machine[int]) (stop bool) {
print(string(rune(machine.Pop()))) print(string(rune(machine.Pop())))
return return
} }

2
go.mod
View File

@@ -1,3 +1,3 @@
module git.tebibyte.media/sashakoshka/creature module git.tebibyte.media/sashakoshka/creature
go 1.19 go 1.18