Last active
September 20, 2018 10:57
-
-
Save nbness2/ef7a5e57f0ff660e028a3c89dd2385b7 to your computer and use it in GitHub Desktop.
Bitfields
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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