Parse the `parameters` array of the class table at `class_idx` into specs. Returns an error string on a malformed entry, empty on success.
| 264 | // Parse the `parameters` array of the class table at `class_idx` into specs. |
| 265 | // Returns an error string on a malformed entry, empty on success. |
| 266 | std::string readParameters(lua_State* L, int class_idx, std::vector<ParamSpec>& out) { |
| 267 | lua_rawgetfield(L, class_idx, "parameters"); |
| 268 | if (lua_isnil(L, -1)) { |
| 269 | lua_pop(L, 1); |
| 270 | return {}; // absent => no params |
| 271 | } |
| 272 | if (!lua_istable(L, -1)) { |
| 273 | lua_pop(L, 1); |
| 274 | return "`parameters` must be a table"; |
| 275 | } |
| 276 | const int params_idx = lua_gettop(L); |
| 277 | const int count = arrayLen(L, params_idx); |
| 278 | for (int i = 1; i <= count; ++i) { |
| 279 | lua_rawgeti(L, params_idx, i); |
| 280 | const int p = lua_gettop(L); |
| 281 | if (!lua_istable(L, p)) { // raw field reads below require a real table |
| 282 | lua_pop(L, 2); // entry + the `parameters` table |
| 283 | return "a `parameters` entry is not a table"; |
| 284 | } |
| 285 | ParamSpec spec; |
| 286 | spec.name = stringField(L, p, "name"); |
| 287 | if (spec.name.empty()) { |
| 288 | lua_pop(L, 2); // param entry + the `parameters` table (leave stack as found) |
| 289 | return "a parameter is missing its `name`"; |
| 290 | } |
| 291 | const std::string type_str = stringField(L, p, "type", "number"); |
| 292 | if (!parseParamType(type_str, spec.type)) { |
| 293 | lua_pop(L, 2); // param entry + the `parameters` table |
| 294 | return "unknown parameter type '" + type_str + "' for '" + spec.name + "'"; |
| 295 | } |
| 296 | spec.label = stringField(L, p, "label", spec.name); |
| 297 | spec.tooltip = stringField(L, p, "tooltip"); |
| 298 | spec.unit = stringField(L, p, "unit"); |
| 299 | spec.min = numberField(L, p, "min"); |
| 300 | spec.max = numberField(L, p, "max"); |
| 301 | spec.step = numberField(L, p, "step"); |
| 302 | if (auto dec = numberField(L, p, "decimals"); dec && std::isfinite(*dec)) { |
| 303 | spec.decimals = static_cast<int>(std::clamp(*dec, 0.0, 17.0)); // guard NaN/huge → int UB |
| 304 | } |
| 305 | lua_rawgetfield(L, p, "default"); |
| 306 | spec.default_value = luaScalarToJson(L, lua_gettop(L)); |
| 307 | lua_pop(L, 1); |
| 308 | if (spec.type == ParamType::kEnum) { |
| 309 | readEnumValues(L, p, spec); |
| 310 | } |
| 311 | // visible_when = { param = "...", equals = <scalar> } |
| 312 | lua_rawgetfield(L, p, "visible_when"); |
| 313 | if (lua_istable(L, -1)) { |
| 314 | const int vw = lua_gettop(L); |
| 315 | std::string vw_param = stringField(L, vw, "param"); |
| 316 | if (!vw_param.empty()) { |
| 317 | spec.visible_when_param = vw_param; |
| 318 | lua_rawgetfield(L, vw, "equals"); |
| 319 | spec.visible_when_equals = luaScalarToJson(L, lua_gettop(L)); |
| 320 | lua_pop(L, 1); |
| 321 | } |
| 322 | } |
| 323 | lua_pop(L, 1); // visible_when |
no test coverage detected