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  }