diff options
Diffstat (limited to 'src/bitflags.c')
-rw-r--r-- | src/bitflags.c | 235 |
1 files changed, 235 insertions, 0 deletions
diff --git a/src/bitflags.c b/src/bitflags.c new file mode 100644 index 0000000..89a88b7 --- /dev/null +++ b/src/bitflags.c @@ -0,0 +1,235 @@ +/// Bitflags module. +// The bitflag object makes it easy to manipulate flags in a bitmask. +// +// It has metamethods that do the hard work, adding flags sets them, substracting +// unsets them. Comparing flags checks if all flags in the second set are also set +// in the first set. The `has` method checks if all flags in the second set are +// also set in the first set, but behaves slightly different. +// +// Indexing allows checking values or setting them by bit index (eg. 0-7 for flags +// in the first byte). +// +// _NOTE_: unavailable flags (eg. Windows flags on a Posix system) should not be +// omitted, but be assigned a value of 0. This is because the `has` method will +// return `false` if the flags are checked and the value is 0. +// +// See `system.bitflag` (the constructor) for extensive examples on usage. +// @classmod bitflags +#include "bitflags.h" + +#define BITFLAGS_MT_NAME "LuaSystem.BitFlags" + +typedef struct { + LSBF_BITFLAG flags; +} LS_BitFlags; + +/// Bit flags. +// Bitflag objects can be used to easily manipulate and compare bit flags. +// These are primarily for use with the terminal functions, but can be used +// in other places as well. +// @section bitflags + + +// pushes a new LS_BitFlags object with the given value onto the stack +void lsbf_pushbitflags(lua_State *L, LSBF_BITFLAG value) { + LS_BitFlags *obj = (LS_BitFlags *)lua_newuserdata(L, sizeof(LS_BitFlags)); + if (!obj) luaL_error(L, "Memory allocation failed"); + luaL_getmetatable(L, BITFLAGS_MT_NAME); + lua_setmetatable(L, -2); + obj->flags = value; +} + +// gets the LS_BitFlags value at the given index. Returns a Lua error if it is not +// a LS_BitFlags object. +LSBF_BITFLAG lsbf_checkbitflags(lua_State *L, int index) { + LS_BitFlags *obj = (LS_BitFlags *)luaL_checkudata(L, index, BITFLAGS_MT_NAME); + return obj->flags; +} + +/*** +Creates a new bitflag object from the given value. +@function system.bitflag +@tparam[opt=0] number value the value to create the bitflag object from. +@treturn bitflag bitflag object with the given values set. +@usage +local sys = require 'system' +local flags = sys.bitflag(2) -- b0010 + +-- get state of individual bits +print(flags[0]) -- false +print(flags[1]) -- true + +-- set individual bits +flags[0] = true -- b0011 +print(flags:value()) -- 3 +print(flags) -- "bitflags: 3" + +-- adding flags (bitwise OR) +local flags1 = sys.bitflag(1) -- b0001 +local flags2 = sys.bitflag(2) -- b0010 +local flags3 = flags1 + flags2 -- b0011 + +-- substracting flags (bitwise AND NOT) +print(flags3:value()) -- 3 +flag3 = flag3 - flag3 -- b0000 +print(flags3:value()) -- 0 + +-- comparing flags +local flags4 = sys.bitflag(7) -- b0111 +local flags5 = sys.bitflag(255) -- b11111111 +print(flags5 >= flags4) -- true, all bits in flags4 are set in flags5 + +-- comparing with 0 flags: comparison and `has` behave differently +local flags6 = sys.bitflag(0) -- b0000 +local flags7 = sys.bitflag(1) -- b0001 +print(flags6 < flags7) -- true, flags6 is a subset of flags7 +print(flags7:has(flags6)) -- false, flags6 is not set in flags7 +*/ +static int lsbf_new(lua_State *L) { + LSBF_BITFLAG flags = 0; + if (lua_gettop(L) > 0) { + flags = luaL_checkinteger(L, 1); + } + lsbf_pushbitflags(L, flags); + return 1; +} + +/*** +Retrieves the numeric value of the bitflag object. +@function bitflag:value +@treturn number the numeric value of the bitflags. +@usage +local sys = require 'system' +local flags = sys.bitflag() -- b0000 +flags[0] = true -- b0001 +flags[2] = true -- b0101 +print(flags:value()) -- 5 +*/ +static int lsbf_value(lua_State *L) { + lua_pushinteger(L, lsbf_checkbitflags(L, 1)); + return 1; +} + +static int lsbf_tostring(lua_State *L) { + lua_pushfstring(L, "bitflags: %d", lsbf_checkbitflags(L, 1)); + return 1; +} + +static int lsbf_add(lua_State *L) { + lsbf_pushbitflags(L, lsbf_checkbitflags(L, 1) | lsbf_checkbitflags(L, 2)); + return 1; +} + +static int lsbf_sub(lua_State *L) { + lsbf_pushbitflags(L, lsbf_checkbitflags(L, 1) & ~lsbf_checkbitflags(L, 2)); + return 1; +} + +static int lsbf_eq(lua_State *L) { + lua_pushboolean(L, lsbf_checkbitflags(L, 1) == lsbf_checkbitflags(L, 2)); + return 1; +} + +static int lsbf_le(lua_State *L) { + LSBF_BITFLAG a = lsbf_checkbitflags(L, 1); + LSBF_BITFLAG b = lsbf_checkbitflags(L, 2); + // Check if all bits in b are also set in a + lua_pushboolean(L, (a & b) == a); + return 1; +} + +/*** +Checks if the given flags are set. +This is different from the `>=` and `<=` operators because if the flag to check +has a value `0`, it will always return `false`. So if there are flags that are +unsupported on a platform, they can be set to 0 and the `has` function will +return `false` if the flags are checked. +@function bitflag:has +@tparam bitflag subset the flags to check for. +@treturn boolean true if all the flags are set, false otherwise. +@usage +local sys = require 'system' +local flags = sys.bitflag(12) -- b1100 +local myflags = sys.bitflag(15) -- b1111 +print(flags:has(myflags)) -- false, not all bits in myflags are set in flags +print(myflags:has(flags)) -- true, all bits in flags are set in myflags +*/ +static int lsbf_has(lua_State *L) { + LSBF_BITFLAG a = lsbf_checkbitflags(L, 1); + LSBF_BITFLAG b = lsbf_checkbitflags(L, 2); + // Check if all bits in b are also set in a, and b is not 0 + lua_pushboolean(L, (a | b) == a && b != 0); + return 1; +} + +static int lsbf_lt(lua_State *L) { + LSBF_BITFLAG a = lsbf_checkbitflags(L, 1); + LSBF_BITFLAG b = lsbf_checkbitflags(L, 2); + // Check if a is strictly less than b, meaning a != b and a is a subset of b + lua_pushboolean(L, (a != b) && ((a & b) == a)); + return 1; +} + +static int lsbf_index(lua_State *L) { + if (!lua_isnumber(L, 2)) { + // the parameter isn't a number, just lookup the key in the metatable + lua_getmetatable(L, 1); + lua_pushvalue(L, 2); + lua_gettable(L, -2); + return 1; + } + + int index = luaL_checkinteger(L, 2); + if (index < 0 || index >= sizeof(LSBF_BITFLAG) * 8) { + return luaL_error(L, "index out of range"); + } + lua_pushboolean(L, (lsbf_checkbitflags(L, 1) & (1 << index)) != 0); + return 1; +} + +static int lsbf_newindex(lua_State *L) { + LS_BitFlags *obj = (LS_BitFlags *)luaL_checkudata(L, 1, BITFLAGS_MT_NAME); + + if (!lua_isnumber(L, 2)) { + return luaL_error(L, "index must be a number"); + } + int index = luaL_checkinteger(L, 2); + if (index < 0 || index >= sizeof(LSBF_BITFLAG) * 8) { + return luaL_error(L, "index out of range"); + } + + luaL_checkany(L, 3); + if (lua_toboolean(L, 3)) { + obj->flags |= (1 << index); + } else { + obj->flags &= ~(1 << index); + } + return 0; +} + +static const struct luaL_Reg lsbf_funcs[] = { + {"bitflag", lsbf_new}, + {NULL, NULL} +}; + +static const struct luaL_Reg lsbf_methods[] = { + {"value", lsbf_value}, + {"has", lsbf_has}, + {"__tostring", lsbf_tostring}, + {"__add", lsbf_add}, + {"__sub", lsbf_sub}, + {"__eq", lsbf_eq}, + {"__le", lsbf_le}, + {"__lt", lsbf_lt}, + {"__index", lsbf_index}, + {"__newindex", lsbf_newindex}, + {NULL, NULL} +}; + +void bitflags_open(lua_State *L) { + luaL_newmetatable(L, BITFLAGS_MT_NAME); + luaL_setfuncs(L, lsbf_methods, 0); + lua_pop(L, 1); + + luaL_setfuncs(L, lsbf_funcs, 0); +} |