package code // Author: simon // Author: ynwdlxm@163.com // Date: 2022/10/19 13:12 // Desc: bytecode import ( "bytes" "encoding/binary" "fmt" ) type Instructions []byte // String MiniDisassembler func (ins Instructions) String() string { var out bytes.Buffer i := 0 for i < len(ins) { def, err := Lookup(ins[i]) if err != nil { _, err := fmt.Fprintf(&out, "ERROR: %s\n", err) if err != nil { return "" } continue } operands, read := ReadOperands(def, ins[i+1:]) _, err = fmt.Fprintf(&out, "%04d %s\n", i, ins.fmtInstructions(def, operands)) if err != nil { return "" } i += 1 + read } return out.String() } func (ins Instructions) fmtInstructions(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]) } return fmt.Sprintf("ERROR: unhandled operandCount for %s\n", def.Name) } type Opcode byte const ( OpConstant Opcode = iota OpAdd // + OpSub // - OpMul // * OpDiv // / OpPop OpTrue // true OpFalse // false OpEqual // == OpNotEqual // != OpGreaterThan // > OpMinus OpBang ) // Definition For debugging and testing purposes // // it’s handy being able to look up how many operands an opcode has and what its human-readable name is. // In order to achieve that, we’ll add proper definitions and some tooling // // Name helps to make an opcode readable // OperandWidths contains the number of bytes each operand takes up type Definition struct { Name string OperandWidths []int } var definitions = map[Opcode]*Definition{ OpConstant: {"OpConstant", []int{2}}, OpAdd: {"OpAdd", []int{}}, // doesn't have any operands OpSub: {"OpSub", []int{}}, OpMul: {"OpMul", []int{}}, OpDiv: {"OpDiv", []int{}}, OpPop: {"OpPop", []int{}}, OpTrue: {"OpTrue", []int{}}, OpFalse: {"OpFalse", []int{}}, OpEqual: {"OpEqual", []int{}}, OpNotEqual: {"OpNotEqual", []int{}}, OpGreaterThan: {"OpGreaterThan", []int{}}, OpMinus: {"OpMinus", []int{}}, OpBang: {"OpBang", []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{} } instructionLen := 1 for _, w := range def.OperandWidths { instructionLen += w } instruction := make([]byte, instructionLen) // allocate the byte slice 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)) } offset += width } return instruction } // ReadOperands reverses everything Make did 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:])) } offset += width } return operands, offset } func ReadUint16(ins Instructions) uint16 { return binary.BigEndian.Uint16(ins) }