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 OpTrue OpFalse OpEqual OpNotEqual OpGreaterThan OpGreaterThanEqual OpMinus OpBang OpJumpNotTruthy OpJump OpNull OpAssignGlobal OpAssignLocal OpGetGlobal OpSetGlobal OpArray OpHash OpGetItem OpSetItem OpCall OpReturn OpGetLocal OpSetLocal OpGetBuiltin OpClosure OpGetFree OpCurrentClosure OpNoop ) 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{}}, OpTrue: {"OpTrue", []int{}}, OpFalse: {"OpFalse", []int{}}, OpEqual: {"OpEqual", []int{}}, OpNotEqual: {"OpNotEqual", []int{}}, OpGreaterThan: {"OpGreaterThan", []int{}}, OpGreaterThanEqual: {"OpGreaterThanEqual", []int{}}, OpMinus: {"OpMinus", []int{}}, OpBang: {"OpBang", []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{}}, OpNoop: {"OpNoop", []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] }