diff --git a/docs/Changelog.md b/docs/Changelog.md index 554d2712c..f968ec4bc 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -4,6 +4,40 @@ sidebar_position: 13 This page contains the changelogs from all [releases of Pluto](https://github.com/PlutoLang/Pluto/releases). +## 0.11.0 +- Added preprocessor aliases (`$alias`) +- Added compile-time evaluated statement `$assert` +- Added warning for unused local variables +- Added octal numerals +- Added `__mindex` metamethod + - This is now used by default table metatable, elimiting the compatibility concerns it previously had. +- Export is no longer deprecated but now implies constness +- Walrus operator can now initialize multiple variables +- Compile-time conditionals can now be equality checks +- Table freezing is now an optional feature, enabled via `PLUTO_ENABLE_TABLE_FREEZING` +- Fixed implicit conversion of booleans to strings outside of concats +- Removed table length cache +- Removed let & const + +Standard library: +- Added buffer library +- Added table.invert, table.dedup/deduplicate, table.deduped/deduplicated, table.values +- Added crypto.crc32c +- crypto.random now treats 1-2 arguments like math.random +- Added optional 'binary' parameter to crypto.md5 +- Added ffi.alloc, ffi.write, & ffi.read +- Added UDP support to socket.connect +- Added socket.isudp, socket.udpserver +- Added io.chmod +- Added os.arch constant +- Added callonce function +- Added sdiv, udiv, smod, & umod functions +- string.split's needle parameter is now required (previously defaulted to `","`) +- socket.listen & socket.bind can now be bound to a specific IP address +- io.part now returns 'parent, name' if part argument is omitted +- Optimized json.encode & json.decode +- Removed `_PSOUP` constant + ## 0.10.5 - Improved error message when `new` is provided with a nil value - Fixed ternary expression sometimes picking a bad register diff --git a/docs/Compatibility.md b/docs/Compatibility.md index 13f5156cc..b2087a56c 100644 --- a/docs/Compatibility.md +++ b/docs/Compatibility.md @@ -75,31 +75,3 @@ These are what they look like: - `pluto_export` - `pluto_try` - `pluto_catch` - -## Default Table Metatable - -This is [a feature in Pluto](Runtime%20Environment/Global%20&%20Base#default-metatables) that, by itself, is a benign QoL improvement for developers. However, in combination with our added standard library functions like [table.min](Runtime%20Environment/Table#tablemin), it can be an unexpected semantic change: - -```pluto showLineNumbers -local function roll(opts) - return math.random(opts.min or 1, opts.max or 100) -end -print(roll{ max = 10 }) -``` -``` -pluto: test.pluto:2: bad argument #1 to 'random' (number expected, got function) -stack traceback: - [C]: in function 'math.rand' - test.pluto:2: in local 'roll' - test.pluto:4: in main chunk -``` - -Integrators can disable this feature by defining the `PLUTO_NO_DEFAULT_TABLE_METATABLE` macro in their luaconf.h or build config, to aid in a smooth transition, should scripts in their ecosystem require it. - -Scripters are advised to use `rawget` and/or `type` to better codify their expectations. For example, the example above seems to care only about providing fallback values and not at all about type-checking, so `rawget` would be an excellent fit for it: -```pluto -local function roll(opts) - return math.random(rawget(opts, "min") or 1, rawget(opts, "max") or 100) -end -print(roll{ max = 10 }) -``` diff --git a/docs/New Features/Compiler Warnings.md b/docs/New Features/Compiler Warnings.md index 4b0687b15..9e5d5447f 100644 --- a/docs/New Features/Compiler Warnings.md +++ b/docs/New Features/Compiler Warnings.md @@ -5,6 +5,17 @@ Pluto offers optional compiler warnings for certain misbehaviors. ## Warning Types +### unused +This is raised when a local is declared but never used. +```pluto showLineNumbers +local a +``` +``` +file.pluto:1: warning: unused local variable [unused] + 1 | local a + | ^^^^^^^ here: 'a' is unused +``` + ### var-shadow This is raised when a new local is created with the same name as an existing one. ```pluto showLineNumbers @@ -179,18 +190,31 @@ end ``` ### implicit-global -This is raised when the `global` keyword is enabled and a global was declared without it. See [Explicit Globals](). +This is raised when a global is declared without an explicit prefix, such as the optional `global` keyword: ```pluto showLineNumbers pluto_use global - a = 1 ``` ``` -file.pluto:3: warning: implicit global creation [implicit-global] - 3 | a = 1 - | ^^^^^ here: prefix this with 'global' to be explicit +file.pluto:2: warning: implicit global creation [implicit-global] + 2 | a = 1 + | ^^^^^ here: prefix this with '_G.' or 'global' to be explicit ``` +Examples of code that does not raise this warning: +```pluto +pluto_use global +global a = 1 +a = 2 +``` +```pluto +-- @pluto_warnings enable-implicit-global +_G.a = 1 +a = 2 +``` + +This warning type is enabled via `pluto_use global` or [compile-time configuration](#compile-time-configuration). + ### discarded-return This is raised when the return value of a function declared `` was discarded. See [Nodiscard Functions](). ```pluto showLineNumbers diff --git a/docs/New Features/Explicit Globals.md b/docs/New Features/Explicit Globals.md deleted file mode 100644 index a05ef39cb..000000000 --- a/docs/New Features/Explicit Globals.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -sidebar_position: 2 ---- - -The optional `global` keyword can be used to explicitly declare global variables. - -To enable it, simply do: -```pluto -pluto_use global -``` - -Then it can be used like this: -```pluto -global a = 1 -``` - -Being identical to the following code: -```pluto -a = 1 -``` - -## Compiler Warnings - -When the `global` keyword is enabled, an 'implicit-global' warning is raised for any globals declared without it: - -```pluto showLineNumbers -pluto_use global - -a = 1 -``` -``` -file.pluto:3: warning: implicit global creation [implicit-global] - 3 | a = 1 - | ^^^^^ here: prefix this with 'global' to be explicit -``` \ No newline at end of file diff --git a/docs/New Features/Export Modifier.md b/docs/New Features/Export Modifier.md new file mode 100644 index 000000000..388211ae7 --- /dev/null +++ b/docs/New Features/Export Modifier.md @@ -0,0 +1,45 @@ +--- +sidebar_position: 2 +--- +The `export` modifier allows you to automatically aggregate things you want to export into a table. + +```pluto title="Old Code" +local version = 2 + +local function add(a, b) + return a + b +end + +return { + version = version, + add = add +} +``` + +```pluto title="New Code" +export version = 2 + +export function add(a, b) + return a + b +end +``` + +The return statement is automatically generated at the end of the block, so it is not limited to the top-level function: + +```pluto +package.preload["test"] = function() + export version = 2 + + export function add(a, b) + return a + b + end + + -- end of scope; 'return' is automatically generated +end + +print(require"test".version) +``` + +## Using Compatibility Mode? + +You may need to use `pluto_export` instead of `export`. Alternatively, `pluto_use export` will enable the keyword independently of environment settings. diff --git a/docs/New Features/Mindex Metamethod.md b/docs/New Features/Mindex Metamethod.md new file mode 100644 index 000000000..d13ae7ccb --- /dev/null +++ b/docs/New Features/Mindex Metamethod.md @@ -0,0 +1,36 @@ +The `__mindex` metamethod stands for 'method index'. It has a secondary priority to `__index` and it's only invoked when the lookup is being performed by method invocation syntax. This is used to avoid compatibility issues regarding Pluto's default metatable for tables. For example: +```pluto +local t = { "a", "b", "c" } +print(t:concat()) +``` +In this code, the following occurs: +1. `concat` key is not directly present in the table, so `__index` is queried. +2. `__index` returns nil. +3. Since `__index` returned nil and this lookup is being performed by method invocation syntax (`:`), `__mindex` is queried. + +Another example: +```pluto +local t = setmetatable({}, { + __mindex = { + function sum() + local accum = 0 + for self as v do + accum += v + end + return accum + end + } +}) +t:insert(1) +t:insert(2) +print(t.sum) --> nil +print(t:sum()) --> 3 +``` + +Beware of a caveat: +```pluto +local t = { 1, 2 } +print(t:min()) --> 1 +t.min = 1 +print(t:min()) -- attempt to call a number value +``` \ No newline at end of file diff --git a/docs/New Features/Numeral Parsing.md b/docs/New Features/Numeral Parsing.md index 664046d8a..ed190f5e5 100644 --- a/docs/New Features/Numeral Parsing.md +++ b/docs/New Features/Numeral Parsing.md @@ -6,21 +6,25 @@ Pluto makes two small changes to numeral parsing. ## Cosmetic Underscores You can add underscores to your numeric literals to make them more readable. -```pluto showLineNumbers title="Example Code" +```pluto local n = 10_000_000 -assert(n == 10000000) +print(n) --> 10000000 ``` These underscores are ignored by the compiler, so they are purely cosmetic. -## Binary Integers +## Binary & Octal Numerals Similar to how Lua allows you to input numbers in hexadecimal: -```pluto showLineNumbers title="Example Code" -local n = 0x420 -assert(n == 1056) +```pluto +local n = 0x2A +print(n) --> 42 ``` -Pluto allows you to input numbers in binary as well: -```pluto showLineNumbers title="Example Code" -local n = 0b1000101 -assert(n == 69) +Pluto allows you to input numbers in binary and octal as well: +```pluto +local n = 0b101010 +print(n) --> 42 +``` +```pluto +local n = 0o52 +print(n) --> 42 ``` diff --git a/docs/New Features/Compile-Time Evaluation.md b/docs/New Features/Preprocessing.md similarity index 69% rename from docs/New Features/Compile-Time Evaluation.md rename to docs/New Features/Preprocessing.md index 269687c13..fbacb6283 100644 --- a/docs/New Features/Compile-Time Evaluation.md +++ b/docs/New Features/Preprocessing.md @@ -1,7 +1,7 @@ --- sidebar_position: 2 --- -Pluto's parser provides some powerful constructs which allow you to write code that will never be seen at runtime. +Pluto provides some powerful constructs which allow you to write code that will never be seen at runtime. ## Function calls @@ -36,6 +36,7 @@ And on the following functions: - `tonumber` - `utonumber` - `type` +- `assert` ## Variables @@ -68,3 +69,31 @@ $end ``` In this case, only one of the two paths will be compiled in; the rest will not take up any space. + +## Aliases + +Preprocessor aliases are similar to C/C++ macros. For example, you can define an alias for a keyword: + +```pluto +$alias let = local +let a = 1 +print(a) --> 1 +``` + +or write simple functions which will be fully inlined at the call site: + +```pluto +$alias add(a, b) = a + b +assert(add(1, 2) == 3) +``` + +If you want to write an alias over multiple lines, you can use a backslash to continue it: + +```pluto +$alias seq = "a" \ + .. \ + "b" \ + .. \ + "c" +assert(seq == "abc") +``` diff --git a/docs/New Features/Table Freezing.md b/docs/New Features/Table Freezing.md index c560185d6..d39c4eee4 100644 --- a/docs/New Features/Table Freezing.md +++ b/docs/New Features/Table Freezing.md @@ -38,4 +38,8 @@ All modifications to the table from within the Lua environment will be prevented :::caution If you're going to use this for a sandbox, ensure you call `table.freeze` before any users can access the Lua environment, otherwise they can replace that function. +::: + +:::info +As of Pluto 0.11.0, these functions are unavailable by default. You can enable them by defining the `PLUTO_ENABLE_TABLE_FREEZING` macro. ::: \ No newline at end of file diff --git a/docs/New Operators.md b/docs/New Operators.md index 2da803d72..e6ef3da5f 100644 --- a/docs/New Operators.md +++ b/docs/New Operators.md @@ -105,7 +105,6 @@ This operator does not implement any metamethods. ## Walrus Operator The Walrus operator allows you to perform assignments inside of conditional expresssions. - ```pluto local get_value = || -> 1 @@ -116,7 +115,7 @@ else -- scope of 'val' ends end ``` -Note that for while-loops, it will be executed as many times as the condition: +It can also be used in while loops, where it will be executed as many times as the condition: ```pluto norun title="Pluto Way" while a := next_value() do -- ... @@ -130,6 +129,22 @@ while true do end ``` +In both cases, it is not limited to initialize only a single variable, although only the first is checked for truthiness: +```pluto +local co = coroutine.create(function() + while true do + while _has_val, val := coroutine.yield() do + print(val) + end + end +end) +co:resume() -- start coroutine +co:resume(true, 1) --> 1 +co:resume(false) +co:resume(true, 2) --> 2 +co:resume(true, nil) --> nil +``` + ## Spaceship Operator The spaceship operator, also known as the three-way comparison operator, allows you to quickly compare 2 values for equality and order. diff --git a/docs/Optimizations/Table Length.md b/docs/Optimizations/Table Length.md deleted file mode 100644 index 6d5ed7f48..000000000 --- a/docs/Optimizations/Table Length.md +++ /dev/null @@ -1 +0,0 @@ -Pluto will cache the length of a table when you request it for the first time. This cache is refreshed whenever you make an edit to the table. It's still advisable to localize this value though, since looking through the cache and the virtual machine is expensive compared to looking up a local. \ No newline at end of file diff --git a/docs/Runtime Environment/Buffer.md b/docs/Runtime Environment/Buffer.md new file mode 100644 index 000000000..8111947ed --- /dev/null +++ b/docs/Runtime Environment/Buffer.md @@ -0,0 +1,38 @@ +The buffer class — available via `require` — is a fast intermediate storage for incrementally-constructed strings. + +--- +### `buffer.new` + +Creates a new buffer instance. + +--- +### `buffer.append` + +Appends a string to a buffer instance. + +#### Parameters + +1. The buffer instance. +2. The string to append. + +--- +### `buffer.tostring`, `__tostring` + +Converts the buffer into a string. + +#### Parameters + +1. The buffer instance. + +#### Returns + +A string. + +```pluto +local buffer = require "pluto:buffer" + +local buff = new buffer() +buff:append("Hello,") +buff:append(" world!") +print(buff) --> Hello, world! +``` diff --git a/docs/Runtime Environment/Crypto.md b/docs/Runtime Environment/Crypto.md index b85ac2c05..5e85470ae 100644 --- a/docs/Runtime Environment/Crypto.md +++ b/docs/Runtime Environment/Crypto.md @@ -9,17 +9,7 @@ Hash a string using Lua's version of the DJB2 non-cryptographic hashing algorith ```pluto local crypto = require("crypto") local str = "hello world" -assert(crypto.lua(str) == 2871868277) -``` ---- -### `crypto.md5` -Hash a string using the MD5 semi-cryptographic hashing algorithm. -#### Parameters -1. The string to hash. -```pluto -local crypto = require("crypto") -local str = "hello world" -assert(crypto.md5(str) == "5eb63bbbe01eeed093cb22bb8f5acdc3") +print(crypto.lua(str)) --> 2871868277 ``` --- ### `crypto.djb2` @@ -31,7 +21,7 @@ An integer between 0 and 0xffffffff, inclusive. ```pluto local crypto = require("crypto") local str = "hello world" -assert(crypto.djb2(str) == 894552257) +print(crypto.djb2(str)) --> 894552257 ``` --- ### `crypto.fnv1` @@ -41,7 +31,7 @@ Hash a string using the FNV1 non-cryptographic hashing algorithm. ```pluto local crypto = require("crypto") local str = "hello world" -assert(crypto.fnv1(str) == 9065573210506989167) +print(crypto.fnv1(str)) --> 9065573210506989167 ``` --- ### `crypto.fnv1a` @@ -51,7 +41,7 @@ Hash a string using the FNV1A non-cryptographic hashing algorithm. ```pluto local crypto = require("crypto") local str = "hello world" -assert(crypto.fnv1a(str) == 8618312879776256743) +print(crypto.fnv1a(str)) --> 8618312879776256743 ``` --- ### `crypto.joaat` @@ -63,7 +53,7 @@ An integer between 0 and 0xffffffff, inclusive. ```pluto local crypto = require("crypto") local str = "hello world" -assert(crypto.joaat(str) == 1045060183) +print(crypto.joaat(str)) --> 1045060183 ``` --- ### `crypto.sdbm` @@ -75,7 +65,7 @@ An integer between 0 and 0xffffffff, inclusive. ```pluto local crypto = require("crypto") local str = "hello world" -assert(crypto.sdbm(str) == 430867652) +print(crypto.sdbm(str)) --> 430867652 ``` --- ### `crypto.crc32` @@ -88,7 +78,20 @@ An integer between 0 and 0xffffffff, inclusive. ```pluto local crypto = require("crypto") local str = "hello world" -assert(crypto.crc32(str) == 222957957) +print(crypto.crc32(str)) --> 222957957 +``` +--- +### `crypto.crc32c` +Hash a string using the CRC32C non-cryptographic hashing algorithm. +#### Parameters +1. The string to hash. +2. The initial value for the hash. By default, this is zero. +#### Returns +An integer between 0 and 0xffffffff, inclusive. +```pluto +local crypto = require("crypto") +local str = "hello world" +print(crypto.crc32c(str)) --> 3381945770 ``` --- ### `crypto.adler32` @@ -99,7 +102,7 @@ Hash a string using the Adler-32 non-cryptographic hashing algorithm. ```pluto local crypto = require("crypto") local str = "hello" -assert(crypto.adler32(str) == 103547413) +print(crypto.adler32(str)) --> 103547413 ``` --- ### `crypto.lookup3` @@ -109,7 +112,7 @@ Hash a string using the Lookup3 non-cryptographic hashing algorithm. ```pluto local crypto = require("crypto") local str = "hello world" -assert(crypto.lookup3(str) == 1252609637) +print(crypto.lookup3(str)) --> 1252609637 ``` --- ### `crypto.times33` @@ -121,7 +124,7 @@ An integer between 0 and 0xffffffff, inclusive. ```pluto local crypto = require("crypto") local str = "hello world" -assert(crypto.times33(str) == 3889643616) +print(crypto.times33(str)) --> 3889643616 ``` ### `crypto.murmur1` Hash a string using the Murmur1 non-cryptographic hashing algorithm. @@ -132,7 +135,7 @@ An integer between 0 and 0xffffffff, inclusive. ```pluto local crypto = require("crypto") local str = "hello world" -assert(crypto.murmur1(str) == 3154674178) +print(crypto.murmur1(str)) --> 3154674178 ``` --- ### `crypto.murmur2` @@ -144,7 +147,7 @@ An integer between 0 and 0xffffffff, inclusive. ```pluto local crypto = require("crypto") local str = "hello world" -assert(crypto.murmur2(str) == 1151865881) +print(crypto.murmur2(str)) --> 1151865881 ``` ### `crypto.murmur2a` Hash a string using the Murmur2A non-cryptographic hashing algorithm. @@ -155,7 +158,7 @@ An integer between 0 and 0xffffffff, inclusive. ```pluto local crypto = require("crypto") local str = "hello world" -assert(crypto.murmur2a(str) == 2650573207) +print(crypto.murmur2a(str)) --> 2650573207 ``` ### `crypto.murmur64a` Hash a string using the Murmur64A non-cryptographic hashing algorithm. @@ -164,7 +167,7 @@ Hash a string using the Murmur64A non-cryptographic hashing algorithm. ```pluto local crypto = require("crypto") local str = "hello world" -assert(crypto.murmur64a(str) == -3190198453633110066) +print(crypto.murmur64a(str)) --> -3190198453633110066 ``` ### `crypto.murmur64b` Hash a string using the Murmur64A non-cryptographic hashing algorithm. @@ -173,7 +176,7 @@ Hash a string using the Murmur64A non-cryptographic hashing algorithm. ```pluto local crypto = require("crypto") local str = "hello world" -assert(crypto.murmur64b(str) == 7088720765356542432) +print(crypto.murmur64b(str)) --> 7088720765356542432 ``` --- ### `crypto.murmur2neutral` @@ -183,7 +186,20 @@ Hash a string using the Murmur2Neutral non-cryptographic hashing algorithm. ```pluto local crypto = require("crypto") local str = "hello world" -assert(crypto.murmur2neutral(str) == 1151865881) +print(crypto.murmur2neutral(str)) --> 1151865881 +``` +--- +## Semi-Cryptographic Hashing Algorithms +### `crypto.md5` +Hash a string using the MD5 semi-cryptographic hashing algorithm. +#### Parameters +1. The string to hash. +2. When set to true, returns raw binary data. false outputs lowercase hex digits. By default, this is false. +```pluto +local crypto = require("crypto") +local str = "hello world" +print(crypto.md5(str)) --> 5eb63bbbe01eeed093cb22bb8f5acdc3 +print(crypto.md5(str, true)) --> \x5e\xb6\x3b\xbb\xe0\x1e\xee\xd0\x93\xcb\x22\xbb\x8f\x5a\xcd\xc3 ``` --- ## Cryptographic Hashing Algorithms @@ -194,8 +210,8 @@ Hash a string using the SHA-1 cryptographic hashing algorithm. 2. When set to true, returns raw binary data. false outputs lowercase hex digits. By default, this is false. ```pluto local crypto = require("crypto") -assert(crypto.sha1("Pluto") == "bce8c9aca4120776fad6b517874aa09c46405454") -assert(crypto.sha1("Pluto", true) == "\xbc\xe8\xc9\xac\xa4\x12\x07\x76\xfa\xd6\xb5\x17\x87\x4a\xa0\x9c\x46\x40\x54\x54") +print(crypto.sha1("Pluto")) --> bce8c9aca4120776fad6b517874aa09c46405454 +print(crypto.sha1("Pluto", true)) --> \xbc\xe8\xc9\xac\xa4\x12\x07\x76\xfa\xd6\xb5\x17\x87\x4a\xa0\x9c\x46\x40\x54\x54 ``` --- ### `crypto.sha256` @@ -205,8 +221,8 @@ Hash a string using the SHA-256 cryptographic hashing algorithm. 2. When set to true, returns raw binary data. false outputs lowercase hex digits. By default, this is false. ```pluto local crypto = require("crypto") -assert(crypto.sha256("Pluto") == "8dad5f6a7dd2dcd8c35ec2fd7babb499bcad60d27d73fe73eca2ce025dfd3b47") -assert(crypto.sha256("Pluto", true) == "\x8d\xad\x5f\x6a\x7d\xd2\xdc\xd8\xc3\x5e\xc2\xfd\x7b\xab\xb4\x99\xbc\xad\x60\xd2\x7d\x73\xfe\x73\xec\xa2\xce\x02\x5d\xfd\x3b\x47") +print(crypto.sha256("Pluto")) --> 8dad5f6a7dd2dcd8c35ec2fd7babb499bcad60d27d73fe73eca2ce025dfd3b47 +print(crypto.sha256("Pluto", true)) --> \x8d\xad\x5f\x6a\x7d\xd2\xdc\xd8\xc3\x5e\xc2\xfd\x7b\xab\xb4\x99\xbc\xad\x60\xd2\x7d\x73\xfe\x73\xec\xa2\xce\x02\x5d\xfd\x3b\x47 ``` --- ### `crypto.sha384` @@ -216,7 +232,7 @@ Hash a string using the SHA-384 cryptographic hashing algorithm. 2. When set to true, returns raw binary data. false outputs lowercase hex digits. By default, this is false. ```pluto local crypto = require("crypto") -assert(crypto.sha384("Pluto", false) == "db890233a919b6745d632633c419e14540ff79f1a89bc4ac194b00e7f913f0f06d5d4d7d6cc2b4aaf9485d223afb8cf0") +print(crypto.sha384("Pluto", false)) --> db890233a919b6745d632633c419e14540ff79f1a89bc4ac194b00e7f913f0f06d5d4d7d6cc2b4aaf9485d223afb8cf0 ``` --- ### `crypto.sha512` @@ -226,7 +242,7 @@ Hash a string using the SHA-512 cryptographic hashing algorithm. 2. When set to true, returns raw binary data. false outputs lowercase hex digits. By default, this is false. ```pluto local crypto = require("crypto") -assert(crypto.sha512("Pluto", false) == "ee8410a8bf9511b94fd6669b5c3e0c4b86e8e4bf7baa8dbd2773d4d6381dd1aecebbe391bef4c6158620ab3f6b794907652d4432c2301d7e1a6caf520565cdf2") +print(crypto.sha512("Pluto", false)) --> ee8410a8bf9511b94fd6669b5c3e0c4b86e8e4bf7baa8dbd2773d4d6381dd1aecebbe391bef4c6158620ab3f6b794907652d4432c2301d7e1a6caf520565cdf2 ``` --- ### `crypto.ripemd160` @@ -235,17 +251,25 @@ Hash a string using the RIPEMD-160 cryptographic hashing algorithm. 1. The string to hash. 2. When set to true, returns raw binary data. false outputs lowercase hex digits. By default, this is false. ```pluto -assert(require"crypto".ripemd160("Pluto") == "c2072a85f4a691803b8942709036072086fd9550") +local crypto = require("crypto") +print(crypto.ripemd160("Pluto")) --> c2072a85f4a691803b8942709036072086fd9550 ``` --- ## Cryptographic PRNGs ### `crypto.random` This is a cryptographically secure PRNG, assuming the platform's implementation of the underlying primitive is secure. +#### Parameters +This function takes 0-2 integer parameters that define the output range: +- If no arguments are given, an inclusive range from `math.mininteger` to `math.maxinteger` is used. +- If 1 argument (`n`) is given, an inclusive range from 1 to `n` is used. +- If 2 arguments (`l`, `u`) are given, an inclusive range from `l` to `u` is used. #### Returns -A random lua integer, in the range from `math.mininteger` to `math.maxinteger`. +A random lua integer, in the given range. ```pluto local crypto = require("crypto") -print(crypto.random()) +print(crypto.random()) -- Prints an integer from math.mininteger to math.maxinteger. +print(crypto.random(6)) -- Prints an integer from 1 to 6, like a dice roll. +print(crypto.random(10, 20)) -- Prints an integer from 10 to 20. ``` --- diff --git a/docs/Runtime Environment/FFI.md b/docs/Runtime Environment/FFI.md index 5143692c1..76c44a1d8 100644 --- a/docs/Runtime Environment/FFI.md +++ b/docs/Runtime Environment/FFI.md @@ -112,6 +112,29 @@ The offset in bytes. ### `ffi.nullptr` This constant can be used to give `0` to a "ptr"-type argument. +--- +### `ffi.alloc` +Creates a userdata with the given size. +#### Parameters +1. The size in bytes. +#### Returns +A new userdata. + +--- +### `ffi.write` +Copies data from a string to a userdata. +#### Parameters +1. The string. +2. The userdata. + +--- +### `ffi.read` +Turns a userdata into a string. +#### Parameters +1. The userdata. +#### Returns +A string. + --- ## FFI Library Class Obtained from `ffi.open`. diff --git a/docs/Runtime Environment/Global & Base.md b/docs/Runtime Environment/Global & Base.md index 1142c094f..612151be6 100644 --- a/docs/Runtime Environment/Global & Base.md +++ b/docs/Runtime Environment/Global & Base.md @@ -8,12 +8,6 @@ This page documents the changes & additions to Pluto's runtime environment, whic ### `_PVERSION` `_PVERSION` is the global to check your current version of Pluto. -### `_PSOUP` -`_PSOUP` is a global boolean you can access to check linkage with Soup. Always true as of 0.8.0. - -### `os.platform` -`os.platform` is a global string containing the host platform. Can be "windows", "wasm", "linux", "macos", "android", or "unknown". - ```pluto if _PVERSION == nil then print("Plain Lua detected (".._VERSION..")") @@ -48,7 +42,9 @@ table.contains(t, 1) table.concat(t, "\n") coroutine.resume(c) ``` -This behavior is implemented by setting the `__index` metamethod to the respective library (`_G.table` or `_G.coroutine`). If you override the metatable, you may want to replicate that. +The default metatables for the types are as follows: +- Table: `{ __mindex = _G.table }` (using Pluto's own [`__mindex` metamethod](<../New Features/Mindex Metamethod.md>)) +- Coroutine/thread: `{ __index = _G.coroutine }` --- ### `dumpvar` @@ -155,3 +151,41 @@ local w = wcall(|| -> warn("Bad!")) print(w ~= "" ? (w:strip()) : "No warnings") -- Output: "Bad!" ``` + +--- +### `sdiv`, `udiv` +Performs a standard integer division (signed and unsigned). +```pluto +assert(0xefffffffffffffff // 12 == -96076792050570582) +assert(sdiv(0xefffffffffffffff, 12) == -96076792050570581) +assert(udiv(0xefffffffffffffff, 12) == 1441151880758558719) +``` + +--- +### `smod`, `umod` +Performs a standard integer modulo operation (signed and unsigned). +```pluto +assert(0xefffffffffffffff % 12 == 7) +assert(smod(0xefffffffffffffff, 12) == -5) +assert(umod(0xefffffffffffffff, 12) == 11) +``` + +--- +### `callonce` +Calls the given function exactly once and then caches its return value. +```pluto +local function expensive_constructor() + print("expensive_constructor called") + return { "A", "B", "C" } +end + +for i = 1, 3 do + local t = callonce(expensive_constructor) + print(t[i]) +end + +--> expensive_constructor called +--> A +--> B +--> C +``` diff --git a/docs/Runtime Environment/IO.md b/docs/Runtime Environment/IO.md index 3009534a3..5b666014e 100644 --- a/docs/Runtime Environment/IO.md +++ b/docs/Runtime Environment/IO.md @@ -25,13 +25,14 @@ end ``` --- ### `io.part` -Extracts the given part from a path. +Extracts the parts of a path. #### Parameters 1. A string path or file stream. -2. The part to return, "parent" or "name". +2. Optionally, which part to return, "parent" or "name". #### Returns -The extracted part. +The extracted part(s) of the path. ```pluto +print(io.part("/path/to/foo.txt")) -- "/path/to", "foo.txt" print(io.part("/path/to/foo.txt", "parent")) -- "/path/to" print(io.part("/path/to/foo.txt", "name")) -- "foo.txt" ``` @@ -185,3 +186,28 @@ If this function is acting as a *getter*, it will return the current working dir local cwd = io.currentdir() -- Get cwd io.currentdir("abc/abc") -- Set a new cwd ``` + +--- +### `io.chmod` +Get or set file permissions on Unix-like systems. +#### Parameters +1. A string path or file stream. If this parameter is absent, this function works as an *availability check*. +2. The desired mode/permissions. If this parameter is absent, this function works as a *getter*. +#### Returns +If this function is acting as an *availability check*, it will return a boolean. + +If this function is acting as a *getter*, it will return an integer if used on a supporting platform and there wasn't an error `stat`ing the file. + +```pluto +io.contents("chmod-test.txt", "") -- create chmod-test.txt with no contents +if mode := io.chmod("chmod-test.txt") then + print("File mode: %o":format(mode)) + mode |= 0o111 -- +x + io.chmod("chmod-test.txt", mode) + print("New file mode: %o":format(mode)) +elseif io.chmod() then + print("Failed to stat chmod-test.txt") +else + print("chmod is not available on this platform.") +end +``` diff --git a/docs/Runtime Environment/OS.md b/docs/Runtime Environment/OS.md index 8ad9e5300..1137e6fb9 100644 --- a/docs/Runtime Environment/OS.md +++ b/docs/Runtime Environment/OS.md @@ -1,5 +1,13 @@ This page documents the changes & additions to the `os` library in Pluto, which is built on top of Lua 5.4's. +--- +### `os.platform` +`os.platform` is a global string containing the host platform. Can be "windows", "wasm", "linux", "macos", "android", or "unknown". + +--- +### `os.arch` +`os.arch` is a global string containing the host CPU architecture as well as the bit width, separated by a comma. For example, it would be `x86, 64-bit` for x86_64, `arm, 64-bit` for ARM64, and `wasm, 32-bit` for a typical WebAssembly environment. + --- ### `os.sleep` #### Parameters diff --git a/docs/Runtime Environment/Socket.md b/docs/Runtime Environment/Socket.md index a6ba1affd..d897e6722 100644 --- a/docs/Runtime Environment/Socket.md +++ b/docs/Runtime Environment/Socket.md @@ -6,6 +6,7 @@ Establishes a TCP connection. #### Parameters 1. The host to connect to. Either an IPv4 or IPv6 address, or a domain name resolving to one. 2. The port to contact the host on. +3. The transport to use, "tcp" or "udp". Defaults to "tcp". #### Returns A socket instance on success. Nil on failure. #### Multitasking @@ -15,7 +16,7 @@ If called inside of a coroutine, this function yields. Otherwise, it blocks. ### `socket.listen` Creates a new listener for the given port. #### Parameters -1. The port to listen on. +1. Either an int with the port to listen on or a string such as `1.2.3.4:567` for systems with multiple public-facing addresses. #### Returns A listener instance on success. Nil on failure. @@ -24,7 +25,7 @@ A listener instance on success. Nil on failure. A convenience function that wraps `socket.listen`, automatically accepting new clients and spinning up a coroutine for them. #### Parameters 1. A [scheduler](Scheduler) instance. -2. The port to listen on. +2. Either an int with the port to listen on or a string such as `1.2.3.4:567` for systems with multiple public-facing addresses. 3. The callback function that will be called in a new coroutine for each client socket. ```pluto norun local { scheduler, socket } = require "*" @@ -37,6 +38,22 @@ end) sched:run() ``` +--- +### `socket.udpserver` +Creates a pseudo-socket to listen for UDP datagrams on the given port. +#### Parameters +1. Either an int with the port to listen on or a string such as `1.2.3.4:567` for systems with multiple public-facing addresses. +#### Returns +A pseudo-socket which can `recv` UDP datagrams and then `send` a response. +```pluto norun +local serv = socket.udpserver(30726) +while data := serv:recv() do + if data == "ping" then + serv:send("pong") + end +end +``` + --- ## Socket Class Socket instances are obtained by calling `socket.connect` (client), or from a listener (server). @@ -169,6 +186,17 @@ assert(sock:starttls("1.1.1.1")) print(sock:istls()) --> true ``` +### `socket.isudp` +Check if a connection is using UDP transport. +#### Parameters +1. The socket instance. +```pluto norun +local sock = require"socket".connect("1.1.1.1", 443, "tcp") +print(sock:isudp()) --> false +sock = require"socket".connect("1.1.1.1", 53, "udp") +print(sock:isudp()) --> true +``` + ### `socket.isopen` Check if a connection is still open. #### Parameters diff --git a/docs/Runtime Environment/String.md b/docs/Runtime Environment/String.md index 8cf499e85..bbcc6b2a0 100644 --- a/docs/Runtime Environment/String.md +++ b/docs/Runtime Environment/String.md @@ -19,21 +19,24 @@ assert(s:lower(1) == "hELLO") Splits a string by a separator. #### Parameters 1. The string to split. -2. The separator to split a string by. This can be any string. Defaults to `,`. +2. The separator to split the string by. 3. An optional limit for the returned table size. #### Returns A table. +```pluto title="Splitting a string by an empty separator" +print(dumpvar("ABC":split(""))) -- { "A", "B", "C" } +``` ```pluto title="Splitting a string by a single character" local s = "hello world, how is everyone doing?" -string.split(s, " ") -- { "hello", "world,", "how", "is", "everyone", "doing?" } +print(dumpvar(s:split(" "))) -- { "hello", "world,", "how", "is", "everyone", "doing?" } ``` ```pluto title="Splitting a string by a substring" local s = "helloFOOworld,FOOhowFOOisFOOeveryoneFOOdoing?" -string.split(s, "FOO") -- { "hello", "world,", "how", "is", "everyone", "doing?" } +print(dumpvar(s:split("FOO"))) -- { "hello", "world,", "how", "is", "everyone", "doing?" } ``` ```pluto title="Splitting a string by a single character with a limit" local s = "hello world, how is everyone doing?" -string.split(s, " ", 3) -- { "hello", "world,", "how is everyone doing?" } +print(dumpvar(s:split(" ", 3))) -- { "hello", "world,", "how is everyone doing?" } ``` --- ### `string.rfind` diff --git a/docs/Runtime Environment/Table.md b/docs/Runtime Environment/Table.md index be85c2723..866399624 100644 --- a/docs/Runtime Environment/Table.md +++ b/docs/Runtime Environment/Table.md @@ -32,28 +32,6 @@ t:clear() print(t:size()) --> 0 ``` --- -### `table.freeze` -Freezes a table to prevent modification. -#### Parameters -1. The table to freeze -#### Returns -The input table. -```pluto -local t = table.freeze({...}) --- `table.freeze(t)` on another line will work fine too. -t.key = "value" -- Fails. -``` ---- -### `table.isfrozen` -Checks if this table is frozen. -#### Parameters -1. The table to check. -```pluto -local t = {} -table.freeze(t) -assert(table.isfrozen(t) == true) -``` ---- ### `table.contains` Checks if this table contains an element. #### Parameters @@ -269,13 +247,28 @@ Returns a new array-like table containing all keys from the table. local t = { ["key1"] = "value1", ["key2"] = "value2", - "sequence", - "sequence sequence" + "foo", + "bar" } print(dumpvar(t:keys())) -- { 1, 2, "key1", "key2" } ``` --- +### `table.values` +Returns a new array-like table containing all values from the table. +#### Parameters +1. The table. +```pluto +local t = { + ["key1"] = "value1", + ["key2"] = "value2", + "foo", + "bar" +} + +print(dumpvar(t:values())) -- { "foo", "bar", "value1", "value2" } +``` +--- ### `table.countvalues` Returns a key-value based table which describes how many times a value appears inside of a table. #### Parameters @@ -292,6 +285,25 @@ local t = { print(dumpvar(t:countvalues())) -- { [1] = 1, [2] = 2, [3] = 3, [4] = 4, ["value"] = 2 } ``` +--- +### `table.dedup`, `table.deduplicate` +Sets any keys with a duplicate value in the table to nil. +#### Parameters +1. The table. +```pluto +local t = { + 1, + 2, 2, + 3, 3, 3, + 4, 4, 4, 4, +} + +print(dumpvar(t:dedup())) -- { [1] = 1, [2] = 2, [4] = 3, [7] = 4 } +print(dumpvar(t:dedup():reorder())) -- { 1, 2, 3, 4 } +``` +### `table.deduped`, `table.deduplicated` +Copying variant of `table.dedup`/`table.deduplicate`; returns a new table instead of modifying the input table. Note that nested tables will not be copied. + --- ### `table.chunk` Generates a new table which collects the values of the input and represents them in chunks of a specified size. @@ -312,6 +324,21 @@ local t = { print(dumpvar(t:chunk(3))) -- { { 1, 2, 3 }, { "hello", "world" } } ``` --- +### `table.invert` +Generates a new table with an inverse key-value relationship to the input table. +#### Parameters +1. The table. +```pluto +local t = { + ["key1"] = "value1", + ["key2"] = "value2", + "foo", + "bar" +} + +print(dumpvar(t:invert())) -- { value1 = "key1", value2 = "key2", foo = 1, bar = 2 } +``` +--- ### `table.back` Returns the last element of a table. This is functionally identical to `t[#t]`. #### Parameters diff --git a/src/theme/Pluto.tmLanguage.json b/src/theme/Pluto.tmLanguage.json index 92ee31830..73d4e00dd 100644 --- a/src/theme/Pluto.tmLanguage.json +++ b/src/theme/Pluto.tmLanguage.json @@ -94,6 +94,40 @@ } ] }, + { + "begin": "(\\$alias)(?:\\s+(?:[a-zA-Z_][a-zA-Z0-9_]*([.:]))?([a-zA-Z_][a-zA-Z0-9_]*))?\\s*(\\()", + "beginCaptures": { + "1": { + "name": "storage.modifier.function.preprocessoralias.pluto" + }, + "2": { + "name": "punctuation.separator.parameter.pluto" + }, + "3": { + "name": "entity.name.function.preprocessoralias.pluto" + }, + "4": { + "name": "punctuation.section.group.begin.pluto" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.section.group.end.pluto" + } + }, + "name": "meta.preprocessoralias.pluto", + "patterns": [ + { + "match": "[a-zA-Z_][a-zA-Z0-9_]*", + "name": "variable.parameter.preprocessoralias.pluto" + }, + { + "match": ",", + "name": "punctuation.separator.comma.pluto" + } + ] + }, { "match": "\\b(function)($|\\s+)(?:[a-zA-Z_][a-zA-Z0-9_]*([.:]))?([a-zA-Z_][a-zA-Z0-9_]*)?", "captures": { @@ -272,7 +306,7 @@ "name": "storage.type.attribute.pluto" }, { - "match": "\\$define\\b", + "match": "\\$(define|alias)\\b", "name": "storage.modifier.pluto" }, { @@ -353,7 +387,7 @@ "name": "keyword.control.pluto" } }, - "match": "\\b((?:pluto_)?enum(?:\\s+class)?)(?:\\s+(begin)|(?:\\s+([a-zA-Z_][a-zA-Z0-9_]*)?)(?:\\s+(begin))?)?", + "match": "\\b((?:pluto_)?enum(?:\\s+class)?)(?:\\s+(begin|do)|(?:\\s+([a-zA-Z_][a-zA-Z0-9_]*)?)(?:\\s+(begin|do))?)?", "name": "meta.enum.pluto" }, { @@ -408,7 +442,11 @@ } }, { - "match": "\\b(?