1 module luad.base;
2 
3 import luad.c.all;
4 import luad.stack;
5 
6 import core.stdc..string : strlen;
7 
8 /**
9  * Enumerates all Lua types.
10  */
11 enum LuaType
12 {
13 	///string
14 	String = LUA_TSTRING,
15 	///number
16 	Number = LUA_TNUMBER,
17 	//table
18 	Table = LUA_TTABLE,
19 	///nil
20 	Nil = LUA_TNIL,
21 	///boolean
22 	Boolean = LUA_TBOOLEAN,
23 	///function
24 	Function = LUA_TFUNCTION,
25 	///userdata
26 	Userdata = LUA_TUSERDATA,
27 	///ditto
28 	LightUserdata = LUA_TLIGHTUSERDATA,
29 	///thread
30 	Thread = LUA_TTHREAD
31 }
32 
33 package struct Nil{}
34 
35 /**
36  * Special value representing the Lua type and value nil.
37  * Examples:
38  * Useful for clearing keys in a table:
39  * --------------------------
40 	lua["n"] = 1.23;
41 	assert(lua.get!double("n") == 1.23);
42 
43 	lua["n"] = nil;
44 	assert(lua["n"].type == LuaType.Nil);
45  * --------------------------
46  */
47 public Nil nil;
48 
49 /**
50  * Represents a reference to a Lua value of any type.
51  * It contains only the bare minimum of functionality which all Lua values support.
52  * For a generic reference type with more functionality, see $(DPREF dynamic,LuaDynamic).
53  */
54 struct LuaObject
55 {
56 	private:
57 	int r = LUA_REFNIL;
58 	lua_State* L = null;
59 
60 	package:
61 	this(lua_State* L, int idx)
62 	{
63 		this.L = L;
64 
65 		lua_pushvalue(L, idx);
66 		r = luaL_ref(L, LUA_REGISTRYINDEX);
67 	}
68 
69 	void push() nothrow
70 	{
71 		lua_rawgeti(L, LUA_REGISTRYINDEX, r);
72 	}
73 
74 	static void checkType(lua_State* L, int idx, int expectedType, const(char)* expectedName)
75 	{
76 		int t = lua_type(L, idx);
77 		if(t != expectedType)
78 		{
79 			luaL_error(L, "attempt to create %s with %s", expectedName, lua_typename(L, t));
80 		}
81 	}
82 
83 	public:
84 	@trusted this(this)
85 	{
86 		push();
87 		r = luaL_ref(L, LUA_REGISTRYINDEX);
88 	}
89 
90 	@trusted nothrow ~this()
91 	{
92 		luaL_unref(L, LUA_REGISTRYINDEX, r);
93 	}
94 
95 	/// The underlying $(D lua_State) pointer for interfacing with C.
96 	lua_State* state() pure nothrow @safe @property
97 	{
98 		return L;
99 	}
100 
101 	/**
102 	 * Release this reference.
103 	 *
104 	 * This reference becomes a nil reference.
105 	 * This is only required when you want to _release the reference before the lifetime
106 	 * of this $(D LuaObject) has ended.
107 	 */
108 	void release() pure nothrow @safe
109 	{
110 		r = LUA_REFNIL;
111 		L = null;
112 	}
113 
114 	/**
115 	 * Type of referenced object.
116 	 * See_Also:
117 	 *	 $(MREF LuaType)
118 	 */
119 	@property LuaType type() @trusted nothrow
120 	{
121 		push();
122 		auto result = cast(LuaType)lua_type(state, -1);
123 		lua_pop(state, 1);
124 		return result;
125 	}
126 
127 	/**
128 	 * Type name of referenced object.
129 	 */
130 	@property string typeName() @trusted /+ nothrow +/
131 	{
132 		push();
133 		const(char)* cname = luaL_typename(state, -1); // TODO: Doesn't have to use luaL_typename, i.e. no copy
134 		auto name = cname[0.. strlen(cname)].idup;
135 		lua_pop(state, 1);
136 		return name;
137 	}
138 
139 	/// Boolean whether or not the referenced object is nil.
140 	@property bool isNil() pure nothrow @safe
141 	{
142 		return r == LUA_REFNIL;
143 	}
144 
145 	/**
146 	 * Convert the referenced object into a textual representation.
147 	 *
148 	 * The returned string is formatted in the same way the Lua $(D tostring) function formats.
149 	 *
150 	 * Returns:
151 	 * String representation of referenced object
152 	 */
153 	string toString() @trusted
154 	{
155 		push();
156 
157 		size_t len;
158 		const(char)* cstr = luaL_tolstring(state, -1, &len);
159 		auto str = cstr[0 .. len].idup;
160 
161 		lua_pop(state, 2);
162 		return str;
163 	}
164 
165 	/**
166 	 * Attempt _to convert the referenced object _to the specified D type.
167 	 * Examples:
168 	 -----------------------
169 	auto results = lua.doString(`return "hello!"`);
170 	assert(results[0].to!string() == "hello!");
171 	 -----------------------
172 	 */
173 	T to(T)()
174 	{
175 		static void typeMismatch(lua_State* L, int t, int e)
176 		{
177 			luaL_error(L, "attempt to convert LuaObject with type %s to a %s", lua_typename(L, t), lua_typename(L, e));
178 		}
179 
180 		push();
181 		return popValue!(T, typeMismatch)(state);
182 	}
183 
184 	/**
185 	 * Compare this object to another with Lua's equality semantics.
186 	 * Also returns false if the two objects are in different Lua states.
187 	 */
188 	bool opEquals(T : LuaObject)(ref T o) @trusted
189 	{
190 		if(o.state != this.state)
191 			return false;
192 
193 		push();
194 		o.push();
195 		scope(success) lua_pop(state, 2);
196 
197 		return lua_equal(state, -1, -2);
198 	}
199 }
200 
201 unittest
202 {
203 	lua_State* L = luaL_newstate();
204 	scope(success) lua_close(L);
205 
206 	lua_pushstring(L, "foobar");
207 	auto o = popValue!LuaObject(L);
208 
209 	assert(!o.isNil);
210 	assert(o.type == LuaType.String);
211 	assert(o.typeName == "string");
212 	assert(o.to!string() == "foobar");
213 
214 	lua_pushnil(L);
215 	auto nilref = popValue!LuaObject(L);
216 
217 	assert(nilref.isNil);
218 	assert(nilref.typeName == "nil");
219 
220 	assert(o != nilref);
221 
222 	auto o2 = o;
223 	assert(o2 == o);
224 }