github.com/arnodel/golua@v0.0.0-20230215163904-e0b5347eaaa1/code/unit_builder.go (about) 1 package code 2 3 // A Label represent a location in the code. 4 type Label uint 5 6 // A Builder helps build a code Unit (in particular it calculates the offsets 7 // for jump instructions). 8 type Builder struct { 9 source string // identifies the source of the code 10 lines []int32 // lines in the source code corresponding to the opcodes 11 code []Opcode // opcodes emitted 12 jumpTo map[Label]int // destination locations for the labels 13 jumpFrom map[Label][]int // lists of locations for opcode that jump to a given label 14 constants []Constant // constants required for the code 15 } 16 17 // NewBuilder returns an empty Builder for the given source. 18 func NewBuilder(source string) *Builder { 19 return &Builder{ 20 source: source, 21 jumpTo: make(map[Label]int), 22 jumpFrom: make(map[Label][]int), 23 } 24 } 25 26 // Emit adds an opcode (associating it with a source code line). 27 func (c *Builder) Emit(opcode Opcode, line int) { 28 c.code = append(c.code, opcode) 29 c.lines = append(c.lines, int32(line)) 30 } 31 32 // EmitJump adds a jump opcode, jumping to the given label. The offset part of 33 // the opcode must be left as 0, it will be filled by the builder when the 34 // location of the label is known. 35 func (c *Builder) EmitJump(opcode Opcode, lbl Label, line int) { 36 jumpToAddr, ok := c.jumpTo[lbl] 37 addr := len(c.code) 38 if ok { 39 opcode = opcode.SetOffset(Offset(jumpToAddr - addr)) 40 } else { 41 c.jumpFrom[lbl] = append(c.jumpFrom[lbl], addr) 42 } 43 c.Emit(opcode, line) 44 } 45 46 // EmitLabel adds a label for the current location. It panics if called twice 47 // with the same label at different locations. 48 func (c *Builder) EmitLabel(lbl Label) { 49 addr := len(c.code) 50 if lblAddr, ok := c.jumpTo[lbl]; ok && lblAddr != addr { 51 panic("Label already emitted for a different location") 52 } 53 c.jumpTo[lbl] = addr 54 for _, jumpFromAddr := range c.jumpFrom[lbl] { 55 c.code[jumpFromAddr] = c.code[jumpFromAddr].SetOffset(Offset(addr - jumpFromAddr)) 56 } 57 delete(c.jumpFrom, lbl) 58 } 59 60 // Offset returns the current location. It must be called when all emitted jump 61 // labels have been resolved, otherwise it panice. 62 func (c *Builder) Offset() uint { 63 if len(c.jumpFrom) > 0 { 64 panic("Illegal offset") 65 } 66 c.jumpTo = make(map[Label]int) 67 return uint(len(c.code)) 68 } 69 70 // AddConstant adds a constant. 71 func (c *Builder) AddConstant(k Constant) { 72 c.constants = append(c.constants, k) 73 } 74 75 // GetUnit returns the build code Unit. 76 func (c *Builder) GetUnit() *Unit { 77 return &Unit{ 78 Source: c.source, 79 Code: c.code, 80 Lines: c.lines, 81 Constants: c.constants, 82 } 83 }