Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions VM/include/llruleset_builder.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,13 @@ void ruleset_builder_def_add_flags(
// Flag boolean properties are merged into their backing integer field before emission.
void slua_ruleset_serialize(lua_State* L, int params_idx, const RulesetBuilderDef* def);

// Coerce a dict-style params table to a flat rules list in-place.
// If the value at params_idx is a dict (table with no integer key 1), serializes it
// using slua_ruleset_serialize and replaces the stack slot with the result.
// If the value is already a sequential list, nil, or non-table, leaves it unchanged.
// Returns true if coercion was performed, false otherwise.
bool slua_ruleset_coerce(lua_State* L, int params_idx, const RulesetBuilderDef* def);

// Register fn as module_name.fn_name in L's globals, with def stored as upvalue 1.
// Creates the module table if it does not yet exist; adds to it if it does.
// Sets the module table readonly after each call.
Expand Down
113 changes: 74 additions & 39 deletions VM/src/llruleset_builder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,19 @@
#include <vector>
#include <unordered_map>
#include <algorithm>
#include <cstring>

#include "lua.h"
#include "lcommon.h"
#include "lualib.h"
#include "llsl.h"
#include "llruleset_builder.h"

#include "lapi.h"
#include "lobject.h"
#include "ltable.h"
#include "lstring.h"

struct RulesetBuilderDef
{
std::vector<RulesetParamDescriptor> descs; // sorted by tag; .name points into string literals
Expand Down Expand Up @@ -56,13 +62,17 @@ namespace
int slua_ruleset_serialize_string_csv(lua_State* L, int list, int idx,
const RulesetParamDescriptor& desc)
{
int n = (int)lua_objlen(L, -1);
LuaTable* t = hvalue(luaA_toobject(L, -1));
int n = luaH_getn(t);
if (n > 0)
{
std::string csv;
for (int j = 1; j <= n; ++j)
{
lua_rawgeti(L, -1, j);
const TValue* elem = luaH_getnum(t, j);
if (ttisnil(elem))
continue;
luaA_pushobject(L, elem);
if (slua_ruleset_to_string(L))
{
size_t len = 0;
Expand Down Expand Up @@ -105,19 +115,21 @@ namespace
{
// Collect keys for deterministic ordering.
std::vector<std::string> keys;
lua_pushnil(L);
while (lua_next(L, -2))
for (int index = 0; (index = lua_rawiter(L, -1, index)) >= 0;)
{
if (lua_type(L, -2) == LUA_TSTRING)
keys.push_back(lua_tostring(L, -2));
lua_pop(L, 1); // pop value, keep key for next iteration
lua_pop(L, 2);
}
std::sort(keys.begin(), keys.end());

LuaTable* t = hvalue(luaA_toobject(L, -1));
for (const auto& key : keys)
{
lua_pushlstring(L, key.c_str(), key.size());
lua_rawget(L, -2); // push value
const TValue* val = luaH_getstr(t, luaS_new(L, key.c_str()));
if (ttisnil(val))
continue;
luaA_pushobject(L, val);
if (!slua_ruleset_to_string(L))
continue;
lua_pushinteger(L, desc.tag);
Expand All @@ -136,10 +148,14 @@ namespace
int slua_ruleset_serialize_string_multi(lua_State* L, int list, int idx,
const RulesetParamDescriptor& desc)
{
int n = (int)lua_objlen(L, -1);
LuaTable* t = hvalue(luaA_toobject(L, -1));
int n = luaH_getn(t);
for (int j = 1; j <= n; ++j)
{
lua_rawgeti(L, -1, j);
const TValue* elem = luaH_getnum(t, j);
if (ttisnil(elem))
continue;
luaA_pushobject(L, elem);
if (slua_ruleset_to_string(L))
{
lua_pushinteger(L, desc.tag);
Expand Down Expand Up @@ -192,21 +208,20 @@ void slua_ruleset_serialize(lua_State* L, int params_idx, const RulesetBuilderDe
return; // empty list

// Phase 1: accumulate flag bits per backing field.
LuaTable* params_h = hvalue(luaA_toobject(L, params_idx));
std::unordered_map<int, int> flag_set_bits; // field_tag -> bits to OR in
std::unordered_map<int, int> flag_clear_bits; // field_tag -> bits to AND out
for (const auto& fdesc : def->flag_descs)
{
lua_rawgetfield(L, params_idx, fdesc.name);
if (!lua_isnil(L, -1))
const TValue* val = luaH_getstr(params_h, luaS_new(L, fdesc.name));
if (!ttisnil(val))
{
bool set = lua_isboolean(L, -1) ? (bool)lua_toboolean(L, -1)
: (lua_tointeger(L, -1) != 0);
bool set = ttisboolean(val) ? (bool)bvalue(val) : (nvalue(val) != 0.0);
if (set)
flag_set_bits[fdesc.field_tag] |= fdesc.mask;
else
flag_clear_bits[fdesc.field_tag] |= fdesc.mask;
}
lua_pop(L, 1);
}

// Phase 2: emit tag/value pairs in tag order.
Expand All @@ -215,12 +230,10 @@ void slua_ruleset_serialize(lua_State* L, int params_idx, const RulesetBuilderDe
int raw_int = 0;
bool has_raw = false;

lua_rawgetfield(L, params_idx, desc.name);
has_raw = !lua_isnil(L, -1);
if (has_raw && lua_isnumber(L, -1))
raw_int = (int)lua_tointeger(L, -1);
if (!has_raw)
lua_pop(L, 1);
const TValue* val = luaH_getstr(params_h, luaS_new(L, desc.name));
has_raw = !ttisnil(val);
if (has_raw && ttisnumber(val))
raw_int = (int)nvalue(val);

// Integer fields that back flags: merge accumulated bits.
auto set_it = flag_set_bits.find(desc.tag);
Expand All @@ -231,7 +244,6 @@ void slua_ruleset_serialize(lua_State* L, int params_idx, const RulesetBuilderDe
int set_mask = (set_it != flag_set_bits.end()) ? set_it->second : 0;
int clear_mask = (clear_it != flag_clear_bits.end()) ? clear_it->second : 0;
int merged = (raw_int | set_mask) & ~clear_mask;
if (has_raw) lua_pop(L, 1);
lua_pushinteger(L, desc.tag);
lua_rawseti(L, list, ++idx);
lua_pushinteger(L, merged);
Expand All @@ -243,12 +255,9 @@ void slua_ruleset_serialize(lua_State* L, int params_idx, const RulesetBuilderDe
// Encode as comma-joined string; emit one tag/value pair.
if (desc.semantic == 'C')
{
if (!has_raw || !lua_istable(L, -1))
{
if (has_raw) lua_pop(L, 1);
if (!has_raw || !ttistable(val))
continue;
}

luaA_pushobject(L, val);
idx = slua_ruleset_serialize_string_csv(L, list, idx, desc);
continue;
}
Expand All @@ -257,11 +266,9 @@ void slua_ruleset_serialize(lua_State* L, int params_idx, const RulesetBuilderDe
// Emit one tag/key/value triple per entry, in sorted key order.
if (desc.semantic == 'M')
{
if (!has_raw || !lua_istable(L, -1))
{
if (has_raw) lua_pop(L, 1);
if (!has_raw || !ttistable(val))
continue;
}
luaA_pushobject(L, val);
idx = slua_ruleset_serialize_string_map(L, list, idx, desc);
continue;
}
Expand All @@ -270,11 +277,9 @@ void slua_ruleset_serialize(lua_State* L, int params_idx, const RulesetBuilderDe
// Emit one tag/value pair per element, preserving order.
if (desc.semantic == 'N')
{
if (!has_raw || !lua_istable(L, -1))
{
if (has_raw) lua_pop(L, 1);
if (!has_raw || !ttistable(val))
continue;
}
luaA_pushobject(L, val);
idx = slua_ruleset_serialize_string_multi(L, list, idx, desc);
continue;
}
Expand All @@ -286,16 +291,46 @@ void slua_ruleset_serialize(lua_State* L, int params_idx, const RulesetBuilderDe
lua_pushinteger(L, desc.tag);
lua_rawseti(L, list, ++idx);

if (desc.semantic == 'b' && lua_isboolean(L, -1))
lua_pushinteger(L, lua_toboolean(L, -1));
if (desc.semantic == 'b' && ttisboolean(val))
lua_pushinteger(L, bvalue(val));
else
lua_pushvalue(L, -1);
luaA_pushobject(L, val);
lua_rawseti(L, list, ++idx);

lua_pop(L, 1);
}
}

bool slua_ruleset_coerce(lua_State* L, int params_idx, const RulesetBuilderDef* def)
{
// Convert relative index to absolute
if (params_idx < 0 && params_idx > LUA_REGISTRYINDEX)
params_idx = lua_gettop(L) + params_idx + 1;

// Not a table? Leave unchanged.
if (!lua_istable(L, params_idx))
return false;

// Check if this is a dict (no integer key 1) vs sequential list (has key 1).
LuaTable* h = hvalue(luaA_toobject(L, params_idx));
bool has_first = !ttisnil(luaH_getnum(h, 1));

if (has_first)
return false; // Sequential list, leave unchanged

// Check if table has any keys at all.
bool has_keys = (lua_rawiter(L, params_idx, 0) >= 0);
if (!has_keys)
return false; // Empty table, already a valid empty list
lua_pop(L, 2); // Pop key and value pushed by lua_rawiter

// Serialize dict to flat list (pushes new table on stack)
slua_ruleset_serialize(L, params_idx, def);

// Replace original table with serialized list
lua_replace(L, params_idx);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this function allows pseudo-indices, automatically calling lua_replace() on params_idx internally may not always be what you want (for ex. with upvalues and whatnot.) Maybe not a problem in practice but something to keep in mind.


return true;
}

void slua_register_ruleset_fn(
lua_State* L,
const char* module_name,
Expand Down
6 changes: 3 additions & 3 deletions autobuild.xml
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,11 @@
<key>archive</key>
<map>
<key>hash</key>
<string>f8949fca1ee107be2b878f38b916d7fe254be03f</string>
<string>c9928095cf40377e901458f5240305a7af2c554d</string>
<key>hash_algorithm</key>
<string>sha1</string>
<key>url</key>
<string>https://github.com/secondlife/lsl-definitions/releases/download/v0.6.11/lsl_definitions-0.6.11-common-28811066278.tar.zst</string>
<string>https://github.com/secondlife/lsl-definitions/releases/download/v0.6.12/lsl_definitions-0.6.12-common-29281156609.tar.zst</string>
</map>
<key>name</key>
<string>common</string>
Expand All @@ -119,7 +119,7 @@
<key>copyright</key>
<string>Copyright (c) 2026, Linden Research, Inc.</string>
<key>version</key>
<string>0.6.11</string>
<string>0.6.12</string>
<key>use_scm_version</key>
<boolean>true</boolean>
<key>name</key>
Expand Down
Loading
Loading