diff options
Diffstat (limited to 'src/nameof.cpp')
-rw-r--r-- | src/nameof.cpp | 208 |
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 | |||
4 | Copyright (C) 2024 benoit Germain <bnt.germain@gmail.com> | ||
5 | |||
6 | Permission is hereby granted, free of charge, to any person obtaining a copy | ||
7 | of this software and associated documentation files (the "Software"), to deal | ||
8 | in the Software without restriction, including without limitation the rights | ||
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
10 | copies of the Software, and to permit persons to whom the Software is | ||
11 | furnished to do so, subject to the following conditions: | ||
12 | |||
13 | The above copyright notice and this permission notice shall be included in | ||
14 | all copies or substantial portions of the Software. | ||
15 | |||
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
22 | THE 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) | ||
167 | LUAG_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 | |||