restructure project
Some checks failed
Build / build (push) Failing after 5m21s
Publish Image / publish (push) Failing after 32s
Test / build (push) Failing after 5m8s

This commit is contained in:
Chuck Smith
2024-03-28 16:20:09 -04:00
parent 362138ff2e
commit fc6ceee02c
93 changed files with 479 additions and 194 deletions

222
internal/code/code.go Normal file
View File

@@ -0,0 +1,222 @@
package code
import (
"bytes"
"encoding/binary"
"fmt"
)
type Instructions []byte
type Opcode byte
func (o Opcode) String() string {
def, err := Lookup(byte(o))
if err != nil {
return ""
}
return def.Name
}
const (
OpConstant Opcode = iota
OpAdd
OpPop
OpSub
OpMul
OpDiv
OpMod
OpOr
OpAnd
OpNot
OpBitwiseOR
OpBitwiseXOR
OpBitwiseAND
OpBitwiseNOT
OpLeftShift
OpRightShift
OpTrue
OpFalse
OpEqual
OpNotEqual
OpGreaterThan
OpGreaterThanEqual
OpMinus
OpJumpNotTruthy
OpJump
OpNull
OpAssignGlobal
OpAssignLocal
OpGetGlobal
OpSetGlobal
OpArray
OpHash
OpGetItem
OpSetItem
OpCall
OpReturn
OpGetLocal
OpSetLocal
OpGetBuiltin
OpClosure
OpGetFree
OpCurrentClosure
OpLoadModule
)
type Definition struct {
Name string
OperandWidths []int
}
var definitions = map[Opcode]*Definition{
OpConstant: {"OpConstant", []int{2}},
OpAssignGlobal: {"OpAssignGlobal", []int{2}},
OpAssignLocal: {"OpAssignLocal", []int{1}},
OpAdd: {"OpAdd", []int{}},
OpPop: {"OpPop", []int{}},
OpSub: {"OpSub", []int{}},
OpMul: {"OpMul", []int{}},
OpDiv: {"OpDiv", []int{}},
OpMod: {"OpMod", []int{}},
OpOr: {"OpOr", []int{}},
OpAnd: {"OpAnd", []int{}},
OpNot: {"OpNot", []int{}},
OpBitwiseOR: {"OpBitwiseOR", []int{}},
OpBitwiseXOR: {"OpBitwiseXOR", []int{}},
OpBitwiseAND: {"OpBitwiseAND", []int{}},
OpBitwiseNOT: {"OpBitwiseNOT", []int{}},
OpLeftShift: {"OpLeftShift", []int{}},
OpRightShift: {"OpRightShift", []int{}},
OpTrue: {"OpTrue", []int{}},
OpFalse: {"OpFalse", []int{}},
OpEqual: {"OpEqual", []int{}},
OpNotEqual: {"OpNotEqual", []int{}},
OpGreaterThan: {"OpGreaterThan", []int{}},
OpGreaterThanEqual: {"OpGreaterThanEqual", []int{}},
OpMinus: {"OpMinus", []int{}},
OpJumpNotTruthy: {"OpJumpNotTruthy", []int{2}},
OpJump: {"OpJump", []int{2}},
OpNull: {"OpNull", []int{}},
OpGetGlobal: {"OpGetGlobal", []int{2}},
OpSetGlobal: {"OpSetGlobal", []int{2}},
OpArray: {"OpArray", []int{2}},
OpHash: {"OpHash", []int{2}},
OpGetItem: {"OpGetItem", []int{}},
OpSetItem: {"OpSetItem", []int{}},
OpCall: {"OpCall", []int{1}},
OpReturn: {"OpReturn", []int{}},
OpGetLocal: {"OpGetLocal", []int{1}},
OpSetLocal: {"OpSetLocal", []int{1}},
OpGetBuiltin: {"OpGetBuiltin", []int{1}},
OpClosure: {"OpClosure", []int{2, 1}},
OpGetFree: {"OpGetFree", []int{1}},
OpCurrentClosure: {"OpCurrentClosure", []int{}},
OpLoadModule: {"OpLoadModule", []int{}},
}
func Lookup(op byte) (*Definition, error) {
def, ok := definitions[Opcode(op)]
if !ok {
return nil, fmt.Errorf("opcode %d undefined", op)
}
return def, nil
}
func Make(op Opcode, operands ...int) []byte {
def, ok := definitions[op]
if !ok {
return []byte{}
}
instructions := 1
for _, w := range def.OperandWidths {
instructions += w
}
instruction := make([]byte, instructions)
instruction[0] = byte(op)
offset := 1
for i, o := range operands {
width := def.OperandWidths[i]
switch width {
case 2:
binary.BigEndian.PutUint16(instruction[offset:], uint16(o))
case 1:
instruction[offset] = byte(o)
}
offset += width
}
return instruction
}
func (ins Instructions) String() string {
var out bytes.Buffer
i := 0
for i < len(ins) {
def, err := Lookup(ins[i])
if err != nil {
fmt.Fprintf(&out, "ERROR: %s\n", err)
continue
}
operands, read := ReadOperands(def, ins[i+1:])
fmt.Fprintf(&out, "%04d %s\n", i, ins.fmtInstruction(def, operands))
i += 1 + read
}
return out.String()
}
func (ins Instructions) fmtInstruction(def *Definition, operands []int) string {
operandCount := len(def.OperandWidths)
if len(operands) != operandCount {
return fmt.Sprintf("ERROR: operand len %d does not match defined %d\n", len(operands), operandCount)
}
switch operandCount {
case 0:
return def.Name
case 1:
return fmt.Sprintf("%s %d", def.Name, operands[0])
case 2:
return fmt.Sprintf("%s %d %d", def.Name, operands[0], operands[1])
}
return fmt.Sprintf("ERROR: unhandled operandCount for %s\n", def.Name)
}
func ReadOperands(def *Definition, ins Instructions) ([]int, int) {
operands := make([]int, len(def.OperandWidths))
offset := 0
for i, width := range def.OperandWidths {
switch width {
case 2:
operands[i] = int(ReadUint16(ins[offset:]))
case 1:
operands[i] = int(ReadUint8(ins[offset:]))
}
offset += width
}
return operands, offset
}
func ReadUint16(ins Instructions) uint16 {
return binary.BigEndian.Uint16(ins)
}
func ReadUint8(ins Instructions) uint8 {
return ins[0]
}

View File

@@ -0,0 +1,88 @@
package code
import "testing"
func TestMake(t *testing.T) {
test := []struct {
op Opcode
operands []int
expected []byte
}{
{OpConstant, []int{65534}, []byte{byte(OpConstant), 255, 254}},
{OpAdd, []int{}, []byte{byte(OpAdd)}},
{OpGetLocal, []int{255}, []byte{byte(OpGetLocal), 255}},
{OpClosure, []int{65534, 255}, []byte{byte(OpClosure), 255, 254, 255}},
}
for _, tt := range test {
instructions := Make(tt.op, tt.operands...)
if len(instructions) != len(tt.expected) {
t.Errorf("instruction has wrong length. want=%d, got=%d", len(tt.expected), len(instructions))
}
for i, b := range tt.expected {
if instructions[i] != tt.expected[i] {
t.Errorf("wrong byte at pos %d. want=%d, got=%d", i, b, instructions[i])
}
}
}
}
func TestInstructions(t *testing.T) {
instructions := []Instructions{
Make(OpAdd),
Make(OpGetLocal, 1),
Make(OpConstant, 2),
Make(OpConstant, 65535),
Make(OpClosure, 65535, 255),
}
expected := `0000 OpAdd
0001 OpGetLocal 1
0003 OpConstant 2
0006 OpConstant 65535
0009 OpClosure 65535 255
`
concatted := Instructions{}
for _, ins := range instructions {
concatted = append(concatted, ins...)
}
if concatted.String() != expected {
t.Errorf("instructions wrong formatted.\nwant=%q\ngot=%q", expected, concatted.String())
}
}
func TestReadOperands(t *testing.T) {
tests := []struct {
op Opcode
operands []int
bytesRead int
}{
{OpConstant, []int{65535}, 2},
{OpGetLocal, []int{255}, 1},
{OpClosure, []int{65535, 255}, 3},
}
for _, tt := range tests {
instruction := Make(tt.op, tt.operands...)
def, err := Lookup(byte(tt.op))
if err != nil {
t.Fatalf("definition not found: %q\n", err)
}
operandsRead, n := ReadOperands(def, instruction[1:])
if n != tt.bytesRead {
t.Fatalf("n wrong. want=%d, got=%d", tt.bytesRead, n)
}
for i, want := range tt.operands {
if operandsRead[i] != want {
t.Errorf("operand wrong. want=%d, got=%d", want, operandsRead[i])
}
}
}
}