Skip to content

Instantly share code, notes, and snippets.

@nbness2
Last active September 20, 2018 10:57
Show Gist options
  • Save nbness2/ef7a5e57f0ff660e028a3c89dd2385b7 to your computer and use it in GitHub Desktop.
Save nbness2/ef7a5e57f0ff660e028a3c89dd2385b7 to your computer and use it in GitHub Desktop.
Bitfields
import java.math.BigInteger
import kotlin.IndexOutOfBoundsException
data class BitFieldInfo(val fieldName: String, val fieldSize: Int, val fieldValue: BigInteger=0.bi) {
init {
if (fieldValue.bitLength() > fieldSize) throw IndexOutOfBoundsException("$fieldValue (${fieldValue.bitLength()} bits) too big to fit in $fieldSize bits")
if (fieldValue < 0.bi) throw NotImplementedError("Signed numbers are not yet implemented for BitFields")
if (fieldSize < 1) throw IndexOutOfBoundsException("Can't have a negative amount of bits ($fieldName, $fieldSize)")
}
constructor(fieldName: String, fieldSize: Int, fieldValue: Byte): this(fieldName, fieldSize, fieldValue.toInt())
constructor(fieldName: String, fieldSize: Int, fieldValue: Short): this(fieldName, fieldSize, fieldValue.toInt())
constructor(fieldName: String, fieldSize: Int, fieldValue: Int): this(fieldName, fieldSize, fieldValue.bi)
constructor(fieldName: String, fieldSize: Int, fieldValue: Long): this(fieldName, fieldSize, fieldValue.bi)
constructor(fieldName: String, fieldSize: Int, fieldValue: Number): this(fieldName, fieldSize, fieldValue.toLong().bi)
}
infix fun String.size(other: Int) = BitFieldInfo(this, fieldSize=other)
infix fun BitFieldInfo.value(other: Number): BitFieldInfo = BitFieldInfo(this.fieldName, this.fieldSize, other)
infix fun BitFieldInfo.value(other: BigInteger): BitFieldInfo = BitFieldInfo(this.fieldName, this.fieldSize, other)
fun Boolean.toInt(): Int = if (this) 1 else 0
val Int.bi: BigInteger
get() = this.toBigInteger()
val Long.bi: BigInteger
get() = this.toBigInteger()
open class BitFields {
private infix fun List<Pair<String, Int>>.valueOf(other: String): Int? {
for (item in this)
if (item.first == other)
return item.second
return null
}
private var bitArray: BigInteger = 0.bi
val num: BigInteger
get() = bitArray
val bits: String
get() = this.bitArray.toString(2)
private val fieldSizes: List<Pair<String, Int>>
protected val fieldNames: List<String>
get() = this.fieldSizes.map{ it.first }
val length: Int
constructor(vararg bitFieldInfo: BitFieldInfo) {
var bitLength = 0
val fieldSizeList = mutableListOf<Pair<String, Int>>()
for (info in bitFieldInfo) {
if (bitFieldInfo.count{it.fieldName == info.fieldName} > 2)
throw ArrayStoreException("Cannot define field ${info.fieldName} more than once")
if (info.fieldSize < 1)
throw Exception("Cannot set a field (${info.fieldName}) to a size less than 1 (${info.fieldSize})")
fieldSizeList.add(info.fieldName to info.fieldSize)
this.bitArray = this.bitArray shl info.fieldSize
bitLength += info.fieldSize
}
this.length = bitLength
this.fieldSizes = fieldSizeList.toList()
for (info in bitFieldInfo) {
this[info.fieldName] = info.fieldValue
}
}
constructor(vararg bitFieldInfo: Pair<String, Int>) {
var bitLength = 0
val fieldSizeList = mutableListOf<Pair<String, Int>>()
for (info in bitFieldInfo) {
if (bitFieldInfo.count{it.first == info.first} > 2)
throw ArrayStoreException("Cannot define field ${info.first} more than once")
if (info.second < 1)
throw Exception("Cannot set a field (${info.first}) to a size less than 1 (${info.second})")
fieldSizeList.add(info)
this.bitArray = this.bitArray shl info.second
bitLength += info.second
}
this.length = bitLength
this.fieldSizes = fieldSizeList.toList()
for (info in bitFieldInfo) {
this[info.first] = info.second
}
}
private fun getPos(other: String): Int {
var currentPos = 0
for (field in this.fieldSizes) {
if (field.first == other)
return currentPos
currentPos += field.second
}
return -1
}
fun bitOn(bitIndex: Int) { this[bitIndex] = true}
fun bitOff(bitIndex: Int) { this[bitIndex] = false}
operator fun set(other: String, value: Number) { this[other] = value.toLong().bi }
operator fun set(other: String, value: BigInteger) {
val fieldSize: Int = this.fieldSizes valueOf other
?: throw java.lang.NullPointerException("'$other' is not a valid field name")
if (value < 0.bi)
throw NotImplementedError("Signed numbers are not yet implemented for BitFields")
if (value.bitLength() > fieldSize)
throw IndexOutOfBoundsException("Can't fit $value (${value.bitLength()} bits) in to $fieldSize bits.")
val fieldPos = this.getPos(other)
for (offset in 0 until fieldSize) {
if (value.testBit(offset))
this.bitArray = this.bitArray.setBit(fieldPos + offset)
else
this.bitArray = this.bitArray.clearBit(fieldPos + offset)
}
}
operator fun set(bitIndex: Int, value: Boolean) {
if (bitIndex < 0 || bitIndex >= this.bitArray.bitLength()) // Don't want any arbitrary extending of the bit array
throw IndexOutOfBoundsException("Index $bitIndex out of range")
if (value)
this.bitArray = this.bitArray.setBit(bitIndex)
else
this.bitArray = this.bitArray.clearBit(bitIndex)
}
operator fun get(other: String): BigInteger {
var result = 0.bi
val fieldSize: Int = this.fieldSizes valueOf other
?: throw java.lang.NullPointerException("'$other' is not a valid field name")
val fieldPos = this.getPos(other)
for (offset in 1 .. fieldSize) {
result += this.bitArray.testBit(fieldPos + fieldSize - offset).toInt().bi
if (offset < fieldSize)
result = result shl 1
}
return result
}
}
class BitFields:
show_bitsize = True
def __init__(self, **kwargs):
self.__field_sizes = []
self.__bit_array = 0
self.__length = 0
for field_name, field_values in kwargs.items():
if field_name in self.__field_sizes:
raise KeyError(f"Cannot define field '{field_name}' more than once")
if type(field_values) not in (tuple, list, int):
raise TypeError(f"Cannot take '{type(field_values).__qualname__}' as field value type")
if type(field_values) is int:
field_values = (field_values, 0)
if len(field_values) != 2:
raise AttributeError(f"Length of field_values must be 2 not '{len(field_values)}'")
if type(field_values[0]) is not int:
raise TypeError("Type of field_values[0] must be int")
if field_values[0] < 1:
raise ValueError(f"Cannot set a field to a negative size ({field_values[0]})")
self.__field_sizes.append((field_name, field_values[0]))
self.__bit_array <<= field_values[0]
self.__length += field_values[0]
self.__field_sizes = tuple(self.__field_sizes)
for field_name, field_values in kwargs.items():
if type(field_values) in (tuple, list):
self[field_name] = field_values[1]
else:
self[field_name] = 0
def __contains__(self, item):
for name, size in self.__field_sizes:
if name == item:
return True
return False
def __str__(self):
retstr = [f"{self.__class__.__qualname__}("]
for field_name, field_size in self.__field_sizes:
retstr.append(f"{field_name}{f'[{field_size}]' if self.show_bitsize else ''}={self[field_name]}, ")
return "".join(retstr)[:-2]+")"
def __len__(self):
return self.__length
def __getitem__(self, item):
if type(item) is int:
return (self.__bit_array & (1 << item)) != 0
elif type(item) is str:
if item not in self:
raise KeyError(f"field {item} not stored in this BitArray")
result = 0
bit_pos = self.get_field_position(item)
bit_size = self.get_field_size(item)
for offset in range(bit_size):
result += 1 * self[bit_pos + offset]
if offset < bit_size-1:
result <<= 1
return result
else:
raise TypeError(f"Cannot index bitfield with type {type(item).__qualname__}")
def __setitem__(self, key, value):
if type(key) not in (str, int):
raise TypeError(f"Cannot index bitfield with type '{type(key).__qualname__}'")
if type(key) is int:
self.set_bit(key, value)
return
field_pos = self.get_field_position(key)
field_size = self.get_field_size(key)
if not BitFields.data_fits_in_bits(value, field_size):
raise ValueError(f"size of ({value}) too big for field {key}({field_size})")
data = BitFields.format_data(value, field_size)
for offset in range(field_size):
self[field_pos + offset] = data[offset]
def full_value(self):
return self.__bit_array
def get_field_size(self, field_name):
for name, field_size in self.__field_sizes:
if field_name == name:
return field_size
def get_field_position(self, field_name):
if field_name not in self:
raise KeyError(f"'{field_name}' is not a valid field name")
bit_pos = 0
for name, size in self.__field_sizes:
if name == field_name:
return bit_pos
bit_pos += size
return bit_pos
def set_bit(self, bit_id, value):
if type(bit_id) is not int:
raise TypeError(f"Can only index byte with int, not {type(bit_id)}")
value = bool(value)
if value:
self.bit_on(bit_id)
return
self.bit_off(bit_id)
def set_bits(self, pairs):
for bit_id, bit_value in pairs:
self.set_bit(bit_id, bit_value)
def bit_on(self, bit_id):
if bit_id > len(self):
raise IndexError(f"index {bit_id} out of range for len {len(self)}")
self.__bit_array = self.__bit_array | (1 << bit_id)
def bit_off(self, bit_id):
if bit_id > len(self):
raise IndexError(f"index {bit_id} out of range for len {len(self)}")
self.__bit_array = self.__bit_array & ~(1 << bit_id)
def clear_field(self, key):
self[key] = 0
@staticmethod
def data_fits_in_bits(data, bit_size):
if type(data) in (list, tuple):
if len(data) <= bit_size:
return True
else:
return False
elif type(data) in (int, bool):
if len(bin(data & 0xFF)[2:]) <= bit_size:
return True
else:
raise ValueError(f"Cannot measure bit size of '{type(data)}'")
return False
@staticmethod
def format_data(data, bit_size):
if type(data) in (list, tuple):
if len(data) > bit_size:
raise IndexError(f"Cannot fit {len(data)} in to {bit_size} bits")
data = list(map(bool, data))
elif type(data) is bool:
data = [data]
elif type(data) is int:
if data.bit_length() > bit_size:
raise IndexError(f"Cannot fit int ({data}) in to {bit_size} bits")
data = [BitFields.get_bit_value(data, data.bit_length() - bit_id - 1) for bit_id in range(data.bit_length())]
else:
raise ValueError(f"Cannot fit '{type(data)}' data to bits")
while len(data) < bit_size:
data.insert(0, False)
return data
@staticmethod
def get_bit_value(number, bit_index):
if bit_index > number.bit_length()-1:
raise IndexError(f"the number ({number}) does not have {bit_index} bits!")
elif bit_index < 0:
raise IndexError(f"any number ever does not have a negative number of bits!")
return (number & (1 << bit_index)) != 0
open class Item(itemId: Int, itemAmount: Int, vararg extraFields: BitFieldInfo): BitFields("itemId" size 16 value itemId, "itemAmount" size 31 value itemAmount, *extraFields) {
var itemId: Int
get() = this["itemId"].toInt()
set(other: Int) { this["itemId"] = other }
var itemAmount: Int
get() = this["itemAmount"].toInt()
set(other: Int) { this["itemAmount"] = other }
override fun toString(): String {
val returnString = StringBuilder("Item(")
for (fieldName in this.fieldNames) {
returnString.append("$fieldName=${this[fieldName]}")
if (fieldName == this.fieldNames.last()) {
returnString.append(")")
return returnString.toString()
}
returnString.append(", ")
}
return returnString.toString()
}
}
class ItemWithCharges(itemId: Int, itemAmount: Int, maxCharges: Int, vararg extraFields: BitFieldInfo): Item(itemId, itemAmount, "currentCharges" size 24, "maxCharges" size 16 value maxCharges, *extraFields) {
var charges: Int
get() = this["currentCharges"].toInt()
set(other) {
if (other > this["maxCharges"].toInt())
throw ArrayIndexOutOfBoundsException("You cannot set charges for this item over ${this["maxCharges"]}")
this["currentCharges"] = other
}
}
fun main(args: Array<String>) {
val item1 = Item(4151, 1)
val item2 = ItemWithCharges(1337, 1, 32767)
println("$item1")
println("$item2")
item1.itemId = 19784
item1.itemAmount = 2_000_000_000
item2.itemId = 14484
item2.charges = 32767
println("$item1")
println("$item2")
}
from BitFields import BitFields
class InvalidParamAlias(BaseException):
pass
class Item(BitFields):
item_id_size = 16
item_amount_size = 31
show_bitsize = False
parameters = {
"item_id": ("id", "itemid", "iid"),
"item_amount": ("ia", "amount", "itemamount", "quantity"),
}
def __init__(self, item_id, item_amount=1, **kwargs):
super().__init__(item_id=(Item.item_id_size, item_id), item_amount=(Item.item_amount_size, item_amount), **kwargs)
@property
def item_id(self):
return self["item_id"]
@item_id.setter
def item_id(self, new_id):
self["item_id"] = new_id
@property
def item_amount(self):
return self["item_amount"]
@item_amount.setter
def item_amount(self, new_amount):
self["item_amount"] = new_amount
class ItemWithCharges(Item):
charges_size = 16
def __init__(self, item_id, item_amount=1, max_charges=32767, current_charges=0, **kwargs):
super().__init__(item_id=item_id, item_amount=item_amount, current_charges=(ItemWithCharges.charges_size, current_charges), max_charges=(ItemWithCharges.charges_size, max_charges), **kwargs)
@property
def item_charges(self):
return self["current_charges"]
@item_charges.setter
def item_charges(self, new_charges):
if new_charges > self["max_charges"]:
raise ValueError(f"You cannot set charges for this item over {self['max_charges']}")
self["current_charges"] = new_charges
if __name__ == "__main__":
item_1 = Item(item_id=4_151)
item_2 = ItemWithCharges(1337, current_charges=0)
print(item_1)
print(item_2)
item_1.item_id = 19784
item_1.item_amount = 2_000_000_000
item_2.item_id = 14484
item_2.item_charges = 32767
print(item_1)
print(item_2)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment