code: func (bc Bytecode) Tokens() ([]string, error) {
  	// Overestimate of capacity in the presence of pushes
  	tokens := make([]string, 0, len(bc))
  
  	for i := 0; i < len(bc); i++ {
  		op, ok := asm.GetOpCode(bc[i])
  		if !ok {
  			return tokens, fmt.Errorf("did not recognise byte %#x at position %v as an OpCode:\n %s",
  				bc[i], i, lexingPositionString(bc, i, tokens))
  		}
  		pushes := op.Pushes()
  		tokens = append(tokens, op.Name())
  		if pushes > 0 {
  			// This is a PUSH<N> OpCode so consume N bytes from the input, render them as hex, and skip to next OpCode
  			if i+pushes >= len(bc) {
  				return tokens, fmt.Errorf("token %v of input is %s but not enough input remains to push %v: %s",
  					i, op.Name(), pushes, lexingPositionString(bc, i, tokens))
  			}
  			pushedBytes := bc[i+1 : i+pushes+1]
  			tokens = append(tokens, fmt.Sprintf("0x%s", pushedBytes))
  			i += pushes
  		}
  	}
  	return tokens, nil
  }
docs: // Tokenises the bytecode into opcodes and values