diff --git a/luad/conversions/enums.d b/luad/conversions/enums.d new file mode 100644 index 0000000..974861f --- /dev/null +++ b/luad/conversions/enums.d @@ -0,0 +1,173 @@ +/** +Internal module for pushing and getting _enums. + +Enum's are treated in Lua as strings. + +Conversion of enum keys is case-insensitive, which I think is more useful for Lua's typical 'config' style usage + +This is still a work-in-progress. Outstanding issues include: + Handling of bitfields. + Assignment of integer keys? + Conversion function needs to be improved (linear search! >_<) +*/ +module luad.conversions.enums; + +import luad.c.all; +import luad.stack; + +import std.traits; +import std.conv; +import std.range; +import std.string; + +struct KeyValuePair(ValueType) +{ + string key; + ValueType value; +} + +// produce a tuple of KeyValuePair's for an enum. +template EnumKeyValuePair(Enum) +{ + template impl(size_t len, size_t offset, Items...) + { + static if(offset == len) + alias impl = TypeTuple!(); + else + alias impl = TypeTuple!(KeyValuePair!Enum(Items[offset], Items[len + offset]), impl!(len, offset + 1, Items)); + } + + alias Keys = TypeTuple!(__traits(allMembers, Enum)); + alias Values = EnumMembers!Enum; + static assert(Keys.length == Values.length); + + alias EnumKeyValuePair = impl!(Keys.length, 0, TypeTuple!(Keys, Values)); +} + +immutable(KeyValuePair!Enum)[] getKeyValuePairs(Enum)() pure nothrow @nogc +{ + static immutable(KeyValuePair!Enum[]) kvp = [ EnumKeyValuePair!Enum ]; + return kvp; +} + +// TODO: These linear lookups are pretty crappy... we can do better, but this get's us working. +Enum getEnumValue(Enum)(lua_State* L, const(char)[] value) if(is(Enum == enum)) +{ + value = value.strip; + if(!value.empty) + { + auto kvp = getKeyValuePairs!Enum(); + foreach(ref i; kvp) + { + if(!icmp(i.key, value)) // case inseneitive enum keys... + return i.value; + } + } + luaL_error(L, "invalid enum key '%s' for enum type %s", value.ptr, Enum.stringof.ptr); + return Enum.init; +} + +string getEnumFromValue(Enum)(Enum value) +{ + auto kvp = getKeyValuePairs!Enum(); + foreach(ref i; kvp) + { + if(value == i.value) + return i.key; + } + return null; +} + +void pushEnum(T)(lua_State* L, T value) if (is(T == enum)) +{ + string key = getEnumFromValue(value); + if(key) + lua_pushlstring(L, key.ptr, key.length); + else + luaL_error(L, "invalid value for enum type %s", T.stringof.ptr); +} + +T getEnum(T)(lua_State* L, int idx) if(is(T == enum)) +{ + // TODO: check to see if idx is a number, if it is, convert it directly? + + size_t len; + const(char)* s = lua_tolstring(L, idx, &len); + return getEnumValue!T(L, s[0..len]); +} + +void pushStaticTypeInterface(T)(lua_State* L) if(is(T == enum)) +{ + lua_newtable(L); + + // TODO: we could get fancy and make an __index table of keys, so that they are read-only + // ... but for now, we'll just populate a table with the keys as strings + + // set 'init' + string initVal = getEnumFromValue(T.init); + lua_pushlstring(L, initVal.ptr, initVal.length); + lua_setfield(L, -2, "init"); + + // TODO: integral enums also have 'min' and 'max' + + // we'll create tables for the keys and valyes arrays. + lua_newtable(L); // keys + lua_newtable(L); // values + + // add the enum keys + auto kvp = getKeyValuePairs!T(); + foreach(int i, ref e; kvp) + { + // set the key to the key string (lua will carry enums by string) + lua_pushlstring(L, e.key.ptr, e.key.length); + lua_setfield(L, -4, e.key.ptr); + + // push the key to the keys array + lua_pushlstring(L, e.key.ptr, e.key.length); + lua_rawseti(L, -3, i+1); + + // push the value to the values array + pushValue!(OriginalType!T)(L, e.value); + lua_rawseti(L, -2, i+1); + } + + lua_setfield(L, -3, "values"); + lua_setfield(L, -2, "keys"); +} + +version(unittest) +{ + import luad.base; + + enum E + { + Key0, + Key1 + } +} + +unittest +{ + import luad.testing; + + lua_State* L = luaL_newstate(); + scope(success) lua_close(L); + luaL_openlibs(L); + + pushValue(L, E.Key0); + assert(lua_isstring(L, -1)); + lua_setglobal(L, "enum"); + + unittest_lua(L, ` + assert(enum == "Key0") + + enum = "key1" + `); + + lua_getglobal(L, "enum"); + E e = getValue!E(L, -1); + assert(e == E.Key1); + lua_pop(L, 1); + + // TODO: test the enum type interface... +} diff --git a/luad/stack.d b/luad/stack.d index d1cf6f5..8f0eeb5 100644 --- a/luad/stack.d +++ b/luad/stack.d @@ -71,6 +71,7 @@ import luad.conversions.arrays; import luad.conversions.structs; import luad.conversions.assocarrays; import luad.conversions.classes; +import luad.conversions.enums; import luad.conversions.variant; /** @@ -90,6 +91,9 @@ void pushValue(T)(lua_State* L, T value) else static if(is(T == Nil)) lua_pushnil(L); + else static if(is(T == enum)) + pushEnum(L, value); + else static if(is(T == bool)) lua_pushboolean(L, cast(bool)value); @@ -157,7 +161,10 @@ template isVoidArray(T) */ template luaTypeOf(T) { - static if(is(T == bool)) + static if(is(T == enum)) + enum luaTypeOf = LUA_TSTRING; + + else static if(is(T == bool)) enum luaTypeOf = LUA_TBOOLEAN; else static if(is(T == Nil)) @@ -251,6 +258,9 @@ T getValue(T, alias typeMismatchHandler = defaultTypeMismatch)(lua_State* L, int else static if(is(T == Nil)) return nil; + else static if(is(T == enum)) + return getEnum!T(L, idx); + else static if(is(T == bool)) return lua_toboolean(L, idx); diff --git a/luad/state.d b/luad/state.d index d58757a..6c350bc 100644 --- a/luad/state.d +++ b/luad/state.d @@ -8,6 +8,7 @@ import std.typecons : isTuple; import luad.c.all; import luad.stack; import luad.conversions.classes; +import luad.conversions.enums; import luad.base, luad.table, luad.lfunction, luad.dynamic, luad.error; diff --git a/visuald/LuaD.visualdproj b/visuald/LuaD.visualdproj index 8218571..ce04125 100644 --- a/visuald/LuaD.visualdproj +++ b/visuald/LuaD.visualdproj @@ -296,6 +296,7 @@ +