package text import ( "encoding/binary" "fmt" "jinx/pkg/lang/vm/code" "jinx/pkg/libs/rangemap" "math" "strconv" "strings" ) type Decompiler struct { c code.Code pcToLine rangemap.RangeMap[int] generatePCs bool } func NewDecompiler(c code.Code, generatePCs bool) *Decompiler { return &Decompiler{ c: c, pcToLine: rangemap.New[int](), generatePCs: generatePCs, } } func (d *Decompiler) Decompile() string { lines := make([]string, 0) bc := d.c.Code() longestPcStringLen := len(strconv.FormatInt(int64(len(bc)), 10)) for len(bc) != 0 { startingPc := len(d.c.Code()) - len(bc) line, rest := d.decompileInstruction(bc) bc = rest d.pcToLine.AppendToLast(d.c.Len()-len(bc), len(lines)) if d.generatePCs { line = fmt.Sprintf("%*d: %s", longestPcStringLen, startingPc, line) } lines = append(lines, line) } return strings.Join(lines, "\n") } func (d *Decompiler) decompileInstruction(bc code.Raw) (string, code.Raw) { op := code.Op(bc[0]) opString := OpToString(op) if opString == "unknown" { return fmt.Sprintf("unknown(%x)", bc[0]), bc[1:] } switch op { // Operations that take no arguments. case code.OpNop, code.OpHalt, code.OpPushTrue, code.OpPushFalse, code.OpPushNull, code.OpPushArray, code.OpPushObject, code.OpAnchorType, code.OpAdd, code.OpSub, code.OpMul, code.OpDiv, code.OpMod, code.OpEq, code.OpLt, code.OpGt, code.OpLte, code.OpGte, code.OpIndex, code.OpSetAtIndex, code.OpRet, code.OpTempArrLen, code.OpTempArrPush: return opString, bc[1:] // Operations that take an int. case code.OpPushInt, code.OpDrop, code.OpGetLocal, code.OpSetLocal, code.OpGetEnv, code.OpSetEnv, code.OpAddToEnv, code.OpSetArgCount, code.OpCall: i, rest := d.decompileInt(bc[1:]) return fmt.Sprintf("%s %s", opString, i), rest // Operations that take a float. case code.OpPushFloat: f, rest := d.decompileFloat(bc[1:]) return fmt.Sprintf("%s %s", opString, f), rest // Operations that take a string. case code.OpPushString, code.OpPushType, code.OpAddGlobal, code.OpGetGlobal, code.OpSetGlobal, code.OpSetMember, code.OpGetMember: s, rest := d.decompileString(bc[1:]) return fmt.Sprintf("%s %s", opString, s), rest // Operations that take a pc, belonging to a function. case code.OpPushFunction: // TODO: Add function labels to output and give them names. fallthrough // Operations that take a pc, belonging to a non-call jump. (when branching) case code.OpJmp, code.OpJt, code.OpJf: // TODO: Add jump labels to output and give them names. i, rest := d.decompileInt(bc[1:]) return fmt.Sprintf("%s @%s", opString, i), rest } panic("decompiler can't decompile op: " + opString) } func (d *Decompiler) decompileInt(bc code.Raw) (string, code.Raw) { i := binary.LittleEndian.Uint64(bc[:8]) return strconv.FormatInt(int64(i), 10), bc[8:] } func (d *Decompiler) decompileFloat(bc code.Raw) (string, code.Raw) { i := binary.LittleEndian.Uint64(bc[:8]) f := math.Float64frombits(i) return strconv.FormatFloat(f, 'f', -1, 64), bc[8:] } func (d *Decompiler) decompileString(bc code.Raw) (string, code.Raw) { var buf strings.Builder buf.WriteString("\"") end := 0 for i, b := range bc { if b == 0 { end = i break } buf.WriteByte(b) } buf.WriteString("\"") return buf.String(), bc[end+1:] }