diff --git a/doc/environment.adoc b/doc/environment.adoc new file mode 100644 index 0000000..03c7e3a --- /dev/null +++ b/doc/environment.adoc @@ -0,0 +1,252 @@ += Lua Environment + +:description: Documentation of the global environment Minetest mods run in +:keywords: lua, luajit, environment, mod, security, portability, platform, library + +:toc: + +:url-reference-manual: https://www.lua.org/manual/5.1/manual.html + +Minetest uses Lua 5.1. The environment in which Minetest executes mods depends on four factors: + +. The operating system +. The Minetest build: LuaJIT or PUC Lua 5.1 +. The mod type: Client-side or server-side +. The mod environment: Secure or insecure + +== Platform Independence / Portability + +See the {url-reference-manual}[Lua 5.1 Reference Manual] for platform- and OS-environment-dependant Lua features. These include: + +* Locale, affecting pattern matching (character classes) and character codes used by `string.char` and `string.byte` +* Large parts of the `os` library, particularly `os.execute` (only available in an insecure environment) +* Some parts of the `io` library like `io.popen` (only available in an insecure environment) or handling of binary files +* `require` and `package.loadlib` (only available in an insecure environment anyways) + +== Standard Library Extensions + +=== `math` + +==== `math.hypot(x, y)` + +Finds the length of the hypotenuse `z` according to the Pythagorean Theorem: `z^2 = x^2 + y^2`. Shorthand for `math.sqrt(x*x + y*y)`. + +==== `math.sign(x, [tolerance])` + +`tolerance` defaults to `0` if falsy. Returns `-1` if the value is smaller than the `tolerance`, `1` if it is larger. Returns `0` if `x` is within the closed tolerance interval `[-tolerance, tolerance]`. Also returns `0` if `x` is `nan`. + +==== `math.factorial(x)` + +`x` must be a non-negative integer; otherwise, the function will error with `"factorial expects a non-negative integer"`. If `x` is at least `171`, `+inf` is returned. + +As Python has built-in big integer support (and uses 64-bit `float`), it can be used to easily determine for which `x` this implementation becomes imprecise due to float precision limitations: + +[source,python3] +---- +def factorial(x): + return x if x == 1 else x * factorial(x-1) +for x in range(1, 171): + if factorial(float(x)) != factorial(x): + print(x) + break +---- + +This will print `23`. This means that only for `x` values ranging from `1` to `22`, both inclusive, `factorial(x)` will be fully accurate. + +==== `math.round(x)` + +Rounds `x` towards the nearest integer value. Edge cases: + +* Ties: If `x` is exactly the same distance from two integer values (`x = k + 0.5`) with `k` being an integer, it is rounded "away from zero", to the value with the higher absolute value: +** If `x > 0`, `x` will be rounded to the larger value; +** If `x < 0`, `x` will be rounded to the smaller value. +* Precision: Numbers very close to ties (plus/minus `0.49999999999999994`) are incorrectly handled like ties. See https://stackoverflow.com/a/58411671/7185318[this StackOverflow answer on rounding in Lua]. +* Special float values: `nan` and `inf` are preserved, as well as their sign. + +=== `string` + +These functions can be used over the string metatable as well, using `self:func(...)` if `self` is a string. + +Note. `"...":func(...)` is a syntax error in Lua. Wrap strings in brackets `(...)` if you want to index them: `("..."):func(...)`. + +==== `string.trim(self)` + +Will return a string with consecutive spacing characters (`%s` pattern character class) at the start and the end of the string removed. Usually the following characters are considered spacing: + +* Horizontal tab: `'\t'` or `'\9'` +* Newline: `'\n'` or `'\10'` +* Vertical tab: `'\v'` or `'\11'` +* Form feed: `'\f'` or `'\12'` +* Carriage return: `'\r'` or `'\13'` +* Space: `' '` or `'\32'` + +As determined using the below Lua script, which outputs the decimal character codes: + +[source,lua] +---- +for i = 0, 255 do + if string.char(i):match"%s" then + print(i) + end +end +---- + +WARNING: Platform-independence is not guaranteed: "The definitions of letter, space, and other character groups depend on the current locale." - https://www.lua.org/manual/5.1/manual.html#5.4.1[Lua 5.1 Reference Manual, section 5.4.1] + +==== `string.split(str, [delim], [include_empty], [max_splits], [sep_is_pattern])` + +* `str`: The string to split. +* `delim`: Delimiter/separator. Defaults to `","` if falsy. +* `include_empty`: If truthy, empty strings (`""`) are included in the returned list. +* `max_splits`: Maximum amount of splits to be done. Splits are done in left-to-right (string start to end) order. The resulting list can have up to `max_splits + 1` entries. The last element in the list may contain the delimiter. Unlimited splits if falsy, negative, `nan` or `+inf`. +* `sep_is_pattern`: If truthy, `delim` is used as a pattern. + +Returns a list containing the delimited parts without the delimiters. + +=== `table` + +==== `table.indexof(list, val)` + +Linear search for `val` in the `list`. Returns the first index where the value equals `val`. Returns `-1` if the value is not found. + +==== `table.copy(t, [seen])` + +Deepcopies the table `t` and all it's subtables - both keys and values. Non-table types are not copied, even if they are reference types (userdata, functions and threads). The reference structure will be fully preserved: A single table, even if referenced multiple times, will only be copied a single time; subsequent references in the copy will just reference the same copied table. + +The `seen` table is a lookup for already copied tables, which are used as keys. The value is the copy. By providing `[table] = table` entries for certain tables, you can prevent them from being copied. + +==== `table.insert_all(t, other)` + +Adds all the list entries of `other` to `t` (list part concatenation). + +==== `table.key_value_swap(t)` + +Returns a new table with the keys of `t` as values and the corresponding values as keys. If a value occurs multiple times in `t`, any of the keys might be the value in the resulting table. + +==== `table.shuffle(t, from, to, random)` + +Performs a Fisher-Yates shuffling on the specified range of the list part of `t`. + +* `from`: Inclusive starting index of the range to be shuffled. Defaults to the first item of the list part if falsy. +* `to`: Inclusive end index of the range to be shuffled. Defaults to the last item of the list part if falsy. +* `random`: A `function(from, to)` that returns a random integer in the specified range, with both `from` and `to` inclusive. Defaults to `math.random` if falsy. + +Returns nothing. + +== LuaJIT extensions + +Minetest builds compiled with LuaJIT (`ENABLE_LUAJIT=1`) provide the https://luajit.org/extensions.html[LuaJIT extensions]. These include syntactical Lua 5.2 language features like `goto`, which will lead to a syntax error on PUC Lua 5.1. Hex escapes will be converted into the raw characters by PUC Lua 5.1. + +== Common extensions + +https://bitop.luajit.org/[LuaJIT's `bit` library] is made available for both PUC Lua and LuaJIT builds. It must not be required, as this will lead to a crash in a secure environment as documented below; in an insecure environment, it is simply unneeded. + +== Secure environment whitelists + +In the secure environment, the following builtin Lua(JIT) libraries and library functions are whitelisted: + +* `_VERSION` +* Garbage collection: `collectgarbage` +* Cooperative multithreading: `coroutine` +* Error handling: +** `assert` +** `error` +** `pcall` +** `xpcall` +* Function environments: +** `setfenv` +** `getfenv` +* `math` +* `string` +* Tables: +** `table` +** Iteration: +*** `next` +*** `pairs` +*** `ipairs` +* Metatables: +** `setmetatable` +** `getmetatable` (SSM-only) +** Raw methods: +*** `rawset` +*** `rawget` +*** `rawequals` +* Varargs: +** `select` +** `unpack` +* Conversion: +** `tostring` +** `tonumber` +* `type` +* Output: `print` + +Some library tables are restricted by whitelists as well: + +* `io` (SSM-only) +** `read` +** `write` +** `flush` +** `close` +** `type` +* `os`: Mostly time-related functions +** `clock` +** `date` +** `difftime` +** `time` +** SSM-only: +*** `getenv` +*** `setlocale` +*** `tmpname` +* `debug`: +** `gethook` +** `traceback` +** SSM-only: +*** `getinfo` +*** `getmetatable` +*** `setmetatable` +*** `upvalueid` +*** `sethook` +*** `debug` +* `package` (SSM-only): +** `config` +** `cpath` +** `path` +** `searchpath` +* `jit` (LuaJIT-only): +** `arch` +** `flush` +** `off` +** `on` +** `opt` +** `os` +** `status` +** `version` +** `version_num` + +Everything file-related is replaced by a secure variant: + +* Loading Lua code: Errors with `"Bytecode prohibited when mod security is enabled."` if the sources are bytecode (strings starting with `'\27'`). For CSM, the file-related functions operate on virtual paths and only have access to CSM files. +** `dofile` +** `load` +** `loadfile` +** `loadstring` +** `require`: Disabled, errors with `"require() is disabled when mod security is on."`. + +The following functions, which are *not available to CSM / SSM-only*, allow read-only access to all mod directories and write access to the current loading mod's directory only while it's loading (a handle with write access to a file within the mod directory can however be stored and used at a later time) and read & write access to the world directory excepting the `worldmods` and `game` subfolders: + +* `io`: +** `open` +** `input` +** `output` +** `lines` +* `os`: +** `remove` +** `rename` + +Builtin can read and write anywhere during it's load time. + +See the {url-reference-manual}[Lua 5.1 Reference Manual] for documentation of the Lua standard library. + +If mod security is disabled, server-side mods run in an insecure environment, which contains all libraries and library functions, without any restrictions. The same restrictions apply to trusted server-side mods, which can however request an insecure environment in table form using `minetest.request_insecure_environment` which will contain shallow copies of library tables and no global restrictions. + +// TODO link minetest.request_insecure_environment