44#include " lualib.h"
55
66#include < algorithm>
7+ #include < optional>
78#include < string>
89#include < string_view>
910#include < variant>
@@ -136,6 +137,173 @@ namespace {
136137 return 1 ;
137138 }
138139
140+ // ── Manifest parsing (barWidget.define) ──────────────────────────────────
141+
142+ std::string tableStringField (lua_State* L, int tableIndex, const char * key, std::string fallback = {}) {
143+ lua_getfield (L, tableIndex, key);
144+ std::string out = lua_isstring (L, -1 ) ? std::string (lua_tostring (L, -1 )) : std::move (fallback);
145+ lua_pop (L, 1 );
146+ return out;
147+ }
148+
149+ bool tableBoolField (lua_State* L, int tableIndex, const char * key, bool fallback) {
150+ lua_getfield (L, tableIndex, key);
151+ bool out = lua_isnil (L, -1 ) ? fallback : (lua_toboolean (L, -1 ) != 0 );
152+ lua_pop (L, 1 );
153+ return out;
154+ }
155+
156+ std::optional<double > tableNumberField (lua_State* L, int tableIndex, const char * key) {
157+ lua_getfield (L, tableIndex, key);
158+ std::optional<double > out;
159+ if (lua_isnumber (L, -1 )) {
160+ out = lua_tonumber (L, -1 );
161+ }
162+ lua_pop (L, 1 );
163+ return out;
164+ }
165+
166+ scripting::ManifestFieldType parseFieldType (std::string_view type) {
167+ if (type == " bool" || type == " boolean" ) {
168+ return scripting::ManifestFieldType::Bool;
169+ }
170+ if (type == " int" || type == " integer" ) {
171+ return scripting::ManifestFieldType::Int;
172+ }
173+ if (type == " double" || type == " number" || type == " float" ) {
174+ return scripting::ManifestFieldType::Double;
175+ }
176+ if (type == " select" || type == " enum" ) {
177+ return scripting::ManifestFieldType::Select;
178+ }
179+ if (type == " color" ) {
180+ return scripting::ManifestFieldType::Color;
181+ }
182+ return scripting::ManifestFieldType::String;
183+ }
184+
185+ void parseFieldDefault (lua_State* L, int fieldIndex, scripting::ManifestField& field) {
186+ lua_getfield (L, fieldIndex, " default" );
187+ switch (field.type ) {
188+ case scripting::ManifestFieldType::Bool:
189+ field.boolDefault = lua_toboolean (L, -1 ) != 0 ;
190+ break ;
191+ case scripting::ManifestFieldType::Int:
192+ case scripting::ManifestFieldType::Double:
193+ field.numberDefault = lua_isnumber (L, -1 ) ? lua_tonumber (L, -1 ) : 0.0 ;
194+ break ;
195+ default :
196+ field.stringDefault = lua_isstring (L, -1 ) ? std::string (lua_tostring (L, -1 )) : std::string{};
197+ break ;
198+ }
199+ lua_pop (L, 1 );
200+ }
201+
202+ void parseFieldOptions (lua_State* L, int fieldIndex, scripting::ManifestField& field) {
203+ lua_getfield (L, fieldIndex, " options" );
204+ if (lua_istable (L, -1 )) {
205+ const int optionsIndex = lua_gettop (L);
206+ const int count = static_cast <int >(lua_objlen (L, optionsIndex));
207+ for (int i = 1 ; i <= count; ++i) {
208+ lua_rawgeti (L, optionsIndex, i);
209+ if (lua_istable (L, -1 )) {
210+ const int optIndex = lua_gettop (L);
211+ scripting::ManifestSelectOption opt;
212+ opt.value = tableStringField (L, optIndex, " value" );
213+ opt.label = tableStringField (L, optIndex, " label" , opt.value );
214+ if (!opt.value .empty ()) {
215+ field.options .push_back (std::move (opt));
216+ }
217+ } else if (lua_isstring (L, -1 )) {
218+ std::string value = lua_tostring (L, -1 );
219+ field.options .push_back (scripting::ManifestSelectOption{.value = value, .label = value});
220+ }
221+ lua_pop (L, 1 );
222+ }
223+ }
224+ lua_pop (L, 1 );
225+ }
226+
227+ void parseFieldVisibility (lua_State* L, int fieldIndex, scripting::ManifestField& field) {
228+ lua_getfield (L, fieldIndex, " visible_when" );
229+ if (lua_istable (L, -1 )) {
230+ const int visIndex = lua_gettop (L);
231+ scripting::ManifestVisibility vis;
232+ vis.key = tableStringField (L, visIndex, " key" );
233+ lua_getfield (L, visIndex, " values" );
234+ if (lua_istable (L, -1 )) {
235+ const int valuesIndex = lua_gettop (L);
236+ const int count = static_cast <int >(lua_objlen (L, valuesIndex));
237+ for (int i = 1 ; i <= count; ++i) {
238+ lua_rawgeti (L, valuesIndex, i);
239+ if (lua_isstring (L, -1 )) {
240+ vis.values .emplace_back (lua_tostring (L, -1 ));
241+ } else if (lua_isboolean (L, -1 )) {
242+ vis.values .emplace_back (lua_toboolean (L, -1 ) != 0 ? " true" : " false" );
243+ }
244+ lua_pop (L, 1 );
245+ }
246+ }
247+ lua_pop (L, 1 );
248+ if (!vis.key .empty () && !vis.values .empty ()) {
249+ field.visibleWhen = std::move (vis);
250+ }
251+ }
252+ lua_pop (L, 1 );
253+ }
254+
255+ void parseManifest (lua_State* L, int tableIndex, scripting::ScriptWidgetManifest& manifest) {
256+ manifest.label = tableStringField (L, tableIndex, " label" );
257+ manifest.icon = tableStringField (L, tableIndex, " icon" );
258+ manifest.description = tableStringField (L, tableIndex, " description" );
259+ manifest.pickable = tableBoolField (L, tableIndex, " pickable" , true );
260+
261+ lua_getfield (L, tableIndex, " settings" );
262+ if (lua_istable (L, -1 )) {
263+ const int settingsIndex = lua_gettop (L);
264+ const int count = static_cast <int >(lua_objlen (L, settingsIndex));
265+ for (int i = 1 ; i <= count; ++i) {
266+ lua_rawgeti (L, settingsIndex, i);
267+ if (lua_istable (L, -1 )) {
268+ const int fieldIndex = lua_gettop (L);
269+ scripting::ManifestField field;
270+ field.key = tableStringField (L, fieldIndex, " key" );
271+ if (!field.key .empty ()) {
272+ field.type = parseFieldType (tableStringField (L, fieldIndex, " type" , " string" ));
273+ field.label = tableStringField (L, fieldIndex, " label" , field.key );
274+ field.description = tableStringField (L, fieldIndex, " description" );
275+ field.advanced = tableBoolField (L, fieldIndex, " advanced" , false );
276+ field.minValue = tableNumberField (L, fieldIndex, " min" );
277+ field.maxValue = tableNumberField (L, fieldIndex, " max" );
278+ if (auto step = tableNumberField (L, fieldIndex, " step" ); step.has_value ()) {
279+ field.step = *step;
280+ }
281+ parseFieldDefault (L, fieldIndex, field);
282+ parseFieldOptions (L, fieldIndex, field);
283+ parseFieldVisibility (L, fieldIndex, field);
284+ manifest.settings .push_back (std::move (field));
285+ }
286+ }
287+ lua_pop (L, 1 );
288+ }
289+ }
290+ lua_pop (L, 1 );
291+ }
292+
293+ int luau_define (lua_State* L) {
294+ auto * context = getContext (L);
295+ if (context != nullptr && context->manifestOut != nullptr && lua_istable (L, 1 )) {
296+ *context->manifestOut = {};
297+ parseManifest (L, 1 , *context->manifestOut );
298+ context->defineCalled = true ;
299+ }
300+ // Abort the chunk during extraction so no top-level side effects run.
301+ if (context != nullptr && context->manifestExtractionMode ) {
302+ luaL_error (L, " __manifest_captured" );
303+ }
304+ return 0 ;
305+ }
306+
139307 const luaL_Reg kWidgetLib [] = {
140308 {" setText" , luau_setText},
141309 {" setGlyph" , luau_setGlyph},
@@ -146,6 +314,7 @@ namespace {
146314 {" setUpdateInterval" , luau_setUpdateInterval},
147315 {" setVisible" , luau_setVisible},
148316 {" getConfig" , luau_getConfig},
317+ {" define" , luau_define},
149318 {nullptr , nullptr },
150319 };
151320
0 commit comments