aboutsummaryrefslogtreecommitdiff
path: root/src/nameof.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/nameof.cpp')
-rw-r--r--src/nameof.cpp208
1 files changed, 208 insertions, 0 deletions
diff --git a/src/nameof.cpp b/src/nameof.cpp
new file mode 100644
index 0000000..7614577
--- /dev/null
+++ b/src/nameof.cpp
@@ -0,0 +1,208 @@
1/*
2===============================================================================
3
4Copyright (C) 2024 benoit Germain <bnt.germain@gmail.com>
5
6Permission is hereby granted, free of charge, to any person obtaining a copy
7of this software and associated documentation files (the "Software"), to deal
8in the Software without restriction, including without limitation the rights
9to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10copies of the Software, and to permit persons to whom the Software is
11furnished to do so, subject to the following conditions:
12
13The above copyright notice and this permission notice shall be included in
14all copies or substantial portions of the Software.
15
16THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22THE SOFTWARE.
23
24===============================================================================
25*/
26
27#include "nameof.h"
28
29#include "tools.h"
30
31// #################################################################################################
32
33// Return some name helping to identify an object
34[[nodiscard]] static int DiscoverObjectNameRecur(lua_State* L_, int shortest_, int depth_)
35{
36 static constexpr int kWhat{ 1 }; // the object to investigate // L_: o "r" {c} {fqn} ... {?}
37 static constexpr int kResult{ 2 }; // where the result string is stored
38 static constexpr int kCache{ 3 }; // a cache
39 static constexpr int kFQN{ 4 }; // the name compositing stack
40 // no need to scan this table if the name we will discover is longer than one we already know
41 if (shortest_ <= depth_ + 1) {
42 return shortest_;
43 }
44 STACK_GROW(L_, 3);
45 STACK_CHECK_START_REL(L_, 0);
46 // stack top contains the table to search in
47 lua_pushvalue(L_, -1); // L_: o "r" {c} {fqn} ... {?} {?}
48 lua_rawget(L_, kCache); // L_: o "r" {c} {fqn} ... {?} nil/1
49 // if table is already visited, we are done
50 if (!lua_isnil(L_, -1)) {
51 lua_pop(L_, 1); // L_: o "r" {c} {fqn} ... {?}
52 return shortest_;
53 }
54 // examined table is not in the cache, add it now
55 lua_pop(L_, 1); // L_: o "r" {c} {fqn} ... {?}
56 lua_pushvalue(L_, -1); // L_: o "r" {c} {fqn} ... {?} {?}
57 lua_pushinteger(L_, 1); // L_: o "r" {c} {fqn} ... {?} {?} 1
58 lua_rawset(L_, kCache); // L_: o "r" {c} {fqn} ... {?}
59 // scan table contents
60 lua_pushnil(L_); // L_: o "r" {c} {fqn} ... {?} nil
61 while (lua_next(L_, -2)) { // L_: o "r" {c} {fqn} ... {?} k v
62 // std::string_view const _strKey{ (lua_type(L_, -2) == LUA_TSTRING) ? lua_tostringview(L_, -2) : "" }; // only for debugging
63 // lua_Number const numKey = (lua_type(L_, -2) == LUA_TNUMBER) ? lua_tonumber(L_, -2) : -6666; // only for debugging
64 STACK_CHECK(L_, 2);
65 // append key name to fqn stack
66 ++depth_;
67 lua_pushvalue(L_, -2); // L_: o "r" {c} {fqn} ... {?} k v k
68 lua_rawseti(L_, kFQN, depth_); // L_: o "r" {c} {fqn} ... {?} k v
69 if (lua_rawequal(L_, -1, kWhat)) { // is it what we are looking for?
70 STACK_CHECK(L_, 2);
71 // update shortest name
72 if (depth_ < shortest_) {
73 shortest_ = depth_;
74 std::ignore = tools::PushFQN(L_, kFQN, depth_); // L_: o "r" {c} {fqn} ... {?} k v "fqn"
75 lua_replace(L_, kResult); // L_: o "r" {c} {fqn} ... {?} k v
76 }
77 // no need to search further at this level
78 lua_pop(L_, 2); // L_: o "r" {c} {fqn} ... {?}
79 STACK_CHECK(L_, 0);
80 break;
81 }
82 switch (lua_type(L_, -1)) { // L_: o "r" {c} {fqn} ... {?} k v
83 default: // nil, boolean, light userdata, number and string aren't identifiable
84 break;
85
86 case LUA_TTABLE: // L_: o "r" {c} {fqn} ... {?} k {}
87 STACK_CHECK(L_, 2);
88 shortest_ = DiscoverObjectNameRecur(L_, shortest_, depth_);
89 // search in the table's metatable too
90 if (lua_getmetatable(L_, -1)) { // L_: o "r" {c} {fqn} ... {?} k {} {mt}
91 if (lua_istable(L_, -1)) {
92 ++depth_;
93 lua_pushliteral(L_, "__metatable"); // L_: o "r" {c} {fqn} ... {?} k {} {mt} "__metatable"
94 lua_rawseti(L_, kFQN, depth_); // L_: o "r" {c} {fqn} ... {?} k {} {mt}
95 shortest_ = DiscoverObjectNameRecur(L_, shortest_, depth_);
96 lua_pushnil(L_); // L_: o "r" {c} {fqn} ... {?} k {} {mt} nil
97 lua_rawseti(L_, kFQN, depth_); // L_: o "r" {c} {fqn} ... {?} k {} {mt}
98 --depth_;
99 }
100 lua_pop(L_, 1); // L_: o "r" {c} {fqn} ... {?} k {}
101 }
102 STACK_CHECK(L_, 2);
103 break;
104
105 case LUA_TTHREAD: // L_: o "r" {c} {fqn} ... {?} k T
106 // TODO: explore the thread's stack frame looking for our culprit?
107 break;
108
109 case LUA_TUSERDATA: // L_: o "r" {c} {fqn} ... {?} k U
110 STACK_CHECK(L_, 2);
111 // search in the object's metatable (some modules are built that way)
112 if (lua_getmetatable(L_, -1)) { // L_: o "r" {c} {fqn} ... {?} k U {mt}
113 if (lua_istable(L_, -1)) {
114 ++depth_;
115 lua_pushliteral(L_, "__metatable"); // L_: o "r" {c} {fqn} ... {?} k U {mt} "__metatable"
116 lua_rawseti(L_, kFQN, depth_); // L_: o "r" {c} {fqn} ... {?} k U {mt}
117 shortest_ = DiscoverObjectNameRecur(L_, shortest_, depth_);
118 lua_pushnil(L_); // L_: o "r" {c} {fqn} ... {?} k U {mt} nil
119 lua_rawseti(L_, kFQN, depth_); // L_: o "r" {c} {fqn} ... {?} k U {mt}
120 --depth_;
121 }
122 lua_pop(L_, 1); // L_: o "r" {c} {fqn} ... {?} k U
123 }
124 STACK_CHECK(L_, 2);
125 // search in the object's uservalues
126 {
127 int _uvi{ 1 };
128 while (lua_getiuservalue(L_, -1, _uvi) != LUA_TNONE) { // L_: o "r" {c} {fqn} ... {?} k U {u}
129 if (lua_istable(L_, -1)) { // if it is a table, look inside
130 ++depth_;
131 lua_pushliteral(L_, "uservalue"); // L_: o "r" {c} {fqn} ... {?} k v {u} "uservalue"
132 lua_rawseti(L_, kFQN, depth_); // L_: o "r" {c} {fqn} ... {?} k v {u}
133 shortest_ = DiscoverObjectNameRecur(L_, shortest_, depth_);
134 lua_pushnil(L_); // L_: o "r" {c} {fqn} ... {?} k v {u} nil
135 lua_rawseti(L_, kFQN, depth_); // L_: o "r" {c} {fqn} ... {?} k v {u}
136 --depth_;
137 }
138 lua_pop(L_, 1); // L_: o "r" {c} {fqn} ... {?} k U
139 ++_uvi;
140 }
141 // when lua_getiuservalue() returned LUA_TNONE, it pushed a nil. pop it now
142 lua_pop(L_, 1); // L_: o "r" {c} {fqn} ... {?} k U
143 }
144 STACK_CHECK(L_, 2);
145 break;
146 }
147 // make ready for next iteration
148 lua_pop(L_, 1); // L_: o "r" {c} {fqn} ... {?} k
149 // remove name from fqn stack
150 lua_pushnil(L_); // L_: o "r" {c} {fqn} ... {?} k nil
151 lua_rawseti(L_, kFQN, depth_); // L_: o "r" {c} {fqn} ... {?} k
152 STACK_CHECK(L_, 1);
153 --depth_;
154 } // L_: o "r" {c} {fqn} ... {?}
155 STACK_CHECK(L_, 0);
156 // remove the visited table from the cache, in case a shorter path to the searched object exists
157 lua_pushvalue(L_, -1); // L_: o "r" {c} {fqn} ... {?} {?}
158 lua_pushnil(L_); // L_: o "r" {c} {fqn} ... {?} {?} nil
159 lua_rawset(L_, kCache); // L_: o "r" {c} {fqn} ... {?}
160 STACK_CHECK(L_, 0);
161 return shortest_;
162}
163
164// #################################################################################################
165
166// "type", "name" = lanes.nameof(o)
167LUAG_FUNC(nameof)
168{
169 int const _what{ lua_gettop(L_) };
170 if (_what > 1) {
171 raise_luaL_argerror(L_, _what, "too many arguments.");
172 }
173
174 // nil, boolean, light userdata, number and string aren't identifiable
175 if (lua_type(L_, 1) < LUA_TTABLE) {
176 lua_pushstring(L_, luaL_typename(L_, 1)); // L_: o "type"
177 lua_insert(L_, -2); // L_: "type" o
178 return 2;
179 }
180
181 STACK_GROW(L_, 4);
182 STACK_CHECK_START_REL(L_, 0);
183 // this slot will contain the shortest name we found when we are done
184 lua_pushnil(L_); // L_: o nil
185 // push a cache that will contain all already visited tables
186 lua_newtable(L_); // L_: o nil {c}
187 // push a table whose contents are strings that, when concatenated, produce unique name
188 lua_newtable(L_); // L_: o nil {c} {fqn}
189 // {fqn}[1] = "_G"
190 lua_pushliteral(L_, LUA_GNAME); // L_: o nil {c} {fqn} "_G"
191 lua_rawseti(L_, -2, 1); // L_: o nil {c} {fqn}
192 // this is where we start the search
193 lua_pushglobaltable(L_); // L_: o nil {c} {fqn} _G
194 std::ignore = DiscoverObjectNameRecur(L_, std::numeric_limits<int>::max(), 1);
195 if (lua_isnil(L_, 2)) { // try again with registry, just in case...
196 lua_pop(L_, 1); // L_: o nil {c} {fqn}
197 lua_pushliteral(L_, "_R"); // L_: o nil {c} {fqn} "_R"
198 lua_rawseti(L_, -2, 1); // L_: o nil {c} {fqn}
199 lua_pushvalue(L_, LUA_REGISTRYINDEX); // L_: o nil {c} {fqn} _R
200 std::ignore = DiscoverObjectNameRecur(L_, std::numeric_limits<int>::max(), 1);
201 }
202 lua_pop(L_, 3); // L_: o "result"
203 STACK_CHECK(L_, 1);
204 lua_pushstring(L_, luaL_typename(L_, 1)); // L_: o "result" "type"
205 lua_replace(L_, -3); // L_: "type" "result"
206 return 2;
207}
208