diff options
author | Benoit Germain <benoit.germain@ubisoft.com> | 2025-03-07 17:44:59 +0100 |
---|---|---|
committer | Benoit Germain <benoit.germain@ubisoft.com> | 2025-03-07 17:44:59 +0100 |
commit | aaef96a2faeb761b2a8b893158899f48c39cbf4b (patch) | |
tree | b500548e19ce09e1695598af21d486765cc6441a | |
parent | b92dbd75b1c9988dcc83e5876e1ce2815145fa30 (diff) | |
download | lanes-aaef96a2faeb761b2a8b893158899f48c39cbf4b.tar.gz lanes-aaef96a2faeb761b2a8b893158899f48c39cbf4b.tar.bz2 lanes-aaef96a2faeb761b2a8b893158899f48c39cbf4b.zip |
Revamped lanes.nameof
-rw-r--r-- | src/nameof.cpp | 310 | ||||
-rw-r--r-- | src/tools.cpp | 9 | ||||
-rw-r--r-- | src/tools.hpp | 2 | ||||
-rw-r--r-- | unit_tests/lane_tests.cpp | 15 |
4 files changed, 218 insertions, 118 deletions
diff --git a/src/nameof.cpp b/src/nameof.cpp index b3874f3..1f338a5 100644 --- a/src/nameof.cpp +++ b/src/nameof.cpp | |||
@@ -31,137 +31,225 @@ THE SOFTWARE. | |||
31 | 31 | ||
32 | // ################################################################################################# | 32 | // ################################################################################################# |
33 | 33 | ||
34 | DECLARE_UNIQUE_TYPE(FqnLength, lua_Unsigned); | ||
35 | |||
34 | // Return some name helping to identify an object | 36 | // Return some name helping to identify an object |
35 | [[nodiscard]] | 37 | [[nodiscard]] |
36 | static int DiscoverObjectNameRecur(lua_State* const L_, int shortest_, TableIndex const curDepth_) | 38 | FqnLength DiscoverObjectNameRecur(lua_State* const L_, FqnLength const shortest_) |
37 | { | 39 | { |
38 | static constexpr StackIndex kWhat{ 1 }; // the object to investigate // L_: o "r" {c} {fqn} ... {?} | 40 | static constexpr StackIndex kWhat{ 1 }; // the object to investigate // L_: o "r" {c} {fqn} ... <> |
39 | static constexpr StackIndex kResult{ 2 }; // where the result string is stored | 41 | static constexpr StackIndex kResult{ 2 }; // where the result string is stored |
40 | static constexpr StackIndex kCache{ 3 }; // a cache | 42 | static constexpr StackIndex kCache{ 3 }; // a cache, where visited locations remember the FqnLength to reach them |
41 | static constexpr StackIndex kFQN{ 4 }; // the name compositing stack | 43 | static constexpr StackIndex kFQN{ 4 }; // the name compositing stack |
42 | // no need to scan this table if the name we will discover is longer than one we already know | 44 | |
43 | TableIndex const _nextDepth{ curDepth_ + 1 }; | 45 | // no need to scan this location if the name we will discover is longer than one we already know |
44 | if (shortest_ <= _nextDepth) { | 46 | FqnLength const _fqnLength{ lua_rawlen(L_, kFQN) }; |
47 | if (shortest_ <= _fqnLength) { | ||
45 | return shortest_; | 48 | return shortest_; |
46 | } | 49 | } |
47 | 50 | ||
48 | auto _pushNameOnFQN = [L_](std::string_view const& name_, TableIndex const depth_) { | 51 | // in: k, v at the top of the stack |
49 | luaG_pushstring(L_, name_); // L_: o "r" {c} {fqn} ... name_ | 52 | static constexpr auto _pushNameOnFQN = [](lua_State* const L_) { |
50 | lua_rawseti(L_, kFQN, depth_); // L_: o "r" {c} {fqn} ... {mt} | 53 | STACK_CHECK_START_REL(L_, 0); |
54 | lua_pushvalue(L_, -2); // L_: o "r" {c} {fqn} ... k v k | ||
55 | auto const _keyType{ luaG_type(L_, kIdxTop) }; | ||
56 | if (_keyType != LuaType::STRING) { | ||
57 | lua_pop(L_, 1); // L_: o "r" {c} {fqn} ... k v | ||
58 | luaG_pushstring(L_, "<%s>", luaG_typename(L_, _keyType).data()); // L_: o "r" {c} {fqn} ... k v "<type of k>" | ||
59 | } else { | ||
60 | // decorate the key string with something that tells us the type of the value | ||
61 | switch (luaG_type(L_, StackIndex{ -2 })) { | ||
62 | default: | ||
63 | luaG_pushstring(L_, "??"); // L_: o "r" {c} {fqn} ... k v "k" "??" | ||
64 | break; | ||
65 | case LuaType::FUNCTION: | ||
66 | luaG_pushstring(L_, "()"); // L_: o "r" {c} {fqn} ... k v "k" "()" | ||
67 | break; | ||
68 | case LuaType::TABLE: | ||
69 | luaG_pushstring(L_, "[]"); // L_: o "r" {c} {fqn} ... k v "k" "[]" | ||
70 | break; | ||
71 | case LuaType::USERDATA: | ||
72 | luaG_pushstring(L_, "<>"); // L_: o "r" {c} {fqn} ... k v "k" "<>" | ||
73 | break; | ||
74 | } | ||
75 | lua_concat(L_, 2); // L_: o "r" {c} {fqn} ... k v "k??" | ||
76 | } | ||
77 | |||
78 | FqnLength const _depth{ lua_rawlen(L_, kFQN) + 1 }; | ||
79 | lua_rawseti(L_, kFQN, _depth); // L_: o "r" {c} {fqn} ... k v | ||
80 | STACK_CHECK(L_, 0); | ||
81 | STACK_CHECK(L_, 0); | ||
82 | return _depth; | ||
51 | }; | 83 | }; |
52 | 84 | ||
53 | auto _popNameFromFQN = [L_](TableIndex const depth_) { | 85 | static constexpr auto _popNameFromFQN = [](lua_State* const L_) { |
86 | STACK_CHECK_START_REL(L_, 0); | ||
54 | lua_pushnil(L_); // L_: o "r" {c} {fqn} ... nil | 87 | lua_pushnil(L_); // L_: o "r" {c} {fqn} ... nil |
55 | lua_rawseti(L_, kFQN, depth_); // L_: o "r" {c} {fqn} ... | 88 | lua_rawseti(L_, kFQN, lua_rawlen(L_, kFQN)); // L_: o "r" {c} {fqn} ... |
89 | STACK_CHECK(L_, 0); | ||
56 | }; | 90 | }; |
57 | 91 | ||
58 | auto _recurseIfTableThenPop = [&_pushNameOnFQN, &_popNameFromFQN, L_](std::string_view const& name_, TableIndex const depth_, int shortest_) { | 92 | static constexpr auto _recurseThenPop = [](lua_State* const L_, FqnLength const shortest_) -> FqnLength { |
59 | STACK_CHECK_START_REL(L_, 0); // L_: o "r" {c} {fqn} ... <> {}? | 93 | STACK_CHECK_START_REL(L_, 0); // L_: o "r" {c} {fqn} ... <> |
60 | if (lua_istable(L_, kIdxTop)) { | 94 | FqnLength r_{ shortest_ }; |
61 | _pushNameOnFQN(name_, depth_); | 95 | auto const _type{ luaG_type(L_, kIdxTop) }; |
62 | shortest_ = DiscoverObjectNameRecur(L_, shortest_, TableIndex{ depth_ + 1 }); | 96 | if (_type == LuaType::TABLE || _type == LuaType::USERDATA || _type == LuaType::FUNCTION) { |
63 | _popNameFromFQN(depth_); | 97 | r_ = DiscoverObjectNameRecur(L_, shortest_); |
98 | STACK_CHECK(L_, 0); | ||
99 | _popNameFromFQN(L_); | ||
100 | STACK_CHECK(L_, 0); | ||
64 | } | 101 | } |
65 | lua_pop(L_, 1); // L_: o "r" {c} {fqn} ... <> | 102 | |
103 | lua_pop(L_, 1); // L_: o "r" {c} {fqn} ... | ||
66 | STACK_CHECK(L_, -1); | 104 | STACK_CHECK(L_, -1); |
67 | return shortest_; | 105 | return r_; |
68 | }; | 106 | }; |
69 | 107 | ||
70 | STACK_GROW(L_, 3); | 108 | // in: k, v at the top of the stack |
71 | STACK_CHECK_START_REL(L_, 0); | 109 | // out: v popped from the stack |
72 | // stack top contains the table to search in | 110 | static constexpr auto _processKeyValue = [](lua_State* const L_, FqnLength const shortest_) -> FqnLength { |
73 | LUA_ASSERT(L_, lua_istable(L_, kIdxTop)); | 111 | FqnLength _r{ shortest_ }; |
74 | lua_pushvalue(L_, kIdxTop); // L_: o "r" {c} {fqn} ... {?} {?} | 112 | STACK_GROW(L_, 2); |
75 | lua_rawget(L_, kCache); // L_: o "r" {c} {fqn} ... {?} nil/1 | 113 | STACK_CHECK_START_REL(L_, 0); // L_: o "r" {c} {fqn} ... k v |
76 | // if table is already visited, we are done | 114 | |
77 | if (!lua_isnil(L_, kIdxTop)) { | 115 | // filter out uninteresting values |
78 | lua_pop(L_, 1); // L_: o "r" {c} {fqn} ... {?} | 116 | auto const _valType{ luaG_type(L_, kIdxTop) }; |
79 | return shortest_; | 117 | if (_valType == LuaType::BOOLEAN || _valType == LuaType::LIGHTUSERDATA || _valType == LuaType::NUMBER || _valType == LuaType::STRING) { |
80 | } | 118 | lua_pop(L_, 1); // L_: o "r" {c} {fqn} ... k |
81 | // examined table is not in the cache, add it now | 119 | return _r; |
82 | lua_pop(L_, 1); // L_: o "r" {c} {fqn} ... {?} | 120 | } |
83 | // cache[o] = 1 | 121 | |
84 | lua_pushvalue(L_, kIdxTop); // L_: o "r" {c} {fqn} ... {?} {?} | ||
85 | lua_pushinteger(L_, 1); // L_: o "r" {c} {fqn} ... {?} {?} 1 | ||
86 | lua_rawset(L_, kCache); // L_: o "r" {c} {fqn} ... {?} | ||
87 | // scan table contents | ||
88 | lua_pushnil(L_); // L_: o "r" {c} {fqn} ... {?} nil | ||
89 | while (lua_next(L_, -2)) { // L_: o "r" {c} {fqn} ... {?} k v | ||
90 | // std::string_view const _strKey{ (luaG_type(L_, -2) == LuaType::STRING) ? luaG_tostring(L_, -2) : "" }; // only for debugging | ||
91 | // lua_Number const numKey = (luaG_type(L_, -2) == LuaType::NUMBER) ? lua_tonumber(L_, -2) : -6666; // only for debugging | ||
92 | STACK_CHECK(L_, 2); | ||
93 | // append key name to fqn stack | 122 | // append key name to fqn stack |
94 | lua_pushvalue(L_, -2); // L_: o "r" {c} {fqn} ... {?} k v k | 123 | FqnLength const _depth{ _pushNameOnFQN(L_) }; // L_: o "r" {c} {fqn} ... k v |
95 | lua_rawseti(L_, kFQN, _nextDepth); // L_: o "r" {c} {fqn} ... {?} k v | 124 | |
125 | // process the value | ||
96 | if (lua_rawequal(L_, kIdxTop, kWhat)) { // is it what we are looking for? | 126 | if (lua_rawequal(L_, kIdxTop, kWhat)) { // is it what we are looking for? |
97 | STACK_CHECK(L_, 2); | ||
98 | // update shortest name | 127 | // update shortest name |
99 | if (_nextDepth < shortest_) { | 128 | if (_depth < _r) { |
100 | shortest_ = _nextDepth; | 129 | _r = _depth; |
101 | std::ignore = tools::PushFQN(L_, kFQN, _nextDepth); // L_: o "r" {c} {fqn} ... {?} k v "fqn" | 130 | std::ignore = tools::PushFQN(L_, kFQN); // L_: o "r" {c} {fqn} ... k v "fqn" |
102 | lua_replace(L_, kResult); // L_: o "r" {c} {fqn} ... {?} k v | 131 | lua_replace(L_, kResult); // L_: o "r" {c} {fqn} ... k v |
103 | } | 132 | } |
104 | // no need to search further at this level | 133 | |
105 | lua_pop(L_, 2); // L_: o "r" {c} {fqn} ... {?} | 134 | lua_pop(L_, 1); // L_: o "r" {c} {fqn} ... k |
106 | STACK_CHECK(L_, 0); | 135 | _popNameFromFQN(L_); |
107 | break; | 136 | } else { |
137 | // let's see if the value contains what we are looking for | ||
138 | _r = _recurseThenPop(L_, _r); // L_: o "r" {c} {fqn} ... k | ||
108 | } | 139 | } |
109 | switch (luaG_type(L_, kIdxTop)) { // L_: o "r" {c} {fqn} ... {?} k v | ||
110 | default: // nil, boolean, light userdata, number and string aren't identifiable | ||
111 | break; | ||
112 | |||
113 | case LuaType::TABLE: // L_: o "r" {c} {fqn} ... {?} k {} | ||
114 | STACK_CHECK(L_, 2); | ||
115 | shortest_ = DiscoverObjectNameRecur(L_, shortest_, _nextDepth); | ||
116 | // search in the table's metatable too | ||
117 | if (lua_getmetatable(L_, -1)) { // L_: o "r" {c} {fqn} ... {?} k {} {mt} | ||
118 | shortest_ = _recurseIfTableThenPop("__metatable", _nextDepth, shortest_); // L_: o "r" {c} {fqn} ... {?} k {} | ||
119 | } | ||
120 | STACK_CHECK(L_, 2); | ||
121 | break; | ||
122 | |||
123 | case LuaType::THREAD: // L_: o "r" {c} {fqn} ... {?} k T | ||
124 | // TODO: explore the thread's stack frame looking for our culprit? | ||
125 | break; | ||
126 | |||
127 | case LuaType::USERDATA: // L_: o "r" {c} {fqn} ... {?} k U | ||
128 | STACK_CHECK(L_, 2); | ||
129 | // search in the object's metatable (some modules are built that way) | ||
130 | if (lua_getmetatable(L_, -1)) { // L_: o "r" {c} {fqn} ... {?} k U {mt} | ||
131 | shortest_ = _recurseIfTableThenPop("__metatable", _nextDepth, shortest_); // L_: o "r" {c} {fqn} ... {?} k U | ||
132 | } | ||
133 | 140 | ||
134 | STACK_CHECK(L_, 2); | 141 | STACK_CHECK(L_, -1); |
135 | // search in the object's uservalues | 142 | return _r; |
136 | { | 143 | }; |
137 | UserValueIndex _uvi{ 1 }; | 144 | |
138 | while (lua_getiuservalue(L_, kIdxTop, _uvi) != LUA_TNONE) { // L_: o "r" {c} {fqn} ... {?} k U {u} | 145 | static constexpr auto _scanTable = [](lua_State* const L_, FqnLength const shortest_) -> FqnLength { |
139 | shortest_ = _recurseIfTableThenPop("uservalue", _nextDepth, shortest_); // L_: o "r" {c} {fqn} ... {?} k U | 146 | FqnLength r_{ shortest_ }; |
140 | ++_uvi; | 147 | STACK_GROW(L_, 2); |
141 | } | 148 | STACK_CHECK_START_REL(L_, 0); |
142 | // when lua_getiuservalue() returned LUA_TNONE, it pushed a nil. pop it now | 149 | lua_pushnil(L_); // L_: o "r" {c} {fqn} ... {?} nil |
143 | lua_pop(L_, 1); // L_: o "r" {c} {fqn} ... {?} k U | 150 | while (lua_next(L_, -2)) { // L_: o "r" {c} {fqn} ... {?} k v |
151 | r_ = _processKeyValue(L_, r_); // L_: o "r" {c} {fqn} ... {?} k | ||
152 | } // L_: o "r" {c} {fqn} ... {?} | ||
153 | |||
154 | if (lua_getmetatable(L_, kIdxTop)) { // L_: o "r" {c} {fqn} ... {?} {mt} | ||
155 | lua_pushstring(L_, "__metatable"); // L_: o "r" {c} {fqn} ... {?} {mt} "__metatable" | ||
156 | lua_insert(L_, -2); // L_: o "r" {c} {fqn} ... {?} "__metatable" {mt} | ||
157 | r_ = _processKeyValue(L_, r_); // L_: o "r" {c} {fqn} ... {?} "__metatable" | ||
158 | lua_pop(L_, 1); // L_: o "r" {c} {fqn} ... {?} | ||
159 | } | ||
160 | |||
161 | STACK_CHECK(L_, 0); | ||
162 | return r_; | ||
163 | }; | ||
164 | |||
165 | static constexpr auto _scanUserData = [](lua_State* const L_, FqnLength const shortest_) -> FqnLength { | ||
166 | FqnLength r_{ shortest_ }; | ||
167 | FqnLength const _depth{ lua_rawlen(L_, kFQN) + 1 }; | ||
168 | STACK_GROW(L_, 2); | ||
169 | STACK_CHECK_START_REL(L_, 0); | ||
170 | if (lua_getmetatable(L_, kIdxTop)) { // L_: o "r" {c} {fqn} ... U {mt} | ||
171 | lua_pushstring(L_, "__metatable"); // L_: o "r" {c} {fqn} ... U {mt} "__metatable" | ||
172 | lua_insert(L_, -2); // L_: o "r" {c} {fqn} ... U "__metatable" {mt} | ||
173 | r_ = _processKeyValue(L_, r_); // L_: o "r" {c} {fqn} ... U "__metatable" | ||
174 | lua_pop(L_, 1); // L_: o "r" {c} {fqn} ... U | ||
175 | } | ||
176 | |||
177 | STACK_CHECK(L_, 0); | ||
178 | |||
179 | UserValueIndex _uvi{ 0 }; | ||
180 | while (lua_getiuservalue(L_, kIdxTop, ++_uvi) != LUA_TNONE) { // L_: o "r" {c} {fqn} ... U uv | ||
181 | luaG_pushstring(L_, "<uv:%d>", _uvi); // L_: o "r" {c} {fqn} ... U uv name | ||
182 | lua_insert(L_, -2); // L_: o "r" {c} {fqn} ... U name uv | ||
183 | r_ = _processKeyValue(L_, r_); // L_: o "r" {c} {fqn} ... U name | ||
184 | lua_pop(L_, 1); // L_: o "r" {c} {fqn} ... U | ||
185 | } | ||
186 | |||
187 | // when lua_getiuservalue() returned LUA_TNONE, it pushed a nil. pop it now | ||
188 | lua_pop(L_, 1); // L_: o "r" {c} {fqn} ... U | ||
189 | |||
190 | STACK_CHECK(L_, 0); | ||
191 | return r_; | ||
192 | }; | ||
193 | |||
194 | static constexpr auto _scanFunction = [](lua_State* const L_, FqnLength const shortest_) -> FqnLength { | ||
195 | FqnLength r_{ shortest_ }; | ||
196 | STACK_GROW(L_, 2); | ||
197 | STACK_CHECK_START_REL(L_, 0); // L_: o "r" {c} {fqn} ... F | ||
198 | int _n{ 0 }; | ||
199 | for (char const* _upname{}; (_upname = lua_getupvalue(L_, kIdxTop, ++_n));) { // L_: o "r" {c} {fqn} ... F up | ||
200 | if (*_upname == 0) { | ||
201 | _upname = "<C>"; | ||
144 | } | 202 | } |
145 | STACK_CHECK(L_, 2); | ||
146 | break; | ||
147 | 203 | ||
148 | case LuaType::FUNCTION: // L_: o "r" {c} {fqn} ... {?} k F | 204 | luaG_pushstring(L_, "upvalue:%s", _upname); // L_: o "r" {c} {fqn} ... F up name |
149 | // TODO: explore the function upvalues | 205 | lua_insert(L_, -2); // L_: o "r" {c} {fqn} ... F name up |
150 | break; | 206 | r_ = _processKeyValue(L_, r_); // L_: o "r" {c} {fqn} ... F name |
207 | lua_pop(L_, 1); // L_: o "r" {c} {fqn} ... F | ||
151 | } | 208 | } |
152 | // make ready for next iteration | 209 | |
153 | lua_pop(L_, 1); // L_: o "r" {c} {fqn} ... {?} k | 210 | STACK_CHECK(L_, 0); |
154 | // remove name from fqn stack | 211 | return r_; |
155 | _popNameFromFQN(_nextDepth); | 212 | }; |
156 | STACK_CHECK(L_, 1); | 213 | |
157 | } // L_: o "r" {c} {fqn} ... {?} | 214 | STACK_GROW(L_, 2); |
158 | STACK_CHECK(L_, 0); | 215 | STACK_CHECK_START_REL(L_, 0); |
159 | // remove the visited table from the cache, in case a shorter path to the searched object exists | 216 | // stack top contains the location to search in (table, function, userdata) |
160 | lua_pushvalue(L_, kIdxTop); // L_: o "r" {c} {fqn} ... {?} {?} | 217 | [[maybe_unused]] auto const _typeWhere{ luaG_type(L_, kIdxTop) }; |
161 | lua_pushnil(L_); // L_: o "r" {c} {fqn} ... {?} {?} nil | 218 | LUA_ASSERT(L_, _typeWhere == LuaType::TABLE || _typeWhere == LuaType::USERDATA || _typeWhere == LuaType::FUNCTION); |
162 | lua_rawset(L_, kCache); // L_: o "r" {c} {fqn} ... {?} | 219 | lua_pushvalue(L_, kIdxTop); // L_: o "r" {c} {fqn} ... <> <> |
220 | lua_rawget(L_, kCache); // L_: o "r" {c} {fqn} ... <> nil/N | ||
221 | auto const _visitDepth{ lua_isnil(L_, kIdxTop) ? std::numeric_limits<FqnLength::type>::max() : lua_tointeger(L_, kIdxTop) }; | ||
222 | lua_pop(L_, 1); // L_: o "r" {c} {fqn} ... <> | ||
223 | // if location is already visited with a name of <= length, we are done | ||
224 | if (_visitDepth <= _fqnLength) { | ||
225 | return shortest_; | ||
226 | } | ||
227 | |||
228 | // examined location is not in the cache, add it now | ||
229 | // cache[o] = _fqnLength | ||
230 | lua_pushvalue(L_, kIdxTop); // L_: o "r" {c} {fqn} ... <> <> | ||
231 | lua_pushinteger(L_, _fqnLength); // L_: o "r" {c} {fqn} ... <> <> N | ||
232 | lua_rawset(L_, kCache); // L_: o "r" {c} {fqn} ... <> | ||
233 | |||
234 | FqnLength r_; | ||
235 | // scan location contents | ||
236 | switch (luaG_type(L_, kIdxTop)) { // L_: o "r" {c} {fqn} ... <> | ||
237 | default: | ||
238 | raise_luaL_error(L_, "unexpected error, please investigate"); | ||
239 | break; | ||
240 | case LuaType::TABLE: | ||
241 | r_ = _scanTable(L_, shortest_); | ||
242 | break; | ||
243 | case LuaType::USERDATA: | ||
244 | r_ = _scanUserData(L_, shortest_); | ||
245 | break; | ||
246 | case LuaType::FUNCTION: | ||
247 | r_ = _scanFunction(L_, shortest_); | ||
248 | break; | ||
249 | } | ||
250 | |||
163 | STACK_CHECK(L_, 0); | 251 | STACK_CHECK(L_, 0); |
164 | return shortest_; | 252 | return r_; |
165 | } | 253 | } |
166 | 254 | ||
167 | // ################################################################################################# | 255 | // ################################################################################################# |
@@ -170,16 +258,17 @@ static int DiscoverObjectNameRecur(lua_State* const L_, int shortest_, TableInde | |||
170 | LUAG_FUNC(nameof) | 258 | LUAG_FUNC(nameof) |
171 | { | 259 | { |
172 | auto const _argCount{ lua_gettop(L_) }; | 260 | auto const _argCount{ lua_gettop(L_) }; |
173 | if (_argCount > 1) { | 261 | if (_argCount != 1) { |
174 | raise_luaL_argerror(L_, StackIndex{ _argCount }, "too many arguments."); | 262 | raise_luaL_argerror(L_, StackIndex{ _argCount }, "exactly 1 argument expected"); |
175 | } | 263 | } |
176 | 264 | ||
177 | // nil, boolean, light userdata, number and string aren't identifiable | 265 | // nil, boolean, light userdata, number and string aren't identifiable |
178 | auto const _isIdentifiable = [L_]() { | 266 | static constexpr auto _isIdentifiable = [](lua_State* const L_) { |
179 | auto const _valType{ luaG_type(L_, kIdxTop) }; | 267 | auto const _valType{ luaG_type(L_, kIdxTop) }; |
180 | return _valType == LuaType::TABLE || _valType == LuaType::FUNCTION || _valType == LuaType::USERDATA || _valType == LuaType::THREAD; | 268 | return _valType == LuaType::TABLE || _valType == LuaType::FUNCTION || _valType == LuaType::USERDATA || _valType == LuaType::THREAD; |
181 | }; | 269 | }; |
182 | if (!_isIdentifiable()) { | 270 | |
271 | if (!_isIdentifiable(L_)) { | ||
183 | luaG_pushstring(L_, luaG_typename(L_, kIdxTop)); // L_: o "type" | 272 | luaG_pushstring(L_, luaG_typename(L_, kIdxTop)); // L_: o "type" |
184 | lua_insert(L_, -2); // L_: "type" o | 273 | lua_insert(L_, -2); // L_: "type" o |
185 | return 2; | 274 | return 2; |
@@ -198,13 +287,14 @@ LUAG_FUNC(nameof) | |||
198 | lua_rawseti(L_, -2, 1); // L_: o nil {c} {fqn} | 287 | lua_rawseti(L_, -2, 1); // L_: o nil {c} {fqn} |
199 | // this is where we start the search | 288 | // this is where we start the search |
200 | luaG_pushglobaltable(L_); // L_: o nil {c} {fqn} _G | 289 | luaG_pushglobaltable(L_); // L_: o nil {c} {fqn} _G |
201 | std::ignore = DiscoverObjectNameRecur(L_, std::numeric_limits<int>::max(), TableIndex{ 1 }); | 290 | auto const _foundInG{ DiscoverObjectNameRecur(L_, FqnLength{ std::numeric_limits<FqnLength::type>::max() }) }; |
202 | if (lua_isnil(L_, 2)) { // try again with registry, just in case... | 291 | if (lua_isnil(L_, 2)) { // try again with registry, just in case... |
292 | LUA_ASSERT(L_, _foundInG == std::numeric_limits<FqnLength::type>::max()); | ||
203 | lua_pop(L_, 1); // L_: o nil {c} {fqn} | 293 | lua_pop(L_, 1); // L_: o nil {c} {fqn} |
204 | luaG_pushstring(L_, "_R"); // L_: o nil {c} {fqn} "_R" | 294 | luaG_pushstring(L_, "_R"); // L_: o nil {c} {fqn} "_R" |
205 | lua_rawseti(L_, -2, 1); // L_: o nil {c} {fqn} | 295 | lua_rawseti(L_, -2, 1); // L_: o nil {c} {fqn} |
206 | lua_pushvalue(L_, kIdxRegistry); // L_: o nil {c} {fqn} _R | 296 | lua_pushvalue(L_, kIdxRegistry); // L_: o nil {c} {fqn} _R |
207 | std::ignore = DiscoverObjectNameRecur(L_, std::numeric_limits<int>::max(), TableIndex{ 1 }); | 297 | [[maybe_unused]] auto const _foundInR{ DiscoverObjectNameRecur(L_, FqnLength{ std::numeric_limits<FqnLength::type>::max() }) }; |
208 | } | 298 | } |
209 | lua_pop(L_, 3); // L_: o "result" | 299 | lua_pop(L_, 3); // L_: o "result" |
210 | STACK_CHECK(L_, 1); | 300 | STACK_CHECK(L_, 1); |
diff --git a/src/tools.cpp b/src/tools.cpp index ee6d720..cbfefb0 100644 --- a/src/tools.cpp +++ b/src/tools.cpp | |||
@@ -95,19 +95,20 @@ namespace tools { | |||
95 | 95 | ||
96 | // inspired from tconcat() in ltablib.c | 96 | // inspired from tconcat() in ltablib.c |
97 | [[nodiscard]] | 97 | [[nodiscard]] |
98 | std::string_view PushFQN(lua_State* const L_, StackIndex const t_, TableIndex const last_) | 98 | std::string_view PushFQN(lua_State* const L_, StackIndex const t_) |
99 | { | 99 | { |
100 | STACK_CHECK_START_REL(L_, 0); | 100 | STACK_CHECK_START_REL(L_, 0); |
101 | // Lua 5.4 pushes &b as light userdata on the stack. be aware of it... | 101 | // Lua 5.4 pushes &b as light userdata on the stack. be aware of it... |
102 | luaL_Buffer _b; | 102 | luaL_Buffer _b; |
103 | luaL_buffinit(L_, &_b); // L_: ... {} ... &b? | 103 | luaL_buffinit(L_, &_b); // L_: ... {} ... &b? |
104 | TableIndex _i{ 1 }; | 104 | TableIndex _i{ 1 }; |
105 | for (; _i < last_; ++_i) { | 105 | TableIndex const _last{ static_cast<TableIndex::type>(lua_rawlen(L_, t_)) }; |
106 | for (; _i < _last; ++_i) { | ||
106 | lua_rawgeti(L_, t_, _i); | 107 | lua_rawgeti(L_, t_, _i); |
107 | luaL_addvalue(&_b); | 108 | luaL_addvalue(&_b); |
108 | luaL_addlstring(&_b, "/", 1); | 109 | luaL_addlstring(&_b, "/", 1); |
109 | } | 110 | } |
110 | if (_i == last_) { // add last value (if interval was not empty) | 111 | if (_i == _last) { // add last value (if interval was not empty) |
111 | lua_rawgeti(L_, t_, _i); | 112 | lua_rawgeti(L_, t_, _i); |
112 | luaL_addvalue(&_b); | 113 | luaL_addvalue(&_b); |
113 | } | 114 | } |
@@ -151,7 +152,7 @@ static void update_lookup_entry(lua_State* const L_, StackIndex const ctxBase_, | |||
151 | TableIndex const _deeper{ depth_ + 1 }; | 152 | TableIndex const _deeper{ depth_ + 1 }; |
152 | lua_rawseti(L_, _fqn, _deeper); // L_: ... {bfc} k o name? | 153 | lua_rawseti(L_, _fqn, _deeper); // L_: ... {bfc} k o name? |
153 | // generate name | 154 | // generate name |
154 | std::string_view const _newName{ tools::PushFQN(L_, _fqn, _deeper) }; // L_: ... {bfc} k o name? "f.q.n" | 155 | std::string_view const _newName{ tools::PushFQN(L_, _fqn) }; // L_: ... {bfc} k o name? "f.q.n" |
155 | // Lua 5.2 introduced a hash randomizer seed which causes table iteration to yield a different key order | 156 | // Lua 5.2 introduced a hash randomizer seed which causes table iteration to yield a different key order |
156 | // on different VMs even when the tables are populated the exact same way. | 157 | // on different VMs even when the tables are populated the exact same way. |
157 | // Also, when Lua is built with compatibility options (such as LUA_COMPAT_ALL), some base libraries register functions under multiple names. | 158 | // Also, when Lua is built with compatibility options (such as LUA_COMPAT_ALL), some base libraries register functions under multiple names. |
diff --git a/src/tools.hpp b/src/tools.hpp index 7955263..420b5f8 100644 --- a/src/tools.hpp +++ b/src/tools.hpp | |||
@@ -36,6 +36,6 @@ static constexpr RegistryUniqueKey kLookupRegKey{ 0xBF1FC5CF3C6DD47Bull }; // re | |||
36 | namespace tools { | 36 | namespace tools { |
37 | void PopulateFuncLookupTable(lua_State* L_, StackIndex i_, std::string_view const& name_); | 37 | void PopulateFuncLookupTable(lua_State* L_, StackIndex i_, std::string_view const& name_); |
38 | [[nodiscard]] | 38 | [[nodiscard]] |
39 | std::string_view PushFQN(lua_State* L_, StackIndex t_, TableIndex last_); | 39 | std::string_view PushFQN(lua_State* L_, StackIndex t_); |
40 | void SerializeRequire(lua_State* L_); | 40 | void SerializeRequire(lua_State* L_); |
41 | } // namespace tools | 41 | } // namespace tools |
diff --git a/unit_tests/lane_tests.cpp b/unit_tests/lane_tests.cpp index 77c7f61..63626ae 100644 --- a/unit_tests/lane_tests.cpp +++ b/unit_tests/lane_tests.cpp | |||
@@ -8,16 +8,25 @@ TEST_CASE("lanes.nameof") | |||
8 | LuaState S{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ false } }; | 8 | LuaState S{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ false } }; |
9 | S.requireSuccess("lanes = require 'lanes'.configure()"); | 9 | S.requireSuccess("lanes = require 'lanes'.configure()"); |
10 | 10 | ||
11 | // no argument is not good | ||
12 | S.requireFailure("local t, n = lanes.nameof()"); | ||
13 | |||
14 | // more than one argument is not good | ||
15 | S.requireFailure("local t, n = lanes.nameof(true, false)"); | ||
16 | |||
11 | // a constant is itself, stringified | 17 | // a constant is itself, stringified |
12 | S.requireReturnedString("local t, n = lanes.nameof('bob'); return t .. ': ' .. tostring(n)", "string: bob"); | 18 | S.requireReturnedString("local t, n = lanes.nameof('bob'); return t .. ': ' .. tostring(n)", "string: bob"); |
13 | S.requireReturnedString("local t, n = lanes.nameof(true); return t .. ': ' .. tostring(n)", "boolean: true"); | 19 | S.requireReturnedString("local t, n = lanes.nameof(true); return t .. ': ' .. tostring(n)", "boolean: true"); |
14 | S.requireReturnedString("local t, n = lanes.nameof(42); return t .. ': ' .. tostring(n)", "number: 42"); | 20 | S.requireReturnedString("local t, n = lanes.nameof(42); return t .. ': ' .. tostring(n)", "number: 42"); |
21 | |||
22 | // a temporary object has no name | ||
15 | S.requireReturnedString("local t, n = lanes.nameof({}); return t .. ': ' .. tostring(n)", "table: nil"); | 23 | S.requireReturnedString("local t, n = lanes.nameof({}); return t .. ': ' .. tostring(n)", "table: nil"); |
24 | S.requireReturnedString("local t, n = lanes.nameof(function() end); return t .. ': ' .. tostring(n)", "function: nil"); | ||
16 | 25 | ||
17 | // look for something in _G | 26 | // look for something in _G |
18 | S.requireReturnedString("local t, n = lanes.nameof(print); return t .. ': ' .. tostring(n)", "function: _G/print"); | 27 | S.requireReturnedString("local t, n = lanes.nameof(print); return t .. ': ' .. tostring(n)", "function: _G/print()"); |
19 | S.requireReturnedString("local t, n = lanes.nameof(string); return t .. ': ' .. tostring(n)", "table: _G/string"); | 28 | S.requireReturnedString("local t, n = lanes.nameof(string); return t .. ': ' .. tostring(n)", "table: _G/string[]"); |
20 | S.requireReturnedString("local t, n = lanes.nameof(string.sub); return t .. ': ' .. tostring(n)", "function: _G/string/sub"); | 29 | S.requireReturnedString("local t, n = lanes.nameof(string.sub); return t .. ': ' .. tostring(n)", "function: _G/string[]/sub()"); |
21 | } | 30 | } |
22 | 31 | ||
23 | // ################################################################################################# | 32 | // ################################################################################################# |