summaryrefslogtreecommitdiff
path: root/src/bitflags.c
blob: e3979182d567758122995f446855971623c43527 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
/// 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;
}

// Validates that the given index is a table containing a field 'fieldname'
// which is a bitflag object and returns its value.
// If the index is not a table or the field is not a bitflag object, a Lua
// error is raised. If the bitflag is not present, the default value is returned.
// The stack remains unchanged.
LSBF_BITFLAG lsbf_checkbitflagsfield(lua_State *L, int index, const char *fieldname, LSBF_BITFLAG default_value) {
    luaL_checktype(L, index, LUA_TTABLE);
    lua_getfield(L, index, fieldname);

    // if null, return default value
    if (lua_isnil(L, -1)) {
        lua_pop(L, 1);
        return default_value;
    }

    // check to bitflags
    LS_BitFlags *obj = luaL_testudata(L, -1, BITFLAGS_MT_NAME);
    if (obj == NULL) {
        lua_pop(L, 1);
        return luaL_error(L, "bad argument #%d, field '%s' must be a bitflag object", index, fieldname);
    }
    LSBF_BITFLAG value = obj->flags;
    lua_pop(L, 1);
    return value;
}

/***
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, not the same flags
local flags6 = sys.bitflag(7)   -- b0111
print(flags6 == flags4)         -- true, same flags

-- comparison of subsets
local flags7 = sys.bitflag(0)    -- b0000
local flags8 = sys.bitflag(3)    -- b0011
local flags9 = sys.bitflag(7)    -- b0111
print(flags9:has_all_of(flags8)) -- true, flags8 bits are all set in flags9
print(flags8:has_any_of(flags9)) -- true, some of flags9 bits are set in flags8
print(flags8:has_all_of(flags7)) -- false, flags7 (== 0) is not set in flags8
*/
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;
}

/***
Checks if all the flags in the given subset are set.
If the flags 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_all_of` function will
return `false` if the flags are checked.
@function bitflag:has_all_of
@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_all_of(myflags))  -- false, not all bits in myflags are set in flags
print(myflags:has_all_of(flags))  -- true, all bits in flags are set in myflags
*/
static int lsbf_has_all_of(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) == b && b != 0);
    return 1;
}

/***
Checks if any of the flags in the given subset are set.
If the flags 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_any_of` function will
return `false` if the flags are checked.
@function bitflag:has_any_of
@tparam bitflag subset the flags to check for.
@treturn boolean true if any of the flags are set, false otherwise.
@usage
local sys = require 'system'
local flags = sys.bitflag(12)     -- b1100
local myflags = sys.bitflag(7)    -- b0111
print(flags:has_any_of(myflags))  -- true, some bits in myflags are set in flags
print(myflags:has_any_of(flags))  -- true, some bits in flags are set in myflags
*/
static int lsbf_has_any_of(lua_State *L) {
    LSBF_BITFLAG a = lsbf_checkbitflags(L, 1);
    LSBF_BITFLAG b = lsbf_checkbitflags(L, 2);
    // Check if any bits in b are set in a
    lua_pushboolean(L, (a & b) != 0);
    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_all_of", lsbf_has_all_of},
    {"has_any_of", lsbf_has_any_of},
    {"__tostring", lsbf_tostring},
    {"__add", lsbf_add},
    {"__sub", lsbf_sub},
    {"__eq", lsbf_eq},
    {"__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);
}