Download CommonLua/Core/ToLuaCode.lua from sirnii/myspace: direct link, hf CLI and curl.
- Browser
- Download file 33.6 kB
-
https://huggingface.co/spaces/sirnii/myspace/resolve/main/CommonLua/Core/ToLuaCode.lua
- Command line
-
hf download hf://spaces/sirnii/myspace/CommonLua/Core/ToLuaCode.lua
-
curl -L -o ToLuaCode.lua https://huggingface.co/spaces/sirnii/myspace/resolve/main/CommonLua/Core/ToLuaCode.lua
33.6 kB
| ---------------------------- | |
| ---- CODE SERIALIZATION ---- | |
| ---------------------------- | |
| --- A table of Lua keywords that are reserved and cannot be used as identifiers. | |
| -- This table is used to check if a given string is a valid identifier name. | |
| -- @table LuaKeywords | |
| -- @field and The `and` keyword. | |
| -- @field break The `break` keyword. | |
| -- @field do The `do` keyword. | |
| -- @field else The `else` keyword. | |
| -- @field elseif The `elseif` keyword. | |
| -- @field end The `end` keyword. | |
| -- @field false The `false` keyword. | |
| -- @field for The `for` keyword. | |
| -- @field function The `function` keyword. | |
| -- @field goto The `goto` keyword. | |
| -- @field if The `if` keyword. | |
| -- @field in The `in` keyword. | |
| -- @field local The `local` keyword. | |
| -- @field nil The `nil` keyword. | |
| -- @field not The `not` keyword. | |
| -- @field or The `or` keyword. | |
| -- @field repeat The `repeat` keyword. | |
| -- @field return The `return` keyword. | |
| -- @field then The `then` keyword. | |
| -- @field true The `true` keyword. | |
| -- @field until The `until` keyword. | |
| -- @field while The `while` keyword. | |
| LuaKeywords = { | |
| ["and"] = true, | |
| ["break"] = true, | |
| ["do"] = true, | |
| ["else"] = true, | |
| ["elseif"] = true, | |
| ["end"] = true, | |
| ["false"] = true, | |
| ["for"] = true, | |
| ["function"] = true, | |
| ["goto"] = true, | |
| ["if"] = true, | |
| ["in"] = true, | |
| ["local"] = true, | |
| ["nil"] = true, | |
| ["not"] = true, | |
| ["or"] = true, | |
| ["repeat"] = true, | |
| ["return"] = true, | |
| ["then"] = true, | |
| ["true"] = true, | |
| ["until"] = true, | |
| ["while"] = true, | |
| } | |
| --- Assigns the `LuaKeywords` table to the local variable `LuaKeywords`. | |
| -- This allows the `LuaKeywords` table to be used within the current scope without needing to reference the global table. | |
| -- @local | |
| -- @tparam table LuaKeywords The table of Lua keywords that are reserved and cannot be used as identifiers. | |
| local LuaKeywords = LuaKeywords | |
| --- Checks if the given key is a valid Lua identifier name. | |
| -- A valid identifier name must start with a letter or underscore, and can contain letters, digits, and underscores. | |
| -- Additionally, the identifier must not be a reserved Lua keyword. | |
| -- @param key The key to check. | |
| -- @return `true` if the key is a valid identifier name, `false` otherwise. | |
| function IsIdentifierName(key) | |
| return string.match(key, "^[_%a][_%w]+$") and not LuaKeywords[key] | |
| end | |
| --- Assigns the `LuaKeywords` table to the local variable `LuaKeywords`. | |
| -- This allows the `LuaKeywords` table to be used within the current scope without needing to reference the global table. | |
| -- @local | |
| -- @tparam table LuaKeywords The table of Lua keywords that are reserved and cannot be used as identifiers. | |
| local IsIdentifierName = IsIdentifierName | |
| --- Formats a key for use in a Lua table. | |
| -- The function takes a key `k` and an optional `pstr` parameter, and returns a string that represents the key in Lua code. | |
| -- If the key is a number, it is formatted as `[<number>] = `. | |
| -- If the key is a boolean, it is formatted as `[true] = ` or `[false] = `. | |
| -- If the key is a valid identifier name, it is formatted as `<key> = `. | |
| -- If the key is a string that is not a valid identifier name, it is formatted as `[<string>] = `. | |
| -- @param k The key to format. | |
| -- @param pstr An optional `pstr` object to append the formatted key to. | |
| -- @return The formatted key as a string. | |
| function FormatKey(k, pstr) | |
| local type = type(k) | |
| assert(type == "number" or type == "boolean" or type == "string", "Trying to use an object for a key?") | |
| if type == "number" then | |
| if not pstr then | |
| return string.format("[%d] = ", k) | |
| else | |
| return pstr:appendf("[%d] = ", k) | |
| end | |
| elseif type == "boolean" then | |
| local fkey = k and "[true] = " or "[false] = " | |
| if not pstr then | |
| return fkey | |
| else | |
| return pstr:append(fkey) | |
| end | |
| elseif IsIdentifierName(k) then | |
| if not pstr then | |
| return k .. " = " | |
| else | |
| return pstr:append(k, " = ") | |
| end | |
| else | |
| if not pstr then | |
| return string.format("[%s] = ", StringToLuaCode(k)) | |
| else | |
| pstr:append("[") | |
| pstr:appends(k) | |
| return pstr:append("] = ") | |
| end | |
| end | |
| end | |
| --- | |
| --- Evaluates a property value, handling cases where the value is a function. | |
| --- If the value is a function, it is called with the `obj` and `prop_meta` arguments, | |
| --- and the result is returned. If the function call fails, the `def` value is returned. | |
| --- | |
| --- @param value any The property value to evaluate. | |
| --- @param obj table The object that the property belongs to. | |
| --- @param prop_meta table The metadata for the property. | |
| --- @param def any The default value to return if the function call fails. | |
| --- @return any The evaluated property value. | |
| --- | |
| function prop_eval(value, obj, prop_meta, def) | |
| while type(value) == "function" do | |
| local ok | |
| ok, value = procall(value, obj, prop_meta) | |
| if not ok then | |
| return def | |
| end | |
| end | |
| return value | |
| end | |
| local eval = prop_eval | |
| --[[@@@ | |
| Converts a value to an executable Lua expresion. | |
| Use _'LuaCodeToTuple()'_ for evaluating the Lua expression. | |
| @function string code ValueToLuaCode(value, int indent) | |
| @param value - value to be converted. | |
| @param int indent - initial number of indentations. | |
| @param pstr pstr - pstr string to serialize into. | |
| @param table injected_props - table with metadata of properties injected from parent objects. | |
| @result string code - the supplied value, converted to Lua code. | |
| ]] | |
| --- | |
| --- Converts a value to an executable Lua expression. | |
| --- | |
| --- @param value any The value to be converted to Lua code. | |
| --- @param indent number The initial number of indentations. | |
| --- @param pstr pstr The pstr string to serialize the value into. | |
| --- @param injected_props table The table with metadata of properties injected from parent objects. | |
| --- @return string The supplied value, converted to Lua code. | |
| --- | |
| function ValueToLuaCode(value, indent, pstr, injected_props) | |
| assert(not pstr or type(pstr) == "userdata") | |
| if pstr then | |
| return pstr:appendv(value, indent, injected_props) | |
| end | |
| local vtype = type(value) | |
| if vtype == "nil" then | |
| return "nil" | |
| end | |
| if vtype == "boolean" then | |
| return value and "true" or "false" | |
| end | |
| if vtype == "number" then | |
| return tostring(value) | |
| end | |
| if vtype == "string" then | |
| return StringToLuaCode(value) | |
| end | |
| if vtype == "function" then | |
| return GetFuncSourceString(value, "") | |
| end | |
| if vtype == "userdata" or vtype == "table" then | |
| local meta = getmetatable(value) | |
| local __toluacode = meta and meta.__toluacode | |
| if __toluacode then | |
| return __toluacode(value, indent, nil, nil, injected_props) | |
| end | |
| if vtype == "table" then | |
| return TableToLuaCode(value, indent, nil, injected_props) | |
| end | |
| if IsPStr(value) then | |
| return StringToLuaCode(value) | |
| end | |
| if Request_IsTask(value) then | |
| return value:GetResource() | |
| end | |
| assert(false, "User data with missing __toluacode method!") | |
| return "nil" | |
| end | |
| end | |
| --- | |
| --- Converts an array of objects to a Lua expression that represents their handles. | |
| --- | |
| --- @param value table The array of objects to convert. | |
| --- @param ret table The table to append the Lua expressions to. | |
| --- | |
| local function ObjectHandlesHelperNoPstr(value, ret) | |
| for i=1, #value do | |
| if type(value[i]) == "boolean" or (not IsValid(value[i]) or value[i]:GetGameFlags(const.gofPermanent) == 0) then | |
| ret[#ret + 1] = "false," | |
| elseif not value[i].handle then | |
| assert(false, "serializing object without handle") | |
| else | |
| ret[#ret + 1] = string.format("o(%d),", value[i].handle) | |
| end | |
| end | |
| end | |
| --- | |
| --- Converts an array of objects to a Lua expression that represents their handles. | |
| --- | |
| --- @param value table The array of objects to convert. | |
| --- @param pstr userdata The string builder to append the Lua expressions to. | |
| --- | |
| local function ObjectHandlesHelperPstr(value, pstr) | |
| for i=1, #value do | |
| if type(value[i]) == "boolean" or (not IsValid(value[i]) or value[i]:GetGameFlags(const.gofPermanent) == 0) then | |
| pstr:append("false,") | |
| elseif not value[i].handle then | |
| assert(false, "serializing object without handle") | |
| else | |
| pstr:appendf("o(%d),", value[i].handle) | |
| end | |
| end | |
| end | |
| --- | |
| --- Processes the indentation level by adding an additional indent level. | |
| --- | |
| --- @param indent number|string The current indentation level, either as a number or a string. | |
| --- @return string The new indentation level with an additional indent. | |
| --- | |
| local function ProcessIndentPlusOneHelper(indent) | |
| local ret = "" | |
| if type(indent) == "string" then | |
| ret = string.format("%s%s", indent, " ") | |
| else | |
| for i = 1, indent + 1 do | |
| ret = string.format("%s%s", ret, " ") | |
| end | |
| end | |
| return ret | |
| end | |
| --- | |
| --- Converts a value to a Lua code expression based on the specified value type. | |
| --- | |
| --- @param value any The value to be converted to a Lua code expression. | |
| --- @param vtype string The type of the value. Can be one of the following: "bool", "boolean", "string", "text", "rgbrm", "packedcurve", "color", "set", "object", "objects", "range", "browse", "func", "expression". | |
| --- @param indent number|string The current indentation level, either as a number or a string. | |
| --- @param pstr userdata The string builder to append the Lua expressions to. | |
| --- @param prop_meta table The metadata for the property being serialized. | |
| --- @param obj table The object that the property belongs to. | |
| --- @param injected_props table Any additional properties to be injected into the Lua code. | |
| --- @return string The Lua code expression representing the value. | |
| --- | |
| function PropToLuaCode(value, vtype, indent, pstr, prop_meta, obj, injected_props) | |
| assert(not vtype or type(vtype) == "string") | |
| assert(not pstr or type(pstr) == "userdata") | |
| if vtype == "bool" or vtype == "boolean" then | |
| return ValueToLuaCode(not not value, indent, pstr) | |
| end | |
| if vtype == "string" or vtype == "text" then | |
| if type(value) ~= "string" and type(value) ~= "number" and type(value) ~= "boolean" then | |
| return TToLuaCode(value, ContextCache[value], pstr) | |
| end | |
| end | |
| if vtype == "rgbrm" then | |
| local r, g, b, ro, m = GetRGBRM(value) | |
| local a = GetAlpha(value) | |
| assert(a == 255) | |
| if ro == 0 and m == 0 then | |
| local fmt = "RGBA(%d, %d, %d, %d)" | |
| if not pstr then | |
| return string.format(fmt, r, g, b, a) | |
| else | |
| return pstr:appendf(fmt, r, g, b, a) | |
| end | |
| end | |
| local fmt = "RGBRM(%d, %d, %d, %d, %d)" | |
| if not pstr then | |
| return string.format(fmt, r, g, b, ro, m) | |
| else | |
| return pstr:appendf(fmt, r, g, b, ro, m) | |
| end | |
| end | |
| if vtype == "packedcurve" then | |
| local pt1, pt2, pt3, pt4, max_y = UnpackCurveParams(value) | |
| local fmt = "PackCurveParams(%d, %d, %d, %d, %d, %d, %d, %d, %d)" | |
| if not pstr then | |
| return string.format(fmt, pt1:x(), pt1:y(), pt2:x(), pt2:y(), pt3:x(), pt3:y(), pt4:x(), pt4:y(), max_y) | |
| else | |
| return pstr:appendf(fmt, pt1:x(), pt1:y(), pt2:x(), pt2:y(), pt3:x(), pt3:y(), pt4:x(), pt4:y(), max_y) | |
| end | |
| end | |
| if vtype == "color" then | |
| local r, g, b, a = GetRGBA(value) | |
| local fmt = "RGBA(%d, %d, %d, %d)" | |
| if not pstr then | |
| return string.format(fmt, r, g, b, a) | |
| else | |
| return pstr:appendf(fmt, r, g, b, a) | |
| end | |
| end | |
| if vtype == "set" then | |
| value = IsSet(value) and value or setmetatable(value, __set_meta) | |
| return ValueToLuaCode(value, indent, pstr) | |
| end | |
| if vtype == "object" then | |
| if not value.handle then | |
| assert(false, "serializing object without handle") | |
| return | |
| end | |
| if not pstr then | |
| return string.format("o(%d)", value.handle) | |
| else | |
| return pstr:appendf("o(%d)", value.handle) | |
| end | |
| end | |
| if vtype == "objects" then | |
| if not pstr then | |
| local ret = { "{" } | |
| if #value == 0 and next(value) then | |
| local indentStr = ProcessIndentPlusOneHelper(indent) | |
| for k, v in sorted_pairs(value) do | |
| if not v then --uninitialized | |
| ret[#ret + 1] = string.format("\n%s%s = false,", indentStr, k) | |
| elseif IsKindOf(v, "Object") then --single nested object | |
| ret[#ret + 1] = string.format("\n%s%s = o(%d),", indentStr, k, v.handle) | |
| else --table | |
| ret[#ret + 1] = string.format("\n%s%s = {", indentStr, k) | |
| ObjectHandlesHelperNoPstr(v, ret) | |
| ret[#ret + 1] = "}," | |
| end | |
| end | |
| else | |
| ObjectHandlesHelperNoPstr(value, ret) | |
| end | |
| ret[#ret + 1] = "}" | |
| return table.concat(ret) | |
| else | |
| pstr:append("{") | |
| if #value == 0 and next(value) then | |
| local indentStr = ProcessIndentPlusOneHelper(indent) | |
| for k, v in sorted_pairs(value) do | |
| if not v then --uninitialized | |
| pstr:append(string.format("\n%s%s = false,", indentStr, k)) | |
| elseif IsKindOf(v, "Object") then --single nested object | |
| pstr:append(string.format("\n%s%s = o(%d),", indentStr, k, v.handle)) | |
| else --table | |
| pstr:append(string.format("\n%s%s = {", indentStr, k)) | |
| ObjectHandlesHelperPstr(v, pstr) | |
| pstr:append("},") | |
| end | |
| end | |
| else | |
| ObjectHandlesHelperPstr(value, pstr) | |
| end | |
| return pstr:append("}") | |
| end | |
| end | |
| if vtype == "range" then | |
| value = IsRange(value) and value or setmetatable(value, __range_meta) | |
| return ValueToLuaCode(value, indent, pstr) | |
| end | |
| if vtype == "browse" then | |
| if not pstr then | |
| return string.format("%q", value) | |
| else | |
| return pstr:appendf("%q", value) | |
| end | |
| end | |
| if vtype == "func" or vtype == "expression" then | |
| local src = GetFuncSourceStringIndent(indent, value, "", eval(prop_meta.params, obj, prop_meta) or "self") | |
| if not pstr then | |
| return src | |
| else | |
| Msg("OnFunctionSerialized", pstr, value) | |
| return pstr:append(src) | |
| end | |
| end | |
| return ValueToLuaCode(value, indent, pstr, injected_props) | |
| end | |
| --[[@@@ | |
| Converts a tuple of values to an executable Lua expression. | |
| Use [LuaCodeToTuple](#LuaCodeToTuple) for evaluating the Lua expression. | |
| @function string code TupleToLuaCode(values...) | |
| @param values... - tuple of values to be converted to executable code string. | |
| @result string code - the supplied tuple, converted to Lua code. | |
| ]] | |
| --- | |
| ---Converts a tuple of values to an executable Lua expression. | |
| ---Use [LuaCodeToTuple](#LuaCodeToTuple) for evaluating the Lua expression. | |
| ---@param ... values tuple of values to be converted to executable code string. | |
| ---@return string code the supplied tuple, converted to Lua code. | |
| function TupleToLuaCode(...) | |
| local values = pack_params(...) | |
| if not values then return "" end | |
| for i = 1, values.n or #values do | |
| values[i] = ValueToLuaCode(values[i], " ") or "nil" | |
| end | |
| return table.concat(values, ",") | |
| end | |
| --- | |
| ---Evaluates a function call and returns the result or error. | |
| ---@param ok boolean Whether the function call succeeded. | |
| ---@param ... any The return values of the function call. | |
| ---@return any, any The error (if any) and the return values of the function call. | |
| --- | |
| local function _load(ok, ...) | |
| if ok then | |
| return nil, ... | |
| else | |
| return ... | |
| end | |
| end | |
| --- | |
| ---Evaluates a function call and returns the result or error. | |
| ---@param ok boolean Whether the function call succeeded. | |
| ---@param ... any The return values of the function call. | |
| ---@return any, any The error (if any) and the return values of the function call. | |
| --- | |
| local function procall_helper2(ok, ...) | |
| if not ok then return ... or "error" end | |
| return nil, ... | |
| end | |
| local default_env | |
| --- | |
| ---Loads a Lua value from a file. | |
| ---@param filename string The path to the file containing the Lua value. | |
| ---@param env table The environment to use when loading the Lua value. If not provided, a default environment is used. | |
| ---@return any, string The loaded Lua value, or an error message if the load failed. | |
| --- | |
| function FileToLuaValue(filename, env) | |
| default_env = default_env or LuaValueEnv{} | |
| local err, data | |
| err, data = AsyncFileToString(filename) | |
| if err then return err end | |
| local func, err | |
| if not string.starts_with(data, "return") then | |
| data = "return " .. data | |
| end | |
| func, err = load(data, nil, nil, env or default_env) | |
| if not func then return err end | |
| return procall_helper2(procall(func)) | |
| end | |
| --[[@@@ | |
| Evaluates a string generated using [TupleToLuaCode](#TupleToLuaCode) and returns the original values. | |
| @function error, values LuaCodeToTuple(string code, table env) | |
| @param string code - code to be evaluated. | |
| @param table env - evaluation environment. | |
| @result error, values - error string (or nil, if none) and the evaluated tuple. | |
| ]] | |
| --- | |
| ---Evaluates a string generated using [TupleToLuaCode](#TupleToLuaCode) and returns the original values. | |
| ---@param string code The code to be evaluated. | |
| ---@param table env The evaluation environment. | |
| ---@return string|nil error The error string, or nil if none. | |
| ---@return any values The evaluated tuple. | |
| --- | |
| function LuaCodeToTuple(code, env) | |
| local err, code = ChecksumRemove(code) | |
| if err then return err end | |
| local func, err = load("return " .. (code or ""), nil, nil, env or _ENV) | |
| if func then | |
| return _load(pcall(func)) | |
| end | |
| return err | |
| end | |
| --- | |
| ---Converts a Lua table to a string representation that can be evaluated as Lua code. | |
| --- | |
| ---@param tbl table The table to convert to Lua code. | |
| ---@param indent string|number The indentation to use for the Lua code. Can be a string of spaces/tabs or a number representing the number of tabs. | |
| ---@param pstr userdata An optional string builder to append the Lua code to. | |
| ---@param injected_props table An optional table of additional properties to include in the Lua code. | |
| ---@return string The Lua code representation of the table. | |
| --- | |
| function TableToLuaCode(tbl, indent, pstr, injected_props) | |
| assert(not pstr or type(pstr) == "userdata") | |
| if pstr then | |
| return pstr:appendt(tbl, indent, false, injected_props) | |
| end | |
| if type(indent) == "number" then | |
| indent = string.rep("\t", indent) | |
| end | |
| if next(tbl) == nil or indent and #indent > 100 then | |
| assert(not indent or #indent <= 100, "too many nested values") | |
| return "{}" | |
| end | |
| indent = indent or "" | |
| local new_indent = indent == " " and indent or indent .. "\t" | |
| local lines = {} | |
| local keys = {} | |
| for key in pairs(tbl) do | |
| if type(key) ~= "number" or key < 1 or key > #tbl then | |
| keys[#keys + 1] = key | |
| end | |
| end | |
| table.sort(keys, lessthan) | |
| for i, key in ipairs(keys) do | |
| if key ~= "__index" then | |
| local value = ValueToLuaCode(tbl[key], new_indent, nil, injected_props) | |
| if value then | |
| lines[#lines + 1] = FormatKey(key) .. value | |
| end | |
| end | |
| end | |
| local only_numbers = #lines == 0 | |
| for i = 1, #tbl do | |
| local value = tbl[i] | |
| only_numbers = only_numbers and type(value) == "number" | |
| lines[#lines + 1] = ValueToLuaCode(value, new_indent, nil, injected_props) or "nil" | |
| end | |
| if indent == " " or #lines == 0 or only_numbers then | |
| return string.format("{%s}", table.concat(lines, ",")) | |
| end | |
| local code = table.concat(lines, ",\n\t" .. indent) | |
| return string.format("{\n\t%s%s,\n%s}", indent, code, indent) | |
| end | |
| ---@param obj table The object to get the property list from. | |
| ---@param indent string The indentation to use for the Lua code. | |
| ---@param GetPropFunc function An optional function to get the property value. | |
| ---@param pstr string An optional string builder to append the Lua code to. | |
| ---@param additional string An optional additional string to append to the Lua code. | |
| ---@param injected_props table An optional table of additional properties to include in the Lua code. | |
| ---@return string The Lua code representation of the object's property list. | |
| function ObjPropertyListToLuaCode(obj, indent, GetPropFunc, pstr, additional, injected_props) | |
| indent = indent or "" | |
| local new_indent | |
| if not pstr then | |
| new_indent = indent == " " and indent or indent ~= "" and indent .. "\t" or "\t" | |
| else | |
| indent = type(indent) == "number" and indent or 0 | |
| new_indent = indent >= 0 and indent + 1 or indent | |
| end | |
| local code | |
| local props = obj:GetProperties() | |
| local prop_count = #props | |
| for i = 1, prop_count + #injected_props do | |
| local prop = i > prop_count and injected_props[ i - prop_count ] or props[ i ] | |
| local id = prop.id | |
| if injected_props and i <= prop_count and eval(prop.inject_in_subobjects, obj, prop) then | |
| injected_props[#injected_props + 1] = prop | |
| end | |
| local editor = eval(prop.editor, obj, prop) | |
| if not eval(prop.dont_save, obj, prop) and editor then | |
| local value | |
| if GetPropFunc then | |
| value = GetPropFunc(obj, id, prop) | |
| else | |
| value = obj:GetProperty(id) | |
| end | |
| if not obj:IsDefaultPropertyValue(id, prop, value) then | |
| if not pstr then | |
| value = PropToLuaCode(value, editor, new_indent, nil, prop, obj, injected_props) | |
| if value then | |
| code = code or { "{" } | |
| code[#code + 1] = string.format("\t'%s', %s,", id, value) | |
| else | |
| assert(false, print_format("ToLuaCode:", id, "cannot be saved as", editor)) | |
| end | |
| else | |
| if not code then | |
| code = true | |
| if indent < 0 then | |
| pstr:append("{ ") | |
| else | |
| pstr:append("{\n") | |
| pstr:appendr("\t", indent) | |
| end | |
| end | |
| local len = #pstr | |
| pstr:appendf("\t'%s', ", id) | |
| if PropToLuaCode(value, editor, new_indent, pstr, prop, obj, injected_props) then | |
| if indent < 0 then | |
| pstr:append(", ") | |
| else | |
| pstr:append(",\n") | |
| pstr:appendr("\t", indent) | |
| end | |
| else | |
| pstr:resize(len) | |
| assert(false, string.format("ToLuaCode: %s.%s cannot be saved as %s", obj.class, id, editor)) | |
| end | |
| end | |
| end | |
| end | |
| end | |
| if code then | |
| if not pstr then | |
| if additional then | |
| code[#code + 1] = additional | |
| end | |
| code[#code + 1] = "}" | |
| return table.concat(code, indent == " " and " " or "\n" .. indent) | |
| else | |
| if additional then | |
| pstr:append(additional) | |
| end | |
| return pstr:append("}") | |
| end | |
| end | |
| end | |
| --- | |
| --- Converts an array to Lua code representation. | |
| --- | |
| --- @param array table The array to convert to Lua code. | |
| --- @param indent string The indentation to use for the Lua code. | |
| --- @param pstr string The string builder to append the Lua code to. | |
| --- @param injected_props table Any additional properties to inject into the Lua code. | |
| --- @return string The Lua code representation of the array. | |
| --- | |
| function ArrayToLuaCode(array, indent, pstr, injected_props) | |
| if not array or #array == 0 then return end | |
| indent = indent or "" | |
| local new_indent | |
| if not pstr then | |
| new_indent = indent ~= "" and (indent .. "\t") or "\t" | |
| local code = { } | |
| for i = 1, #array do | |
| local value = rawget(array, i) | |
| assert(value ~= nil, "ArrayToLuaCode: nil value") | |
| value = ValueToLuaCode(value, new_indent, nil, injected_props) | |
| assert(value, "ArrayToLuaCode: Value cannot be saved") | |
| code[#code + 1] = value | |
| end | |
| code[#code + 1] = "}" | |
| code[1] = "{\n" .. new_indent .. code[1] | |
| return table.concat(code, ",\n\t" .. indent) | |
| else | |
| indent = type(indent) == "number" and indent or 0 | |
| new_indent = indent >= 0 and indent + 1 or indent | |
| pstr:append("{\n") | |
| pstr:appendr("\t", new_indent) | |
| for i = 1, #array do | |
| local value = rawget(array, i) | |
| assert(value ~= nil, "ArrayToLuaCode: nil value") | |
| pstr:appendv(value, new_indent, injected_props) | |
| pstr:append(",\n\t") | |
| pstr:appendr("\t", indent) | |
| end | |
| return pstr:append("}") | |
| end | |
| end | |
| --- | |
| --- Copies the value of the given Lua value. | |
| --- | |
| --- @param value any The value to copy. | |
| --- @return string|nil, any The error message if an error occurred, or the copied value. | |
| --- | |
| function CopyValue(value) | |
| local vtype = type(value) | |
| if vtype == "number" or vtype == "string" or vtype == "boolean" or vtype == "nil" then | |
| return nil, value | |
| end | |
| local success, code = procall(ValueToLuaCode, value) | |
| if not success then return code end | |
| local success, err, copy = procall(LuaCodeToTuple, code) | |
| assert(success, err) | |
| if not success then return err end | |
| assert(not err, err) | |
| if err then return err end | |
| return nil, copy | |
| end | |
| ------------------------- | |
| ---- FUNCTION SOURCE ---- | |
| ------------------------- | |
| if FirstLoad then | |
| FuncSource = setmetatable({}, weak_keys_meta) -- cache for the source code of functions | |
| LuaSource = {} -- cache for Lua source files; will NOT be updated for externally changed files | |
| end | |
| --- | |
| --- Fetches the Lua source code for the specified file, optionally using a cache. | |
| --- | |
| --- @param file_name string The name of the Lua source file to fetch. | |
| --- @param no_cache boolean (optional) If true, the cache will not be used and the source will be fetched directly. | |
| --- @return table|nil The lines of the Lua source file, or nil if an error occurred. | |
| --- | |
| function FetchLuaSource(file_name, no_cache) | |
| if not no_cache then | |
| local source = LuaSource[file_name] | |
| if source then return source end | |
| end | |
| local err, content = AsyncFileToString(file_name, nil, nil, "lines") | |
| if err then return end | |
| LuaSource[file_name] = content | |
| return content | |
| end | |
| --- | |
| --- Caches the Lua source code for the specified file. | |
| --- | |
| --- @param file_name string The name of the Lua source file to cache. | |
| --- @param source table|string The source code of the Lua file, either as a table of lines or a single string. | |
| --- | |
| function CacheLuaSourceFile(file_name, source) | |
| LuaSource[file_name] = type(source) == "table" and source or string.split(tostring(source), "\n") | |
| end | |
| --- | |
| --- Invalidates the cache of Lua source files. | |
| --- | |
| --- This function clears the `LuaSource` table, which is used to cache the contents of Lua source files. This can be useful if the source files have been modified externally and the cache needs to be refreshed. | |
| --- | |
| function InvalidateGetFuncSourceCache() | |
| LuaSource = {} | |
| end | |
| --- Parses the source file of a function and returns its name, params and body | |
| -- @cstyle name, params, body GetFuncSource(func f). | |
| -- @param f function; | |
| -- @return name string, params string with comma-separated params, body is either a string or a table with strings for multiline functions. | |
| --- | |
| --- Fetches the Lua source code for the specified file, optionally using a cache. | |
| --- | |
| --- @param file_name string The name of the Lua source file to fetch. | |
| --- @param no_cache boolean (optional) If true, the cache will not be used and the source will be fetched directly. | |
| --- @return table|nil The lines of the Lua source file, or nil if an error occurred. | |
| --- | |
| function GetFuncSource(f, no_cache) | |
| assert(not f or type(f) == "function") | |
| if not f or type(f) ~= "function" then return end | |
| if not no_cache then | |
| local name, params, body = unpack_params(FuncSource[f or false]) | |
| if body then | |
| return name, params, body | |
| end | |
| end | |
| local info = debug.getinfo(f, "S") | |
| local first, last = info.linedefined, info.lastlinedefined | |
| local source = info.source | |
| if not info or not source or not first or not last then return end | |
| local file_contents | |
| if source:sub(1, 1) == "@" then | |
| file_contents = FetchLuaSource(source:sub(2), no_cache) | |
| if not file_contents then return end | |
| else -- compiled from a string, e.g. from LuaCodeToTuple | |
| file_contents = string.split(info.source, "\n") | |
| end | |
| local first_line = (file_contents[first] or "") | |
| local name, params, body_start = first_line:match("%f[%w]function%f[%W]%s*([%w:._]*)%s*%(([%w%s,._]-)%)%s*()") | |
| if not body_start then return end | |
| if first == last then | |
| local body = first_line:match("^(.*)%f[%w]end%f[^%w_]", body_start) | |
| body = body and body:match("(.-)%s*$") | |
| FuncSource[f] = { name, params, body } | |
| return name, params, body, first, last, file_contents | |
| else | |
| --print("first", file_contents[first]) | |
| local b = first_line:sub(body_start, -1) | |
| if b == "" then b = nil end | |
| --print(name, params, b, "n") | |
| local body = { b } | |
| local tabs | |
| for i = first + 1, last do | |
| local current_line = file_contents[i] | |
| if i == last then | |
| current_line = (current_line or ""):match("(.-)%s*%f[%w]end%f[^%w_]") | |
| if not current_line then return end | |
| if current_line == "" then break end | |
| end | |
| if not tabs then | |
| tabs = string.match(current_line, "^[\n\r ]*(\t*)") | |
| end | |
| if tabs then | |
| local current_tabs = string.match(current_line, "^(\t*)") or "" | |
| current_line = current_line:sub(Min(#tabs, #current_tabs) + 1) | |
| end | |
| body[#body + 1] = current_line | |
| end | |
| FuncSource[f] = { name, params, body } | |
| return name, params, body, first, last, file_contents | |
| end | |
| end | |
| if FirstLoad then | |
| missing_source_func = function() | |
| assert(false, "Missing func!") | |
| end | |
| end | |
| --- Fallback function to be returned when the source code for a function is missing. | |
| -- This function will assert that the source is missing and return the `missing_source_func` function. | |
| -- @function GetMissingSourceFallback | |
| -- @return function missing_source_func | |
| function GetMissingSourceFallback() | |
| assert(false, "Func source missing!") | |
| return missing_source_func | |
| end | |
| --- Parses the source file of a function and returns its ready for compilation source. | |
| -- @cstyle string GetFuncSourceString(func f, string new_name, string new_params). | |
| -- @param f function; | |
| -- @param new_name string; optional name to be used instead of the original one. | |
| -- @param new_params string; optional parameters to be used instead of the original ones. | |
| -- @return function source. | |
| function GetFuncSourceString(f, new_name, new_params) | |
| if f ~= missing_source_func then | |
| local name, params, body = GetFuncSource(f) | |
| if not body then | |
| print("WARNING: Unable to retrieve a function's source code while saving!\n", rawget(_G, "FindFunctionByAddress") and TableToLuaCode(FindFunctionByAddress(f)) or "(unknown)") | |
| else | |
| name = new_name or name | |
| params = new_params or params | |
| if type(body) == "string" then | |
| return string.format("function %s(%s) %s end", name, params, body) | |
| else | |
| return string.format("function %s(%s)\n%s\nend", name, params, table.concat(body, "\n")) | |
| end | |
| end | |
| end | |
| return "GetMissingSourceFallback()" | |
| end | |
| --- Compiles a function and stores its source code in FuncSource so that it can be looked up with GetFuncSource/GetFuncSourceString later. | |
| -- @cstyle function CompileFunc(string name, string params, string body, string chunkname). | |
| -- @param name string; function name. | |
| -- @param params string; function parameters. | |
| -- @param body string; function body without the final end statement. | |
| -- @return function, error. | |
| function CompileFunc(name, params, body, chunkname) | |
| local src = string.format("return function (%s) %s\nend", params or "", body) | |
| local f, err = load(src, chunkname or string.format("%s(%s)", name or "func", params or "")) | |
| f = f and f() or function() printf("bad function %s(%s): %s", name, params, err) end | |
| body = string.split(body, "\n") | |
| if not err then | |
| FuncSource[f] = { name, params or "", body, err } | |
| end | |
| return f, err | |
| end | |
| --- Compiles an expression returning a value. If needed adds "return " before the expression. | |
| -- @cstyle function CompileExpression(string name, string params, string body, string chunkname). | |
| -- @param name string; function name. | |
| -- @param params string; function parameters. | |
| -- @param body string; function body without the final end statement. | |
| -- @return function, error. | |
| function CompileExpression(name, params, body, chunkname) | |
| body = body:match("^%s*(.-)%s*$") | |
| if not body:find("return ", 1, true) then | |
| body = "return " .. body | |
| end | |
| local src = string.format("local function %s(%s) %s end\nreturn %s", name, params or "", body, name) | |
| local f, err = load(src, chunkname or ("expression " .. (params or ""))) | |
| f = f and f() or function() printf("bad expression %s(%s): %s", name, params, err) end | |
| if not err then | |
| FuncSource[f] = { name, params or "", body } | |
| end | |
| return f, err | |
| end | |
| --- | |
| --- Formats the source code of a function by indenting the lines and ensuring the final "end" statement is on its own line. | |
| --- | |
| --- @param indent string|number The indentation to apply to the function body. If a number, it specifies the number of tabs to use. If a string, it is used as the indentation. | |
| --- @param ... any Arguments passed to `GetFuncSourceString`. | |
| --- @return string The formatted function source code. | |
| function GetFuncSourceStringIndent(indent, ...) | |
| local src = GetFuncSourceString(...) | |
| -- Make sure that we don't persist a function with "end end" as the last line, | |
| -- because GetFuncSource can't correctly find the function code end in this case. | |
| -- | |
| -- Fix up code formatting as well (each function compiled with CompileExpression | |
| -- that has a composite statement will be formatted on multiple lines) | |
| if src:find("%send%W") and src:ends_with(" end") then | |
| local first, last = src:find("function%s*%b() ") | |
| if first == 1 then | |
| src = src:sub(1, last - 1) .. "\n" .. src:sub(last + 1) | |
| end | |
| src = src:sub(1, -5) .. "\nend" | |
| end | |
| local internal_indent | |
| if type(indent) == "number" then | |
| if indent <= 0 then | |
| return src | |
| end | |
| internal_indent = string.rep("\t", indent + 1) | |
| indent = string.rep("\t", indent) | |
| elseif indent == " " then | |
| return src | |
| else -- tabs | |
| internal_indent = indent .. "\t" | |
| end | |
| local lines = string.split(src, "\n") | |
| for i = 2, #lines - 1 do | |
| lines[i] = internal_indent .. lines[i] | |
| end | |
| if #lines > 2 then | |
| lines[#lines] = indent .. lines[#lines] | |
| end | |
| return table.concat(lines, "\n") | |
| end | |
| -- returns the function body (with no enclosing function(...) end) | |
| --- | |
| --- Returns the body of a Lua function. | |
| --- | |
| --- @param func function The Lua function to get the body of. | |
| --- @param indent string|number The indentation to apply to the function body. If a number, it specifies the number of tabs to use. If a string, it is used as the indentation. | |
| --- @param default string The default value to return if the function body is not a string or table. | |
| --- @return string The function body. | |
| function GetFuncBody(func, indent, default) | |
| local name, params, body = GetFuncSource(func) | |
| if type(body) == "table" then | |
| indent = indent or "" | |
| return indent .. table.concat(body, "\n" .. indent) | |
| elseif type(body) == "string" then | |
| return (indent or "") .. body | |
| end | |
| return default or "" | |
| end | |
| -- returns the expression "value" (with no return keyword) | |
| --- | |
| --- Returns the body of a Lua expression, excluding the "return" keyword. | |
| --- | |
| --- @param func function The Lua function to get the expression body of. | |
| --- @return string The expression body. | |
| function GetExpressionBody(func) | |
| local body = GetFuncBody(func) | |
| assert(body == "" or body:starts_with("return")) | |
| return #body >= 8 and body:sub(8) or "nil" | |
| end | |