diff options
42 files changed, 9650 insertions, 7 deletions
diff --git a/Data/BuiltIn/Libraries/GameLab/Class.lua b/Data/BuiltIn/Libraries/GameLab/Class.lua index 8a18182..db8e2b2 100644 --- a/Data/BuiltIn/Libraries/GameLab/Class.lua +++ b/Data/BuiltIn/Libraries/GameLab/Class.lua @@ -1,9 +1,38 @@ --- Declare class
+-- 声明类型
--- 保留方法:New, Ctor, Is
+-- 保留方法:New, Ctor, Is, get_, set_
-- 保留字段:_type, _base
-local TrackClassInstances = false
+local TRACKCLASSINSTANCES = false
+local PROPERTY = true
+
+local index = function(self, key)
+ local c = getmetatable(self)
+
+ local field = rawget(c, key)
+ if field then
+ return field
+ end
+
+ local getter = rawget(c, 'get_' .. key)
+ if getter then
+ return getter(self)
+ end
+
+ return nil
+end
+
+local newindex = function(self, key, value)
+ local c = getmetatable(self)
+
+ local setter = rawget(c, 'set_' .. key)
+ if setter then
+ setter(self, value)
+ return
+ end
+
+ rawset(self, key, value)
+end
local newclass = function (name, ctor)
local c = {}
@@ -22,8 +51,13 @@ local newclass = function (name, ctor) fullName = name
}
- c.__index = c
-
+ if PROPERTY then
+ c.__index = index
+ c.__newindex = newindex
+ else
+ c.__index = c
+ end
+
c.New = function(...)
local obj = {}
setmetatable(obj, c)
diff --git a/Data/BuiltIn/Libraries/GameLab/Containers/Array.lua b/Data/BuiltIn/Libraries/GameLab/Containers/Array.lua new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Data/BuiltIn/Libraries/GameLab/Containers/Array.lua diff --git a/Data/BuiltIn/Libraries/GameLab/Containers/Tuple.lua b/Data/BuiltIn/Libraries/GameLab/Containers/Tuple.lua new file mode 100644 index 0000000..aa7f8bc --- /dev/null +++ b/Data/BuiltIn/Libraries/GameLab/Containers/Tuple.lua @@ -0,0 +1,49 @@ +--[[
+
+ (...) -> tuple
+ wrap(t, [n]) -> tuple
+
+]]
+local meta = {__type = 'tuple'}
+
+local function wrap(t, n)
+ t.n = n or t.n or #t
+ setmetatable(t, meta)
+ return t
+end
+
+local function new(...)
+ return wrap({n=select('#',...),...})
+end
+
+function meta:__eq(other)
+ if self.n ~= other.n then
+ return false
+ end
+ for i=1,self.n do
+ if self[i] ~= other[i] then
+ return false
+ end
+ end
+ return true
+end
+
+function meta:__tostring()
+ local t = {}
+ for i=1,self.n do
+ t[i] = tostring(self[i])
+ end
+ return '('..table.concat(t, ', ', 1, self.n)..')'
+end
+
+local M = {
+ meta = meta,
+ wrap = wrap,
+ new = new,
+}
+
+local tuple = setmetatable(M, {__call = function(_,...) return new(...) end})
+
+GameLab.Containers.Tuple = tuple
+
+return tuple
diff --git a/Data/BuiltIn/Libraries/GameLab/Containers/init.lua b/Data/BuiltIn/Libraries/GameLab/Containers/init.lua new file mode 100644 index 0000000..2568cd9 --- /dev/null +++ b/Data/BuiltIn/Libraries/GameLab/Containers/init.lua @@ -0,0 +1,6 @@ +local m = GameLab.Package("GameLab.Containers")
+local import = GameLab.Import(...)
+
+import("Tuple")
+
+return m
\ No newline at end of file diff --git a/Data/BuiltIn/Libraries/GameLab/Enum.lua b/Data/BuiltIn/Libraries/GameLab/Enum.lua index 52c2beb..b7808fa 100644 --- a/Data/BuiltIn/Libraries/GameLab/Enum.lua +++ b/Data/BuiltIn/Libraries/GameLab/Enum.lua @@ -1,4 +1,4 @@ --- Declare enum +-- 声明枚举 local Debug = GameLab.Debug diff --git a/Data/BuiltIn/Libraries/lua-stdlib/.gitignore b/Data/BuiltIn/Libraries/lua-stdlib/.gitignore new file mode 100644 index 0000000..dbac14c --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-stdlib/.gitignore @@ -0,0 +1,9 @@ +*~ +.DS_Store +/*.src.rock +/Makefile +/build-aux/config.ld +/doc +/lib/std/version.lua +/luacov.*.out +/stdlib-*.tar.gz diff --git a/Data/BuiltIn/Libraries/lua-stdlib/.luacov b/Data/BuiltIn/Libraries/lua-stdlib/.luacov new file mode 100644 index 0000000..40b9d6a --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-stdlib/.luacov @@ -0,0 +1,52 @@ +return { + -- filename to store stats collected + ["statsfile"] = "luacov.stats.out", + + -- filename to store report + ["reportfile"] = "luacov.report.out", + + -- luacov.stats file updating frequency. + -- The lower this value - the more frequenty results will be written out to luacov.stats + -- You may want to reduce this value for short lived scripts (to for example 2) to avoid losing coverage data. + ["savestepsize"] = 100, + + -- Run reporter on completion? (won't work for ticks) + runreport = true, + + -- Delete stats file after reporting? + deletestats = false, + + -- Process Lua code loaded from raw strings + -- (that is, when the 'source' field in the debug info + -- does not start with '@') + codefromstrings = false, + + -- Patterns for files to include when reporting + -- all will be included if nothing is listed + -- (exclude overrules include, do not include + -- the .lua extension, path separator is always '/') + ["include"] = { + "lib/std/_base$", + "lib/std/debug$", + "lib/std/init$", + "lib/std/io$", + "lib/std/math$", + "lib/std/package$", + "lib/std/string$", + "lib/std/table$", + --"lib/std/version$", + }, + + -- Patterns for files to exclude when reporting + -- all will be included if nothing is listed + -- (exclude overrules include, do not include + -- the .lua extension, path separator is always '/') + ["exclude"] = { + "luacov$", + "luacov/reporter$", + "luacov/defaults$", + "luacov/runner$", + "luacov/stats$", + "luacov/tick$", + }, +} diff --git a/Data/BuiltIn/Libraries/lua-stdlib/.travis.yml b/Data/BuiltIn/Libraries/lua-stdlib/.travis.yml new file mode 100644 index 0000000..92f4f73 --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-stdlib/.travis.yml @@ -0,0 +1,34 @@ +language: python + +sudo: false + +env: + matrix: + - LUA="lua=5.3" + - LUA="lua=5.2" + - LUA="lua=5.1" + - LUA="luajit=2.1" + - LUA="luajit=2.0" + +before_install: + - pip install hererocks + - hererocks here -r^ --$LUA --patch + - export PATH=$PWD/here/bin:$PATH + +install: + - luarocks install ldoc + - luarocks install ansicolors + - luarocks install specl + - luarocks install luacov + +script: + - make + - luarocks make + - make check SPECL_OPTS='-vfreport --coverage' + +after_success: + - tail luacov.report.out + - bash <(curl -s https://codecov.io/bash) -v + +notifications: + slack: aspirinc:JyWeNrIdS0J5nf2Pn2BS1cih diff --git a/Data/BuiltIn/Libraries/lua-stdlib/AUTHORS.md b/Data/BuiltIn/Libraries/lua-stdlib/AUTHORS.md new file mode 100644 index 0000000..6589984 --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-stdlib/AUTHORS.md @@ -0,0 +1,27 @@ +# Stdlib's contributors + +This file lists major contributors to _stdlib_. If you think you +should be on it, please raise a [github][] issue. Thanks also to all +those who have contributed bug fixes, suggestions and support. + +Gary V. Vaughan now maintains _stdlib_, having rewritten and reorganised +the libraries for hygiene, consistent argument type checking in debug +mode, and object orientation, in addition to adding a lot of new +functionality. + +Reuben Thomas started the standard libraries project, wrote many of the +libraries, and integrated code from other authors. + +John Belmonte helped set the project up on lua-users, and contributed +to the early organisation of the libraries. + +The call trace debugging code is based on test/trace-calls.lua from +the Lua 5.0 distribution. + +Jamie Webb contributed several miscellaneous functions from his +private standard library. + +Johann Hibschman supplied the code on which math.floor and math.round +were based. + +[github]: https://github.com/lua-stdlib/lua-stdlib/issues diff --git a/Data/BuiltIn/Libraries/lua-stdlib/LICENSE.md b/Data/BuiltIn/Libraries/lua-stdlib/LICENSE.md new file mode 100644 index 0000000..c2b4620 --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-stdlib/LICENSE.md @@ -0,0 +1,20 @@ +Copyright (C) 2002-2018 stdlib authors + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGE- +MENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF +CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Data/BuiltIn/Libraries/lua-stdlib/NEWS.md b/Data/BuiltIn/Libraries/lua-stdlib/NEWS.md new file mode 100644 index 0000000..fdbf720 --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-stdlib/NEWS.md @@ -0,0 +1,1414 @@ +# Stdlib NEWS - User visible changes + +## Noteworthy changes in release ?.? (????-??-??) [?] + +### New features + + - Overhaul of the LDoc documentation, adding more introductory + material, clearer usage examples and better internal consistency. At + this point we're pushing the technical limits of what LDoc can do for + us organization-wise, but improvements and corrections to the content + are always welcome! + + - With this release, stdlib is much more focused, and non-core modules + `optparse`, `std.functional`, 'std.prototype', `std.strict` and + `typecheck` have been moved into their own packages and release + cycle. Also, the shared debug initialization, Lua host normalization + and API deprecation code have been split out into new 'std._debug', + 'std.normalize' and 'apimaturity' respectively, and are pulled in + automatically as dependencies for any of any modules that need them. + You can still install them all separately from their own projects or + by using Luarocks: + + ```bash + luarocks install optparse + luarocks install std.functional + luarocks install std.prototype + luarocks install std.strict + luarocks install typecheck + ``` + + - All support for previously deprecated APIs has been removed, reducing + the install size even further. + + - `std.string.prettytostring` continues to use `normalize.string.render` + for more legible deeply nested table output, identically to previous + releases. + + - `std.npairs` and `std.rnpairs` now respect `__len` metamethod, if any. + + - `std.table.okeys` has been removed for lack of utility. If you + still need it, use this instead: + + ```lua + local okeys = std.functional.compose (std.table.keys, std.table.sort) + ``` + +### Bug fixes + + - `std.string.wrap` doesn't throw a StrBuf deprecation warning any more. + + - `std.getmetamethod` now returns functable valued metamethods + correctly, rather than `nil` as in previous releases. It's also + considerably faster now that it doesn't use `pcall` any more. + + - `table.pack` now sets `n` field to number of arguments packed, even + in Lua 5.1. + +### Incompatible changes + + - `std.container`, `std.functional`, `std.list`, `std.maturity`, + `std.object`, `std.operator`, `std.optparse`, `std.set`, + `std.strbuf`, `std.strict` and `std.tuple` have been moved to their + own packages, and are no longer shipped as part of stdlib. + + - Monkey patching calls `std.barrel`, `std.monkey_patch`, + `std.io.monkey_patch`, `std.math.monkey_patch`, + `std.string.monkey_patch` and `std.table.monkey_patch` have all + been removed. + + - `std.debug.argerror`, `std.debug.argcheck`, `std.debug.argscheck`, + `std.debug.extramsg_mismatch`, `std.debug.extramsg_toomany`, + `std.debug.parsetypes`, `std.debug.resulterror` and `std.debug.typesplit` + have all been moved to their own package, and are no longer shipped + as part of stdlib. + + - `std.debug.DEPRECATED` and `std.debug.DEPRECATIONMSG` have been + removed. At some point these will resurface in a new standalone + package. + + - Deprecated functions `string.assert`, `string.require_version`, + `string.tostring`, `table.clone_rename`, `table.metamethod`, + `table.ripairs` and `table.totable` have been removed. See previous + NEWS entries below for what they were replaced by. + + - Passing a table with a `__len` metamethod, that returns a value other + the index of the largest non-nil valued integer key, to `std.npairs` + now iterates upto whatever `__len` returns rather than `std.table.maxn`. + If `__len` is not present, or gives the same result as `maxn` then + `npairs` continues to behave as in the previous release. + + - `std.tostring` and `std.string.render` have been superceded by their + equivalents from 'std.normalize': `str` and `string.render`. Those + implementations handle skipping initial sequence keys for a more + compact output, escaping of whitespace and other C escape characters + for even more compact output and stringification of nested Objects and + Containers using their `__tostring` metamethods. + + - For consistency with std.normalize and other package symbols, we now + spell `package.path_mark` as `package.pathmark`. + + +## Noteworthy changes in release 41.2.0 (2015-03-08) [stable] + +### New features + + - New iterators, `std.npairs` and `std.rnpairs` behave like + `std.ipairs` and `std.ripairs` resp., except that they will visit + all integer keyed elements, including nil-valued "holes". This is + useful for iterating over argument lists with nils: + + ```lua + function fn(a, b, c) for _, v in npairs {...} do print(v) end + fn(nil, nil, 3) --> nil nil 3 + ``` + + - New `debug.getfenv` and `debug.setfenv` that work with Lua 5.2 and + 5.3. + + - `debug.argscheck` will skip typecheck for `self` parameter if the + function name specification contains a colon. + + - New `debug.resulterror` is much like `debug.argerror`, but uses the + message format "bad result #n from 'fname'". + + - New `debug.extramsg_mismatch` to generate `extramsg` argument for + `debug.argerror` or `debug.resulterror` on encountering a type + mismatch. + + - New `debug.extramsg_toomany` to generate a too many arguments or + similar `extramsg` argument. + +### Deprecations + + - `debug.toomanyargmsg` has been deprecated in favour of the more + orthogal `debug.extramsg_toomany` api. You can rewrite clients of + deprecated api like this: + + ```lua + if maxn(argt) > 7 then + argerror('fname', 8, extramsg_toomany('argument', 7, maxn(argt)), 2) + end + ``` + +### Bug fixes + + - `std.getmetamethod` no longer rejects non-table subjects when + `_DEBUG.argcheck` is set. + + - `functional.bind`, `functional.collect`, `functional.compose`, + `functional.filter` and `functional.map` propagate nil valued + arguments correctly. + + - `functional.callable` no longer raises an argument error when passed + a nil valued argument. + + - `debug.argcheck` and `debug.argscheck` accept "bool" as an alias for + "boolean" consistently. + + - `io.catdir` and `io.dirname` no longer leak extra results from + implementation details. + +### Incompatible changes + + - `functional.collect` uses `std.npairs` as a default iterator rather + than `std.ipairs`. + + +## Noteworthy changes in release 41.1.1 (2015-01-31) [stable] + +### Bug fixes + + - `std.barrel` no longer gets stuck in an infinite loop when called in + Lua 5.3. + + +## Noteworthy changes in release 41.1.0 (2015-01-30) [stable] + +### New features + + - Anything that responds to `tostring` can be appended to a `std.strbuf`: + + ```lua + local a, b = StrBuf {'foo', 'bar'}, StrBuf {'baz', 'quux'} + a = a .. b --> 'foobarbazquux' + ``` + + - `std.strbuf` stringifies lazily, so adding tables to a StrBuf + object, and then changing the content of them before calling + `tostring` also changes the contents of the buffer. See LDocs for + an example. + + - `debug.argscheck` accepts square brackets around final optional + parameters, which is distinct to the old way of appending `?` or + `|nil` in that no spurious "or nil" is reported for type mismatches + against a final bracketed argument. + + - `debug.argscheck` can also check types of function return values, when + specified as: + + ```lua + fn = argscheck('fname(?any...) => int, table or nil, string', fname) + ``` + + Optional results can be marked with brackets, and an ellipsis following + the final type denotes any additional results must match that final + type specification. Alternative result type groups are separated by "or". + + - New `table.unpack(t, [i, [j]])` function that defaults j to + `table.maxn(t)`, even on luajit which stops before the first nil + valued numeric index otherwise. + +### Deprecations + + - `std.strbuf.tostring` has been deprecated in favour of `tostring`. + Why write `std.strbuf.tostring(sb)` or `sb:tostring()` when it is + more idiomatic to write `tostring(sb)`? + +### Bug fixes + + - `std.barrel` and the various `monkey_patch` functions now return + their parent module table as documented. + + - stdlib modules are all `std.strict` compliant; require 'std.strict' + before requiring other modules no longer raises an error. + + - `debug.argscheck` can now diagnose when there are too many arguments, + even in the case where the earlier arguments match parameters by + skipping bracketed optionals, and the total number of arguments is + still less than the absolute maximum allowed if optionals are counted + too. + + - `package.normalize` now leaves valid ./../../ path prefixes unmolested. + +### Incompatible changes + + - `debug.argscheck` requires nil parameter type `?` notation to be + prepended to match Specl and TypedLua syntax. `?` suffixes are a + syntax error. + + - `debug.argscheck` uses `...` instead of `*` appended to the final element + if all unmatched argument types should match. The trailing `*` syntax + was confusing, because it was easy to misread it as "followed by zero-or- + more of this type". + + +## Noteworthy changes in release 41.0.0 (2015-01-03) [beta] + +### New features + + - Preliminary Lua 5.3.0 compatibility. + + - `object.prototype` now reports "file" for open file handles, and + "closed file" for closed file handles. + + - New `debug.argerror` and `debug.argcheck` functions that provide Lua + equivalents of `luaL_argerror` and `luaL_argcheck`. + + - New `debug.argscheck` function for checking all function parameter + types with a single function call in the common case. + + - New `debug.export` function, which returns a wrapper function for + checking all arguments of an inner function against a type list. + + - New `_DEBUG.argcheck` field that disables `debug.argcheck`, and + changes `debug.argscheck` to return its function argument unwrapped, + for production code. Similarly `_DEBUG = false` deactivates these + functions in the same way. + + - New `std.operator` module, with easier to type operator names (`conj`, + `deref`, `diff`, `disj`, `eq`, `neg`, `neq`, `prod`, `quot`, and `sum`), + and a functional operator for concatenation `concat`; plus new mathematical + operators `mod`, and `pow`; and relational operators `lt`, `lte`, `gt` and + `gte`. + + - `functional.case` now accepts non-callable branch values, which are + simply returned as is, and functable values which are called and + their return value propagated back to the case caller. Function + values behave the same as in previous releases. + + - `functional.collect`, `functional.filter`, `functional.map` and + `functional.reduce` now work with standard multi-return iterators, + such as `std.pairs`. + + - `functional.collect` defaults to using `std.ipairs` as an iterator. + + - New `functional.cond`, for evaluating multiple distinct expressions + to determine what following value to be the returned. + + - `functional.filter` and `functional.map` default to using `std.pairs` + as an iterator. + + - The init argument to `functional.foldl` and `functional.foldr` is now + optional; when omitted these functions automatically start with + the left- or right-most element of the table argument resp. + + - New `functional.callable` function for unwrapping objects or + primitives that can be called as if they were a function. + + - New `functional.lambda` function for compiling lambda strings: + + ```lua + table.sort(t, lambda '|a,b| a<b') + ``` + + or, equivalently using auto-arguments: + + ```lua + table.sort(t, lambda '= _1 < _2' + ``` + + - New `functional.map_with` that returns a new table with keys matching + the argument table, and values made by mapping the supplied function + over value tables. This replaces the misplaced, and less powerful + `list.map_with`. + + - `functional.memoize` now propagates multiple return values correctly. + This allows memoizing of functions that use the `return nil, 'message'` + pattern for error message reporting. + + - New `functional.nop` function, for use where a function is required + but no work should be done. + + - New `functional.zip`, which in addition to replacing the functionality + of deprecated `list.transpose` when handling lists of lists, correctly + zips arbitrary tables of tables, and is orthogonal to `functional.map`. + It is also more than twice as fast as `list.transpose`, processing + with a single pass over the argument table as opposed to the two + passes and addition book-keeping required by `list.transpose`s + algorithm. + + - New `functional.zip_with`, subsumes functionality of deprecated + `list.zip_with`, but also handles arbitrary tables of tables correctly, + and is orthogonal to `functional.map_with`. + + - `std` module now collects stdlib functions that do not really belong + in specific type modules: including `std.assert`, `std.eval`, and + `std.tostring`. See LDocs for details. + + - New `std.ipairs` function that ignores `__ipairs` metamethod (like Lua + 5.1 and Lua 5.3), while always iterating from index 1 through n, where n + is the last non-`nil` valued integer key. Writing your loops to use + `std.ipairs` ensures your code will behave consistently across supported + versions of Lua. + + All of stdlib's implementation now uses `std.ipairs` rather than `ipairs` + internally. + + - New `std.ielems` and `std.elems` functions for iterating sequences + analagously to `std.ipairs` and `std.pairs`, but returning only the + value part of each key-value pair visited. + + - New `std.ireverse` function for reversing the proper sequence part of + any table. + + - New `std.pairs` function that respects `__pairs` metamethod, even on + Lua 5.1. + + All of stdlib's implementation now uses `std.pairs` rather than `pairs` + internally. Among other improvements, this makes for a much more + elegant imlementation of `std.object`, which also behaves intuitively + and consistently when passed to `std.pairs`. + + - `std.require` now give a verbose error message when loaded module does not + meet version numbers passed. + + - New `std.ripairs` function for returning index & value pairs in + reverse order, starting at the highest non-nil-valued contiguous integer + key. + + - New `table.len` function for returning the length of a table, much like + the core `#` operation, but respecing `__len` even on Lua 5.1. + + - New `table.insert` and `table.remove` that use `table.len` to + calculate default *pos* parameter, as well as diagnosing out of bounds + *pos* parameters consistently on any supported version of Lua. + + - `table.insert` returns the modified table. + + - New `table.maxn` is available even when Lua compiled without + compatibility, but uses the core implementation when possible. + + - New `table.okeys` function, like `table.keys` except that the list of + keys is returned with numerical keys in order followed by remaining + keys in asciibetical order. + + - `std.tostring`, `std.string.prettytostring` and the base `std.object` + `__tostring` metamethod now all use `table.okeys` to sort keys in the + generated stringification of a table. + +### Deprecations + + - Deprecated APIs are kept for a minimum of 1 year following the first + release that contains the deprecations. With each new release of + lua-stdlib, any APIs that have been deprecated for longer than that + will most likely be removed entirely. You can prevent that by + raising an issue at <https://github.com/lua-stdlib/lua-stdlib/issues> + explaining why any deprecation should be reinstated or at least kept + around for more than 1 year. + + - By default, deprecated APIs will issue a warning to stderr on every + call. However, in production code, you can turn off these warnings + entirely with any of: + + ```lua + _DEBUG = false + _DEBUG = {deprecate=false} + require 'std.debug_init'.deprecate = false + ``` + + Or, to confirm you're not trying to call a deprecated function at + runtime, you can prevent deprecated functions from being defined at + all with any of: + + ```lua + _DEBUG = true + _DEBUG = {deprecate=true} + require 'std.debug_init'.deprecate = true + ``` + + The `_DEBUG` global must be set before requiring any stdlib modules, + but you can adjust the fields in the `std.debug_init` table at any + time. + + - `functional.eval` has been moved to `std.eval`, the old name now + gives a deprecation warning. + + - `functional.fold` has been renamed to `functional.reduce`, the old + name now gives a deprecation warning. + + - `functional.op` has been moved to a new `std.operator` module, the + old function names now gives deprecation warnings. + + - `list.depair` and `list.enpair` have been moved to `table.depair` and + `table.enpair`, the old names now give deprecation warnings. + + - `list.filter` has been moved to `functional.filter`, the old name now + gives a deprecation warning. + + - `list.flatten` has been moved to `table.flatten`, the old name now + gives a deprecation warning. + + - `list.foldl` and `list.foldr` have been replaced by the richer + `functional.foldl` and `functional.foldr` respectively. The old + names now give a deprecation warning. Note that List object methods + `foldl` and `foldr` are not affected. + + - `list.index_key` and `list.index_value` have been deprecated. These + functions are not general enough to belong in lua-stdlib, because + (among others) they only work correctly with tables that can be + inverted without loss of key values. They currently give deprecation + warnings. + + - `list.map` and `list.map_with` has been deprecated, in favour of the + more powerful new `functional.map` and `functional.map_with` which + handle tables as well as lists. + + - `list.project` has been deprecated in favour of `table.project`, the + old name now gives a deprecation warning. + + - `list.relems` has been deprecated, in favour of the more idiomatic + `functional.compose(std.ireverse, std.ielems)`. + + - `list.reverse` has been deprecated in favour of the more general + and more accurately named `std.ireverse`. + + - `list.shape` has been deprecated in favour of `table.shape`, the old + name now gives a deprecation warning. + + - `list.transpose` has been deprecated in favour of `functional.zip`, + see above for details. + + - `list.zip_with` has been deprecated in favour of `functional.zip_with`, + see above for details. + + - `string.assert` has been moved to `std.assert`, the old name now + gives a deprecation warning. + + - `string.require_version` has been moved to `std.require`, the old + name now gives a deprecation warning. + + - `string.tostring` has been moved to `std.tostring`, the old name now + gives a deprecation warning. + + - `table.metamethod` has been moved to `std.getmetamethod`, the old + name now gives a deprecation warning. + + - `table.ripairs` has been moved to `std.ripairs`, the old name now + gives a deprecation warning. + + - `table.totable` has been deprecated and now gives a warning when used. + +### Incompatible changes + + - `std.monkey_patch` works the same way as the other submodule + monkey_patch functions now, by injecting its methods into the given + (or global) namespace. To get the previous effect of running all the + monkey_patch functions, either run them all manually, or call + `std.barrel()` as before. + + - `functional.bind` sets fixed positional arguments when called as + before, but when the newly bound function is called, those arguments + fill remaining unfixed positions rather than being overwritten by + original fixed arguments. For example, where this would have caused + an error previously, it now prints "100" as expected. + + ```lua + local function add(a, b) return a + b end + local incr = functional.bind(add, {1}) + print(incr(99)) + ``` + + If you have any code that calls functions returned from `bind`, you + need to remove the previously ignored arguments that correspond to + the fixed argument positions in the `bind` invocation. + + - `functional.collect`, `functional.filter` and `functional.map` still + make a list from the results from an iterator that returns single + values, but when an iterator returns multiple values they now make a + table with key:value pairs taken from the first two returned values of + each iteration. + + - The `functional.op` table has been factored out into its own new + module `std.operator`. It will also continue to be available from the + legacy `functional.op` access point for the forseeable future. + + - The `functional.op['..']` operator is no longer a list concatenation + only loaded when `std.list` is required, but a regular string + concatenation just like Lua's `..` operator. + + - `io.catdir` now raises an error when called with no arguments, for + consistency with `io.catfile`. + + - `io.die` no longer calls `io.warn` to write the error message to + stderr, but passes that error message to the core `error` function. + + - `std.set` objects used to be lax about enforcing type correctness in + function arguments, but now that we have strict type-checking on all + apis, table arguments are not coerced to Set objects but raise an + error. Due to an accident of implementation, you can get the old + inconsistent behaviour back for now by turning off type checking + before loading any stdlib modules: + + ```lua + _DEBUG = {argcheck=false} + local set = require 'std.set' + ``` + + - `string.pad` will still (by implementation accident) coerce non- + string initial arguments to a string using `string.tostring` as long + as argument checking is disabled. Under normal circumstances, + passing a non-string will now raise an error as specified in the api + documentation. + + - `table.totable` is deprecated, and thus objects no longer provide or + use a `__totable` metamethod. Instead, using a `__pairs` metamethod + to return key/value pairs, and that will automatically be used by + `__tostring`, `object.mapfields` etc. The base object now provides a + `__pairs` metamethod that returns key/value pairs in order, and + ignores private fields. If you have objects that relied on the + previous treatment of `__totable`, please convert them to set a + custom `__pairs` instead. + + +### Bug fixes + + - Removed LDocs for unused `_DEBUG.std` field. + + - `debug.trace` works with Lua 5.2.x again. + + - `list:foldr` works again instead of raising a "bad argument #1 to + 'List'" error. + + - `list.transpose` works again, and handles empty lists without + raising an error; but is deprecated and will be removed in a future + release (see above). + + - `list.zip_with` no longer raises an argument error on every call; but, + like `list.transpose`, is also deprecated (see above). + + - `optparse.on` now works with `std.strict` enabled. + + - `std.require` (nee `string.require_version`) now extracts the last + substring made entirely of digits and periods from the required + module's version string before splitting on period. That means, for + version strings like luaposix's "posix library for Lua 5.2 / 32" we + now correctly compare just the numeric part against specified version + range rather than an ASCII comparison of the whole thing as before! + + - The documentation now correcly notes that `std.require` looks + first in `module.version` and then `module._VERSION` to match the + long-standing implementation. + + - `string.split` now really does split on whitespace when no split + pattern argument is provided. Also, the documentation now + correctly cites `%s+` as the default whitespace splitting pattern + (not `%s*` which splits between every non-whitespace character). + + +## Noteworthy changes in release 40 (2014-05-01) [stable] + +### New features + + - `functional.memoize` now accepts a user normalization function, + falling back on `string.tostring` otherwise. + + - `table.merge` now supports `map` and `nometa` arguments orthogonally + to `table.clone`. + + - New `table.merge_select` function, orthogonal to + `table.clone_select`. See LDocs for details. + +### Incompatible changes + + - Core methods and metamethods are no longer monkey patched by default + when you `require 'std'` (or `std.io`, `std.math`, `std.string` or + `std.table`). Instead they provide a new `monkey_patch` method you + should use when you don't care about interactions with other + modules: + + ```lua + local io = require 'std.io'.monkey_patch() + ``` + + To install all of stdlib's monkey patches, the `std` module itself + has a `monkey_patch` method that loads all submodules with their own + `monkey_patch` method and runs them all. + + If you want full compatibility with the previous release, in addition + to the global namespace scribbling snippet above, then you need to + adjust the first line to: + + ```lua + local std = require 'std'.monkey_patch() + ``` + + - The global namespace is no longer clobbered by `require 'std'`. To + get the old behaviour back: + + ```lua + local std = require 'std'.barrel(_G) + ``` + + This will execute all available monkey_patch functions, and then + scribble all over the `_G` namespace, just like the old days. + + - The `metamethod` call is no longer in `std.functional`, but has moved + to `std.table` where it properly belongs. It is a utility method for + tables and has nothing to do with functional programming. + + - The following deprecated camelCase names have been removed, you + should update your code to use the snake_case equivalents: + `std.io.processFiles`, `std.list.indexKey`, `std.list.indexValue`, + `std.list.mapWith`, `std.list.zipWith`, `std.string.escapePattern`, + `std.string. escapeShell`, `std.string.ordinalSuffix`. + + - The following deprecated function names have been removed: + `std.list.new` (call `std.list` directly instead), + `std.list.slice` (use `std.list.sub` instead), + `std.set.new` (call `std.set` directly instead), + `std.strbuf.new` (call `std.strbuf` directly instead), and + `std.tree.new` (call `std.tree` directly instead). + +### Bug fixes + + - Allow `std.object` derived tables as `std.tree` keys again. + + +## Noteworthy changes in release 39 (2014-04-23) [stable] + +### New features + + - New `std.functional.case` function for rudimentary case statements. + The main difference from serial if/elseif/end comparisons is that + `with` is evaluated only once, and then the match function is looked + up with an O(1) table reference and function call, as opposed to + hoisting an expression result into a temporary variable, and O(n) + comparisons. + + The function call overhead is much more significant than several + comparisons, and so `case` is slower for all but the largest series + of if/elseif/end comparisons. It can make your code more readable, + however. + + See LDocs for usage. + + - New pathstring management functions in `std.package`. + + Manage `package.path` with normalization, duplicate removal, + insertion & removal of elements and automatic folding of '/' and '?' + onto `package.dirsep` and `package.path_mark`, for easy addition of + new paths. For example, instead of all this: + + ```lua + lib = std.io.catfile('.', 'lib', package.path_mark .. '.lua') + paths = std.string.split(package.path, package.pathsep) + for i, path in ipairs(paths) do + -- ... lots of normalization code... + end + i = 1 + while i <= #paths do + if paths[i] == lib then + table.remove(paths, i) + else + i = i + 1 + end + end + table.insert(paths, 1, lib) + package.path = table.concat(paths, package.pathsep) + ``` + + You can now write just: + + ```lua + package.path = package.normalize('./lib/?.lua', package.path) + ``` + + - `std.optparse:parse` accepts a second optional parameter, a table of + default option values. + + - `table.clone` accepts an optional table of key field renames in the + form of `{oldkey=newkey, ...}` subsuming the functionality of + `table.clone_rename`. The final `nometa` parameter is supported + whether or not a rename map is given: + + ```lua + r = table.clone(t, 'nometa') + r = table.clone(t, {oldkey=newkey}, 'nometa') + ``` + +### Deprecations + + - `table.clone_rename` now gives a warning on first call, and will be + removed entirely in a few releases. The functionality has been + subsumed by the improvements to `table.clone` described above. + +### Bug fixes + + - `std.optparse` no longer throws an error when it encounters an + unhandled option in a combined (i.e. `-xyz`) short option string. + + - Surplus unmapped fields are now discarded during object cloning, for + example when a prototype has `_init` set to `{'first', 'second'}`, + and is cloned using `Proto {'one', 'two', 'three'}`, then the + unmapped `three` argument is now discarded. + + - The path element returned by `std.tree.nodes` can now always be + used as a key list to dereference the root of the tree, particularly + `tree[{}]` now returns the root node of `tree`, to match the initial + `branch` and final `join` results from a full traversal by + `std.tree.nodes(tree)`. + +### Incompatible changes + + - `std.string` no longer sets `__append`, `__concat` and `__index` in + the core strings metatable by default, though `require 'std'` does + continue to do so. See LDocs for `std.string` for details. + + - `std.optparse` no longer normalizes unhandled options. For example, + `--unhandled-option=argument` is returned unmolested from `parse`, + rather than as two elements split on the `=`; and if a combined + short option string contains an unhandled option, then whatever was + typed at the command line is returned unmolested, rather than first + stripping off and processing handled options, and returning only the + unhandled substring. + + - Setting `_init` to `{}` in a prototype object will now discard all + positional parameters passed during cloning, because a table valued + `_init` is a list of field names, beyond which surplus arguments (in + this case, all arguments!) are discarded. + + +## Noteworthy changes in release 38 (2014-01-30) [stable] + +### New features + + - The separator parameter to `std.string.split` is now optional. It + now splits strings with `%s+` when no separator is specified. The + new implementation is faster too. + + - New `std.object.mapfields` method factors out the table field copying + and mapping performed when cloning a table `_init` style object. This + means you can call it from a function `_init` style object after + collecting a table to serve as `src` to support derived objects with + normal std.object syntax: + + ```lua + Proto = Object { + _type = 'proto' + _init = function(self, arg, ...) + if type(arg) == 'table' then + mapfields(self, arg) + else + -- non-table instantiation code + end + end, + } + new = Proto(str, #str) + Derived = proto {_type='Derived', ...} + ``` + + - Much faster object cloning; `mapfields` is in imperative style and + makes one pass over each table it looks at, where previous releases + used functional style (stack frame overhead) and multiple passes over + input tables. + + On my 2013 Macbook Air with 1.3GHz Core i5 CPU, I can now create a + million std.objects with several assorted fields in 3.2s. Prior to + this release, the same process took 8.15s... and even release 34.1, + with drastically simpler Objects (19SLOC vs over 120) took 5.45s. + + - `std.object.prototype` is now almost an order of magnitude faster + than previous releases, taking about 20% of the time it previously + used to return its results. + + - `io.warn` and `io.die` now integrate properly with `std.optparse`, + provided you save the `opts` return from `parser:parse` back to the + global namespace where they can access it: + + ```lua + local OptionParser = require 'std.optparse' + local parser = OptionParser 'eg 0\nUsage: eg\n' + _G.arg, _G.opts = parser:parse(_G.arg) + if not _G.opts.keep_going then + require 'std.io'.warn 'oh noes!' + end + ``` + + will, when run, output to stderr: "eg: oh noes!" + +### Bug fixes + + - Much improved documentation for `optparse`, so you should be able + to use it without reading the source code now! + + - `io.warn` and `io.die` no longer output a line-number when there is + no file name to append it to. + + - `io.warn` and `io.die` no longer crash in the absence of a global + `prog` table. + + - `string.split` no longer goes into an infinite loop when given an + empty separator string. + + - Fix `getmetatable(container._functions) == getmetatable(container)`, + which made tostring on containers misbehave, among other latent bugs. + + - `_functions` is never copied into a metatable now, finally solving + the conflicted concerns of needing metatables to be shared between + all objects of the same `_type` (for `__lt` to work correctly for one + thing) and not leaving a dangling `_functions` list in the metatable + of cloned objects, which could delete functions with matching names + from subsequent clones. + + +## Noteworthy changes in release 37 (2014-01-19) [stable] + +### New features + + - Lazy loading of submodules into `std` on first reference. On initial + load, `std` has the usual single `version` entry, but the `__index` + metatable will automatically require submodules on first reference: + + ```lua + local std = require 'std' + local prototype = std.container.prototype + ``` + + - New `std.optparse` module: A civilised option parser. + (L)Documentation distributed in doc/classes/std.optparse.html. + +### Bug fixes + + - Modules no longer leak `new' and `proper_subset' into the global + table. + + - Cloned `Object` and `Container` derived types are more aggressive + about sharing metatables, where previously the metatable was copied + unnecessarily the base object used `_functions` for module functions + + - The retracted release 36 changed the operand order of many `std.list` + module functions unnecessarily. Now that `_function` support is + available, there's no need to be so draconian, so the original v35 + and earlier operand order works as before again. + + - `std.list.new`, `std.set.new`, `set.strbuf.new` and `std.tree.new` + are available again for backwards compatibility. + + - LuaRocks install doesn't copy config.ld and config.ld to $docdir. + +### Incompatible changes + + - `std.getopt` is no more. It appears to have no users, though if there + is a great outcry, it should be easy to make a compatibility api over + `std.optparse` in the next release. + + +## Noteworthy changes in release 36 (2014-01-16) [stable] + +### New features + + - Modules have been refactored so that they can be safely + required individually, and without loading themselves or any + dependencies on other std modules into the global namespace. + + - Objects derived from the `std.object` prototype have a new + <derived_object>:prototype() method that returns the contents of the + new internal `_type` field. This can be overridden during cloning + with, e.g.: + + ```lua + local Object = require 'std.object' + Prototype = Object {_type='Prototype', <other_fields>} + ``` + + - Objects derived from the `std.object` prototype return a new table + with a shallow copy of all non-private fields (keys that do not + begin with '_') when passed to `table.totable` - unless overridden + in the derived object's __totable field. + + - list and strbuf are now derived from `std.object`, which means that + they respond to `object.prototype` with appropriate type names ('List', + 'StrBuf', etc.) and can be used as prototypes for further derived + objects or clones; support object:prototype(); respond to totable etc. + + - A new Container module at `std.container` makes separation between + container objects (which are free to use __index as a '[]' access + metamethod, but) which have no object methods, and regular objects + (which do have object methods, but) which cannot use the __index + metamethod for '[]' access to object contents. + + - set and tree are now derived from `std.container`, so there are no + object methods. Instead there are a full complement of equivalent + module functions. Metamethods continue to work as before. + + - `string.prettytostring` always displays table elements in the same + order, as provided by `table.sort`. + + - `table.totable` now accepts a string, and returns a list of the + characters that comprise the string. + + - Can now be installed directly from a release tarball by `luarocks`. + No need to run `./configure` or `make`, unless you want to install to + a custom location, or do not use LuaRocks. + +### Bug fixes + + - string.escape_pattern is now Lua 5.2 compatible. + + - all objects now reuse prototype metatables, as required for __le and + __lt metamethods to work as documented. + +### Deprecations + + - To avoid confusion between the builtin Lua `type` function and the + method for finding the object prototype names, `std.object.type` is + deprecated in favour of `std.object.prototype`. `std.object.type` + continues to work for now, but might be removed from a future + release. + + ```lua + local prototype = require 'std.object'.prototype + ``` + + ...makes for more readable code, rather than confusion between the + different flavours of `type`. + +### Incompatible changes + + - Following on from the Grand Renaming™ change in the last release, + `std.debug_ext`, `std.io_ext`, `std.math_ext`, `std.package_ext`, + `std.string_ext` and `std.table_ext` no longer have the spurious + `_ext` suffix. Instead, you must now use, e.g.: + + ```lua + local string = require 'std.string' + ``` + + These names are now stable, and will be available from here for + future releases. + + - The `std.list` module, as a consequence of returning a List object + prototype rather than a table of functions including a constructor, + now always has the list operand as the first argument, whether that + function is called with `.` syntax or `:` syntax. Functions which + previously had the list operand in a different position when called + with `.` syntax were: list.filter, list.foldl, list.foldr, + list.index_key, list.index_value, list.map, list.map_with, + list.project, list.shape and list.zip_with. Calls made as object + methods using `:` calling syntax are unchanged. + + - The `std.set` module is a `std.container` with no object methods, + and now uses prototype functions instead: + + ```lua + local union = Set.union(set1, set2) + ``` + + +## Noteworthy changes in release 35 (2013-05-06) [stable] + +### New features + + - Move to the Slingshot release system. + - Continuous integration from Travis automatically builds stdilb + with Lua 5.1, Lua 5.2 and luajit-2.0 with every commit, which + should help prevent future release breaking compatibility with + one or another of those interpreters. + +### Bug fixes + + - `std.package_ext` no longer overwrites the core `package` table, + leaving the core holding on to memory that Lua code could no + longer access. + +### Incompatible changes + + - The Grand Renaming™ - everything now installs to $luaprefix/std/, + except `std.lua` itself. Importing individual modules now involves: + + ```lua + local list = require 'std.list' + ``` + + If you want to have all the symbols previously available from the + global and core module namespaces, you will need to put them there + yourself, or import everything with: + + ```lua + require 'std' + ``` + + which still behaves per previous releases. + + Not all of the modules work correctly when imported individually + right now, until we figure out how to break some circular dependencies. + + +## Noteworthy changes in release 34.1 (2013-04-01) [stable] + + - This is a maintenance release to quickly fix a breakage in getopt + from release v34. Getopt no longer parses non-options, but stops + on the first non-option... if a use case for the other method + comes up, we can always add it back in. + + +## Noteworthy changes in release 34 (2013-03-25) [stable] + + - stdlib is moving towards supporting separate requirement of individual + modules, without scribbling on the global environment; the work is not + yet complete, but we're collecting tests along the way to ensure that + once it is all working, it will carry on working; + + - there are some requirement loops between modules, so not everything can + be required independently just now; + + - `require 'std'` will continue to inject std symbols into the system + tables for backwards compatibility; + + - stdlib no longer ships a copy of Specl, which you will need to install + separately if you want to run the bundled tests; + + - getopt supports parsing of undefined options; useful for programs that + wrap other programs; + + - getopt.Option constructor is no longer used, pass a plain Lua table of + options, and getopt will do the rest; + + +## Noteworthy changes in release 33 (2013-07-27) [stable] + + - This release improves stability where Specl has helped locate some + corner cases that are now fixed. + + - `string_ext.wrap` and `string_ext.tfind` now diagnose invalid arguments. + + - Specl code coverage is improving. + + - OrdinalSuffix improvements. + + - Use '%' instead of math.mod, as the latter does not exist in Lua 5.2. + + - Accept negative arguments. + + +## Noteworthy changes in release 32 (2013-02-22) [stable] + + - This release fixes a critical bug preventing getopt from returning + anything in getopt.opt. Gary V. Vaughan is now a co-maintainer, currently + reworking the sources to use (Lua 5.1 compatible) Lua 5.2 style module + packaging, which requires you to assign the return values from your imports: + + ```lua + getopt = require 'getopt' + ``` + + - Extension modules, table_ext, package_ext etc. return the unextended module + table before injecting additional package methods, so you can ignore those + return values or save them for programatically backing out the changes: + + ```lua + table_unextended = require 'table_ext' + ``` + + - Additionally, Specl (see http://github.com/gvvaughan/specl/) specifications + are being written for stdlib modules to help us stop accidentally breaking + things between releases. + + +## Noteworthy changes in release 31 (2013-02-20) [stable] + + - This release improves the list module: lists now have methods, list.slice + is renamed to list.sub (the old name is provided as an alias for backwards + compatibility), and all functions that construct a new list return a proper + list, not a table. As a result, it is now often possible to write code that + works on both lists and strings. + + +## Noteworthy changes in release 30 (2013-02-17) [stable] + + - This release changes some modules to be written in a Lua 5.2 style (but + not the way they work with 5.1). Some fixes and improvements were made to + the build system. Bugs in the die function, the parser module, and a nasty + bug in the set module introduced in the last release (29) were fixed. + + +## Noteworthy changes in release 29 (2013-02-06) [stable] + + - This release overhauls the build system to have LuaRocks install releases + directly from git rather than from tarballs, and fixes a bug in set (issue + #8). + + +## Noteworthy changes in release 28 (2012-10-28) [stable] + + - This release improves the documentation and build system, and improves + require_version to work by default with more libraries. + + +## Noteworthy changes in release 27 (2012-10-03) [stable] + + - This release changes getopt to return all arguments in a list, rather than + optionally processing them with a function, fixes an incorrect definition + of set.elems introduced in release 26, turns on debugging by default, + removes the not-very-useful string.gsubs, adds constructor functions for + objects, renames table.rearrange to the more descriptive table.clone_rename + and table.indices to table.keys, and makes table.merge not clone but modify + its left-hand argument. A function require_version has been added to allow + version constraints on a module being required. Gary Vaughan has + contributed a memoize function, and minor documentation and build system + improvements have been made. Usage information is now output to stdout, not + stderr. The build system has been fixed to accept Lua 5.2. The luarock now + installs documentation, and the build command used is now more robust + against previous builds in the same tree. + + +## Noteworthy changes in release 26 (2012-02-18) [stable] + + - This release improves getopt's output messages and conformance to + standard practice for default options. io.processFiles now unsets prog.file + when it finishes, so that a program can tell when itâs no longer + processing a file. Three new tree iterators, inodes, leaves and ileaves, + have been added; the set iterator set.elements (renamed to set.elems for + consistency with list.elems) is now leaves rather than pairs. tree indexing + has been made to work in more circumstances (thanks, Gary Vaughan). + io.writeline is renamed io.writelines for consistency with io.readlines and + its function. A slurping function, io.slurp, has been added. Strings now + have a __concat metamethod. + + +## Noteworthy changes in release 25 (2011-09-19) [stable] + + - This release adds a version string to the std module and fixes a buglet in + the build system. + + +## Noteworthy changes in release 24 (2011-09-19) [stable] + + - This release fixes a rename missing from release 23, and makes a couple of + fixes to the new build system, also from release 23. + + +## Noteworthy changes in release 23 (2011-09-17) [stable] + + - This release removes the posix_ext module, which is now part of luaposix, + renames string.findl to string.tfind to be the same as lrexlib, and + autotoolizes the build system, as well as providing a rockspec file. + + +## Noteworthy changes in release 22 (2011-09-02) [stable] + + - This release adds two new modules: strbuf, a trivial string buffers + implementation, which is used to speed up the stdlib tostring method for + tables, and bin, which contains a couple of routines for converting binary + data into numbers and strings. Some small documentation and build system + fixes have been made. + + +## Noteworthy changes in release 21 (2011-06-06) [stable] + + - This release converts the documentation of stdlib to LuaDoc, adds an + experimental Lua 5.2 module "fstable", for storing tables directly on + disk as files and directories, and fixes a few minor bugs (with help from + David Favro). + + - This release has been tested lightly on Lua 5.2 alpha, but is not + guaranteed to work fully. + + +## Noteworthy changes in release 20 (2011-04-14) [stable] + + - This release fixes a conflict between the global _DEBUG setting and the use + of strict.lua, changes the argument order of some list functions to favour + OO-style use, adds posix.euidaccess, and adds OO-style use to set. mk1file + can now produce a single-file version of a user-supplied list of modules, + not just the standard set. + + +## Noteworthy changes in release 19 (2011-02-26) [stable] + + - This release puts the package.config reflection in a new package_ext + module, where it belongs. Thanks to David Manura for this point, and for a + small improvement to the code. + + +## Noteworthy changes in release 18 (2011-02-26) [stable] + + - This release provides named access to the contents of package.config, which + is undocumented in Lua 5.1. See luaconf.h and the Lua 5.2 manual for more + details. + + +## Noteworthy changes in release 17 (2011-02-07) [stable] + + - This release fixes two bugs in string.pad (thanks to Bob Chapman for the + fixes). + + +## Noteworthy changes in release 16 (2010-12-09) [stable] + + - Adds posix module, using luaposix, and makes various other small fixes and + improvements. + + +## Noteworthy changes in release 15 (2010-06-14) [stable] + + - This release fixes list.foldl, list.foldr, the fold iterator combinator and + io.writeLine. It also simplifies the op table, which now merely sugars the + built-in operators rather than extending them. It adds a new tree module, + which subsumes the old table.deepclone and table.lookup functions. + table.subscript has become op['[]'], and table.subscripts has been removed; + the old treeIter iterator has been simplified and generalised, and renamed + to nodes. The mk1file script and std.lua library loader have had the module + list factored out into modules.lua. strict.lua from the Lua distribution is + now included in stdlib, which has been fixed to work with it. Some minor + documentation and other code improvements and fixes have been made. + + +## Noteworthy changes in release 14 (2010-06-07) [stable] + + - This release makes stdlib compatible with strict.lua, which required a + small change to the debug_ext module. Some other minor changes have also + been made to that module. The table.subscripts function has been removed + from the table_ext.lua. + + +## Noteworthy changes in release 13 (2010-06-02) [stable] + + - This release removes the lcs module from the standard set loaded by + 'std', removes an unnecessary definition of print, and tidies up the + implementation of the "op" table of functional versions of the infix + operators and logical operators. + + +## Noteworthy changes in release 12 (2009-09-07) [stable] + + - This release removes io.basename and io.dirname, which are now available in + lposix, and the little-used functions addSuffix and changeSuffix which + dependend on them. io.pathConcat is renamed to io.catdir and io.pathSplit + to io.splitdir, making them behave the same as the corresponding Perl + functions. The dependency on lrexlib has been removed along with the rex + wrapper module. Some of the more esoteric and special-purpose modules + (mbox, xml, parser) are no longer loaded by 'require 'std''. + + This leaves stdlib with no external dependencies, and a rather more + coherent set of basic modules. + + +## Noteworthy changes in release 11 (2009-03-15) [stable] + + - This release fixes a bug in string.format, removes the redundant + string.join (it's the same as table.concat), and adds to table.clone and + table.deepclone the ability to copy without metatables. Thanks to David + Kantowitz for pointing out the various deficiencies. + + +## Noteworthy changes in release 10 (2009-03-13) [stable] + + - This release fixes table.deepclone to copy metatables, as it should. + Thanks to David Kantowitz for the fix. + + +## Noteworthy changes in release 9 (2009-02-19) [stable] + + - This release updates the object module to be the same as that published + in "Lua Gems", and fixes a bug in the utility mk1file which makes a + one-file version of the library, to stop it permanently redefining require. + + +## Noteworthy changes in release 8 (2008-09-04) [stable] + + - This release features fixes and improvements to the set module; thanks to + Jiutian Yanling for a bug report and suggestion which led to this work. + + +## Noteworthy changes in release 7 (2008-09-04) [stable] + + - just a bug fix + + +## Noteworthy changes in release 6 (2008-07-28) [stable] + + - This release rewrites the iterators in a more Lua-ish 5.1 style. + + +## Noteworthy changes in release 5 (2008-03-04) [stable] + + - I'm happy to announce a new release of my standard Lua libraries. It's been + nearly a year since the last release, and I'm happy to say that since then + only one bug has been found (thanks Roberto!). Two functions have been + added in this release, to deal with file paths, and one removed (io.length, + which is handled by lfs.attributes) along with one constant (INTEGER_BITS, + handled by bitlib's bit.bits). + + - For those not familiar with stdlib, it's a pure-Lua library of mostly + fundamental data structures and algorithms, in particular support for + functional and object-oriented programming, string and regex operations and + extensible pretty printing of data structures. More specific modules + include a getopt implementation, a generalised least common subsequences + (i.e. diff algorithm) implementation, a recursive-descent parser generator, + and an mbox parser. + + - It's quite a mixed bag, but almost all written for real projects. It's + written in a doc-string-ish style with the supplied very simple ldoc tool. + + - I am happy with this code base, but there are various things it could use: + + 0. Tests. Tests. Tests. The code has no unit tests. It so needs them. + + 1. More code. Nothing too specialised (unless it's too small to be released + on its own, although very little seems "too small" in the Lua + community). Anything that either has widespread applicability (like + getopt) or is very general (data structures, algorithms, design + patterns) is good. + + 2. Refactoring. The code is not ideally factored. At the moment it is + divided into modules that extend existing libraries, and new modules + constructed along similar lines, but I think that some of the divisions + are confusing. For example, the functional programming support is spread + between the list and base modules, and would probably be better in its + own module, as those who aren't interested in the functional style won't + want the functional list support or the higher-order functions support, + and those who want one will probably want the other. + + 3. Documentation work. There's not a long wrong with the existing + documentation, but it would be nice, now that there is a stable LuaDoc, + to use that instead of the built-in ldoc, which I'm happy to discard now + that LuaDoc is stable. ldoc was always designed as a minimal LuaDoc + substitute in any case. + + 4. Maintenance and advocacy. For a while I have been reducing my work on + Lua, and am also now reducing my work in Lua. If anyone would like to + take on stdlib, please talk to me. It fills a much-needed function: I + suspect a lot of Lua programmers have invented the wheels with which it + is filled over and over again. In particular, many programmers could + benefit from the simplicity of its simple and well-designed functional, + string and regex capabilities, and others will love its comprehensive + getopt. + + +## Noteworthy changes in release 4 (2007-04-26) [beta] + + - This release removes the dependency on the currently unmaintained lposix + library, includes pre-built HTML documentation, and fixes some 5.0-style + uses of variadic arguments. + + Thanks to Matt for pointing out all these problems. stdlib is very much + user-driven at the moment, since it already does everything I need, and I + don't have much time to work on it, so do please contact me if you find + bugs or problems or simply don't understand it, as the one thing I *do* + want to do is make it useful and accessible! + + +## Noteworthy changes in release 3 (2007-02-25) [beta] + + - This release fixes the "set" and "lcs" (longest common subsequence, or + "grep") libraries, which were broken, and adds one or two other bug and + design fixes. Thanks are due to Enrico Tassi for pointing out some of the + problems. + + +## Noteworthy changes in release 2 (2007-01-05) [beta] + + - This release includes some bug fixes, and compatibility with lrexlib 2.0. + + +## Noteworthy changes in release 1 (2011-09-02) [beta] + + - It's just a snapshot of CVS, but it's pretty stable at the moment; stdlib, + until such time as greater interest or participation enables (or forces!) + formal releases will be in permanent beta, and tracking CVS is recommended. + + +[optparse]: https://github.com/gvvaughan/optparse +[strict]: https://github.com/lua-stdlib/strict diff --git a/Data/BuiltIn/Libraries/lua-stdlib/README.md b/Data/BuiltIn/Libraries/lua-stdlib/README.md new file mode 100644 index 0000000..49b0328 --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-stdlib/README.md @@ -0,0 +1,92 @@ +Standard Lua libraries +====================== + +Copyright (C) 2000-2018 [stdlib authors][github] + +[](http://mit-license.org) +[](http://travis-ci.org/lua-stdlib/lua-stdlib/builds) +[](https://codecov.io/gh/lua-stdlib/lua-stdlib) +[](https://waffle.io/lua-stdlib/lua-stdlib) + + +This is a collection of Lua libraries for Lua 5.1 (including LuaJIT), 5.2 +and 5.3. The libraries are copyright by their authors (see the [AUTHORS][] +file for details), and released under the [MIT license][mit] (the same +license as Lua itself). There is no warranty. + +_stdlib_ has no run-time prerequisites beyond a standard Lua system, +though it will take advantage of [strict][] and [typecheck][] if they +are installed. + +[authors]: http://github.com/lua-stdlib/lua-stdlib/blob/master/AUTHORS.md +[github]: http://github.com/lua-stdlib/lua-stdlib/ "Github repository" +[lua]: http://www.lua.org "The Lua Project" +[mit]: http://mit-license.org "MIT License" +[strict]: https://github.com/lua-stdlib/strict "strict variables" +[typecheck]: https://github.com/gvvaughan/typecheck "function type checks" + + +Installation +------------ + +The simplest and best way to install stdlib is with [LuaRocks][]. To +install the latest release (recommended): + +```bash + luarocks install stdlib +``` + +To install current git master (for testing, before submitting a bug +report for example): + +```bash + luarocks install http://raw.githubusercontent.com/lua-stdlib/lua-stdlib/master/stdlib-git-1.rockspec +``` + +The best way to install without [LuaRocks][] is to copy the `std` +folder and its contents into a directory on your package search path. + +[luarocks]: http://www.luarocks.org "Lua package manager" + + +Documentation +------------- + +The latest release of these libraries is [documented in LDoc][github.io]. +Pre-built HTML files are included in the release. + +[github.io]: http://lua-stdlib.github.io/lua-stdlib + + +Bug reports and code contributions +---------------------------------- + +These libraries are written and maintained by their users. + +Please make bug reports and suggestions as [GitHub Issues][issues]. +Pull requests are especially appreciated. + +But first, please check that your issue has not already been reported by +someone else, and that it is not already fixed by [master][github] in +preparation for the next release (see Installation section above for how +to temporarily install master with [LuaRocks][]). + +There is no strict coding style, but please bear in mind the following +points when proposing changes: + +0. Follow existing code. There are a lot of useful patterns and avoided + traps there. + +1. 3-character indentation using SPACES in Lua sources: It makes rogue + TABS easier to see, and lines up nicely with 'if' and 'end' keywords. + +2. Simple strings are easiest to type using single-quote delimiters, + saving double-quotes for where a string contains apostrophes. + +3. Save horizontal space by only using SPACES where the parser requires + them. + +4. Use vertical space to separate out compound statements to help the + coverage reports discover untested lines. + +[issues]: http://github.com/lua-stdlib/lua-stdlib/issues diff --git a/Data/BuiltIn/Libraries/lua-stdlib/STYLE.md b/Data/BuiltIn/Libraries/lua-stdlib/STYLE.md new file mode 100644 index 0000000..f01f630 --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-stdlib/STYLE.md @@ -0,0 +1,97 @@ +## Lua + + - Requiring any stdlib module must not leak any symbols into the global + namespace. + + - Any stdlib module may `require "std.base"`, and use any functions from + there, as well as functions from `std.debug` (and `debug_init`); but, + all other modules export argument checked functions that should not be + called from anywhere in stdlib -- this is the client API. If a + function is needed by more than one module, move it to `std.base` + without argument checking, and re-export with `argscheck` if necess- + ary. + + Obviously, for objects it's perfectly fine to require the file that + defines the object being derived from. But to prevent accidentally + calling argchecked methods, we always immediately create a prototype + object with, e.g: + + local Container = require "std.container" {} + + (`std.object` is an exception to this rule because of how tightly + bound to `std.container` it is, and does directly call some of + containers methods by design). + + - Minimise forward declarations of functions, because having some + declared as `local` in line, and others not is ugly and can easily + cause rogue `local` keywords to be introduced that end up shadowing + the intended declaration. Mutually recursive functions, and + alternate definitions are acceptable, in which case keep the forward + declarations and definitions as close together as possible to + minimise any possible misunderstandings later. + + - Try to maintain asciibetical ordering of function definitions in each + source file, except where doing so would require forward declar- + ations. In that case use topological ordering to avoid the forward + declarations. + + - Unless a table cannot possibly have a __len metamethod (i.e. it was + constructed without one in the current scope), always use + `base.insert` and `base.len` rather than core `table.insert` and the + `#` operator, which do not honor __len in all implementations. + + - Unless a table cannot possibly have __pairs or __len metamethods + (i.e. it was constructed without them in the current scope), always + use `base.pairs` or `base.ipairs` rather than core `pairs` and + `ipairs`, which do not honor __pairs or __len in all implementations. + + - Use consistent short names for common parameters: + + fh a file handle, usually from io.open or similar + fmt a format string + fn a function + i an index + k a value, usually from pairs or similar + l a list-like table + n a number + s a string + t a table + + - Do argument check all object methods (functions available from an + object created by a module function -- usually listed in the + `__index` subtable of the object metatable), to catch pathological + calls early, preferably using a `typecheck.argscheck` wrapper around + the internal implementation: this way, implementation functions can + call each other without excessive rechecking of argument types. + + - Do argument check all module functions (functions available in the + table returned from requiring that module). + + - Do argument check metamethods, to catch pathological calls early. + + +## LDocs + + - LDocs should be next to each function's argcheck wrapper (if it has + one) in the export table, so that it's easy to check the consistency + between the types declared in the LDocs and the argument types + enforced by `typecheck.argscheck` or equivalent. + + - `backtick_references` is disabled for stdlib, if you want an inline + cross-reference, use `@{reference}`. + + - Be liberal with `@see` references to similar apis. + + - Refer to other argument names with italics (`*italic*` in markdown). + + - Try to add entries for callback function signatures, and name them + with the suffix `cb`. + + - Rely on the reader to understand how `:` call syntax works in Lua, and + don't waste effort documenting methods that are already documented as + functions. + + - Do document the prototype chain. Don't document methods inherited + from the prototype, even they have been overridden to behave consist- + ently from a UI perspective even though the implementation needs to be + different to provide that same UI. diff --git a/Data/BuiltIn/Libraries/lua-stdlib/_base.lua b/Data/BuiltIn/Libraries/lua-stdlib/_base.lua new file mode 100644 index 0000000..0c623d4 --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-stdlib/_base.lua @@ -0,0 +1,204 @@ +--[[ + General Lua Libraries for Lua 5.1, 5.2 & 5.3 + Copyright (C) 2002-2018 stdlib authors +]] +--[[-- + Prevent dependency loops with key function implementations. + + A few key functions are used in several stdlib modules; we implement those + functions in this internal module to prevent dependency loops in the first + instance, and to minimise coupling between modules where the use of one of + these functions might otherwise load a whole selection of other supporting + modules unnecessarily. + + Although the implementations are here for logistical reasons, we re-export + them from their respective logical modules so that the api is not affected + as far as client code is concerned. The functions in this file do not make + use of `argcheck` or similar, because we know that they are only called by + other stdlib functions which have already performed the necessary checking + and neither do we want to slow everything down by recheckng those argument + types here. + + This implies that when re-exporting from another module when argument type + checking is in force, we must export a wrapper function that can check the + user's arguments fully at the API boundary. +]] + + +local _ENV = require 'std.normalize' { + concat = 'table.concat', + dirsep = 'package.dirsep', + find = 'string.find', + gsub = 'string.gsub', + insert = 'table.insert', + min = 'math.min', + shallow_copy = 'table.merge', + sort = 'table.sort', + sub = 'string.sub', + table_maxn = table.maxn, + wrap = 'coroutine.wrap', + yield = 'coroutine.yield', +} + + + +--[[ ============================ ]]-- +--[[ Enhanced Core Lua functions. ]]-- +--[[ ============================ ]]-- + + +-- These come as early as possible, because we want the rest of the code +-- in this file to use these versions over the core Lua implementation +-- (which have slightly varying semantics between releases). + + +local maxn = table_maxn or function(t) + local n = 0 + for k in pairs(t) do + if type(k) == 'number' and k > n then + n = k + end + end + return n +end + + + +--[[ ============================ ]]-- +--[[ Shared Stdlib API functions. ]]-- +--[[ ============================ ]]-- + + +-- No need to recurse because functables are second class citizens in +-- Lua: +-- func = function() print 'called' end +-- func() --> 'called' +-- functable=setmetatable({}, {__call=func}) +-- functable() --> 'called' +-- nested=setmetatable({}, {__call=functable}) +-- nested() +-- --> stdin:1: attempt to call a table value(global 'd') +-- --> stack traceback: +-- --> stdin:1: in main chunk +-- --> [C]: in ? +local function callable(x) + if type(x) == 'function' then + return x + end + return (getmetatable(x) or {}).__call +end + + +local function catfile(...) + return concat({...}, dirsep) +end + + +local function compare(l, m) + local lenl, lenm = len(l), len(m) + for i = 1, min(lenl, lenm) do + local li, mi = tonumber(l[i]), tonumber(m[i]) + if li == nil or mi == nil then + li, mi = l[i], m[i] + end + if li < mi then + return -1 + elseif li > mi then + return 1 + end + end + if lenl < lenm then + return -1 + elseif lenl > lenm then + return 1 + end + return 0 +end + + +local function escape_pattern(s) + return (gsub(s, '[%^%$%(%)%%%.%[%]%*%+%-%?]', '%%%0')) +end + + +local function invert(t) + local i = {} + for k, v in pairs(t) do + i[v] = k + end + return i +end + + +local function leaves(it, tr) + local function visit(n) + if type(n) == 'table' then + for _, v in it(n) do + visit(v) + end + else + yield(n) + end + end + return wrap(visit), tr +end + + +local function split(s, sep) + local r, patt = {} + if sep == '' then + patt = '(.)' + insert(r, '') + else + patt = '(.-)' ..(sep or '%s+') + end + local b, slen = 0, len(s) + while b <= slen do + local e, n, m = find(s, patt, b + 1) + insert(r, m or sub(s, b + 1, slen)) + b = n or slen + 1 + end + return r +end + + +--[[ ============= ]]-- +--[[ Internal API. ]]-- +--[[ ============= ]]-- + + +-- For efficient use within stdlib, these functions have no type-checking. +-- In debug mode, type-checking wrappers are re-exported from the public- +-- facing modules as necessary. +-- +-- Also, to provide some sanity, we mirror the subtable layout of stdlib +-- public API here too, which means everything looks relatively normal +-- when importing the functions into stdlib implementation modules. +return { + io = { + catfile = catfile, + }, + + list = { + compare = compare, + }, + + object = { + Module = Module, + mapfields = mapfields, + }, + + string = { + escape_pattern = escape_pattern, + split = split, + }, + + table = { + invert = invert, + maxn = maxn, + }, + + tree = { + leaves = leaves, + }, +} diff --git a/Data/BuiltIn/Libraries/lua-stdlib/build-aux/config.ld.in b/Data/BuiltIn/Libraries/lua-stdlib/build-aux/config.ld.in new file mode 100644 index 0000000..20ee48b --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-stdlib/build-aux/config.ld.in @@ -0,0 +1,53 @@ +--[[ + General Lua Libraries for Lua 5.1, 5.2 & 5.3 + Copyright (C) 2002-2018 stdlib authors +]] + +title = 'stdlib @PACKAGE_VERSION@ Reference' +project = 'stdlib @PACKAGE_VERSION@' +description = [[ +# Standard Lua Libraries + +This is a collection of light-weight libraries for Lua 5.1 (including +LuaJIT), 5.2 and 5.3 written in pure Lua, comprising: + +1. Enhanced and expanded versions of some core Lua functions in the + @{std} module itself; + +2. Enhanced versions of some core Lua libraries: @{std.debug}, @{std.io}, + @{std.math}, @{std.package}, @{std.string} and @{std.table}; + +## LICENSE + +The code is copyright by its respective authors, and released under the +MIT license (the same license as Lua itself). There is no warranty. +]] + +dir = '../doc' + +file = { + '../lib/std/init.lua', + '../lib/std/debug.lua', + '../lib/std/io.lua', + '../lib/std/math.lua', + '../lib/std/package.lua', + '../lib/std/string.lua', + '../lib/std/table.lua', +} + +new_type ('corefunction', 'Core_Functions', true) +new_type ('corelibrary', 'Core_Libraries', true) + +function postprocess_html(s) + s = s:gsub('<h1>%s*Corefunction (.-)</p>', '<h1>Module %1</h1>') + s = s:gsub('<h1>%s*Corelibrary (.-)</p>', '<h1>Module %1</h1>') + s = s:gsub('<h2>Core_Functions</h2>', '<h2>Core Functions</h2>') + s = s:gsub('<h2>Core_Libraries</h2>', '<h2>Core Libraries</h2>') + return s +end + +new_type ('init', 'Initialisation', false, 'Parameters') + +format = 'markdown' +backtick_references = false +sort = false diff --git a/Data/BuiltIn/Libraries/lua-stdlib/debug.lua b/Data/BuiltIn/Libraries/lua-stdlib/debug.lua new file mode 100644 index 0000000..cab29ff --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-stdlib/debug.lua @@ -0,0 +1,156 @@ +--[[ + General Lua Libraries for Lua 5.1, 5.2 & 5.3 + Copyright (C) 2002-2018 stdlib authors +]] +--[[-- + Additions to the core debug module. + + The module table returned by `std.debug` also contains all of the entries + from the core debug table. An hygienic way to import this module, then, is + simply to override the core `debug` locally: + + local debug = require 'std.debug' + + @corelibrary std.debug +]] + + +local _ENV = require 'std.normalize' { + 'debug', + _debug = require 'std._debug', + concat = 'table.concat', + huge = 'math.huge', + max = 'math.max', + merge = 'table.merge', + stderr = 'io.stderr', +} + + + +--[[ =============== ]]-- +--[[ Implementation. ]]-- +--[[ =============== ]]-- + + +local function say(n, ...) + local level, argt = n, {...} + if type(n) ~= 'number' then + level, argt = 1, {n, ...} + end + if _debug.level ~= huge and + ((type(_debug.level) == 'number' and _debug.level >= level) or level <= 1) + then + local t = {} + for k, v in pairs(argt) do + t[k] = str(v) + end + stderr:write(concat(t, '\t') .. '\n') + end +end + + +local level = 0 + +local function trace(event) + local t = debug.getinfo(3) + local s = ' >>> ' + for i = 1, level do + s = s .. ' ' + end + if t ~= nil and t.currentline >= 0 then + s = s .. t.short_src .. ':' .. t.currentline .. ' ' + end + t = debug.getinfo(2) + if event == 'call' then + level = level + 1 + else + level = max(level - 1, 0) + end + if t.what == 'main' then + if event == 'call' then + s = s .. 'begin ' .. t.short_src + else + s = s .. 'end ' .. t.short_src + end + elseif t.what == 'Lua' then + s = s .. event .. ' ' ..(t.name or '(Lua)') .. ' <' .. + t.linedefined .. ':' .. t.short_src .. '>' + else + s = s .. event .. ' ' ..(t.name or '(C)') .. ' [' .. t.what .. ']' + end + stderr:write(s .. '\n') +end + +-- Set hooks according to _debug +if _debug.call then + debug.sethook(trace, 'cr') +end + + + +local M = { + --- Function Environments + -- @section environments + + --- Extend `debug.getfenv` to unwrap functables correctly. + -- @function getfenv + -- @tparam int|function|functable fn target function, or stack level + -- @treturn table environment of *fn* + getfenv = getfenv, + + --- Extend `debug.setfenv` to unwrap functables correctly. + -- @function setfenv + -- @tparam function|functable fn target function + -- @tparam table env new function environment + -- @treturn function *fn* + setfenv = setfenv, + + + --- Functions + -- @section functions + + --- Print a debugging message to `io.stderr`. + -- Display arguments passed through `std.tostring` and separated by tab + -- characters when `std._debug` hinting is `true` and *n* is 1 or less; + -- or `std._debug.level` is a number greater than or equal to *n*. If + -- `std._debug` hinting is false or nil, nothing is written. + -- @function say + -- @int[opt=1] n debugging level, smaller is higher priority + -- @param ... objects to print(as for print) + -- @usage + -- local _debug = require 'std._debug' + -- _debug.level = 3 + -- say(2, '_debug status level:', _debug.level) + say = say, + + --- Trace function calls. + -- Use as debug.sethook(trace, 'cr'), which is done automatically + -- when `std._debug.call` is set. + -- Based on test/trace-calls.lua from the Lua distribution. + -- @function trace + -- @string event event causing the call + -- @usage + -- local _debug = require 'std._debug' + -- _debug.call = true + -- local debug = require 'std.debug' + trace = trace, +} + + +--- Metamethods +-- @section metamethods + +--- Equivalent to calling `debug.say(1, ...)` +-- @function __call +-- @see say +-- @usage +-- local debug = require 'std.debug' +-- debug 'oh noes!' +local metatable = { + __call = function(self, ...) + M.say(1, ...) + end, +} + + +return setmetatable(merge(debug, M), metatable) diff --git a/Data/BuiltIn/Libraries/lua-stdlib/init.lua b/Data/BuiltIn/Libraries/lua-stdlib/init.lua new file mode 100644 index 0000000..732d41f --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-stdlib/init.lua @@ -0,0 +1,389 @@ +--[[ + General Lua Libraries for Lua 5.1, 5.2 & 5.3 + Copyright (C) 2002-2018 stdlib authors +]] +--[[-- + Enhanced Lua core functions, and others. + + After requiring this module, simply referencing symbols in the submodule + hierarchy will load the necessary modules on demand. There are no + changes to any global symbols, or monkey patching of core module tables + and metatables. + + @todo Write a style guide(indenting/wrapping, capitalisation, + function and variable names); library functions should call + error, not die; OO vs non-OO(a thorny problem). + @todo pre-compile. + @corefunction std +]] + + +local _ = require 'std._base' + +local argscheck = _.typecheck and _.typecheck.argscheck +local compare = _.list.compare +local maxn = _.table.maxn +local split = _.string.split + +_ = nil + + +local _ENV = require 'std.normalize' { + format = 'string.format', + match = 'string.match', +} + + + +--[[ =============== ]]-- +--[[ Implementation. ]]-- +--[[ =============== ]]-- + + +local M + + +local function _assert(expect, fmt, arg1, ...) + local msg =(arg1 ~= nil) and format(fmt, arg1, ...) or fmt or '' + return expect or error(msg, 2) +end + + +local function elems(t) + -- capture pairs iterator initial state + local fn, istate, ctrl = pairs(t) + return function(state, _) + local v + ctrl, v = fn(state, ctrl) + if ctrl then + return v + end + end, istate, true -- wrapped initial state +end + + +local function eval(s) + return load('return ' .. s)() +end + + +local function ielems(t) + -- capture pairs iterator initial state + local fn, istate, ctrl = ipairs(t) + return function(state, _) + local v + ctrl, v = fn(state, ctrl) + if ctrl then + return v + end + end, istate, true -- wrapped initial state +end + + +local function npairs(t) + local m = getmetamethod(t, '__len') + local i, n = 0, m and m(t) or maxn(t) + return function(t) + i = i + 1 + if i <= n then + return i, t[i] + end + end, + t, i +end + + +local function ripairs(t) + local oob = 1 + while t[oob] ~= nil do + oob = oob + 1 + end + + return function(t, n) + n = n - 1 + if n > 0 then + return n, t[n] + end + end, t, oob +end + + +local function rnpairs(t) + local m = getmetamethod(t, '__len') + local oob =(m and m(t) or maxn(t)) + 1 + + return function(t, n) + n = n - 1 + if n > 0 then + return n, t[n] + end + end, t, oob +end + + +local vconvert = setmetatable({ + string = function(x) + return split(x, '%.') + end, + number = function(x) + return {x} + end, + table = function(x) + return x + end, +}, { + __call = function(self, x) + local fn = self[type(x)] or function() + return 0 + end + return fn(x) + end, +}) + + +local function vcompare(a, b) + return compare(vconvert(a), vconvert(b)) +end + + +local function _require(module, min, too_big, pattern) + pattern = pattern or '([%.%d]+)%D*$' + + local s, m = '', require(module) + if type(m) == 'table' then + s = tostring(m.version or m._VERSION or '') + end + local v = match(s, pattern) or 0 + if min then + _assert(vcompare(v, min) >= 0, "require '" .. module .. + "' with at least version " .. min .. ', but found version ' .. v) + end + if too_big then + _assert(vcompare(v, too_big) < 0, "require '" .. module .. + "' with version less than " .. too_big .. ', but found version ' .. v) + end + return m +end + + + +--[[ ================= ]]-- +--[[ Public Interface. ]]-- +--[[ ================= ]]-- + + +local function X(decl, fn) + return argscheck and argscheck('std.' .. decl, fn) or fn +end + +M = { + --- Release version string. + -- @field version + + + --- Core Functions + -- @section corefuncs + + --- Enhance core `assert` to also allow formatted arguments. + -- @function assert + -- @param expect expression, expected to be *truthy* + -- @string[opt=''] f format string + -- @param[opt] ... arguments to format + -- @return value of *expect*, if *truthy* + -- @usage + -- std.assert(expect == nil, '100% unexpected!') + -- std.assert(expect == 'expect', '%s the unexpected!', expect) + assert = X('assert(?any, ?string, [any...])', _assert), + + --- Evaluate a string as Lua code. + -- @function eval + -- @string s string of Lua code + -- @return result of evaluating `s` + -- @usage + -- --> 2 + -- std.eval 'math.min(2, 10)' + eval = X('eval(string)', eval), + + --- Return named metamethod, if any, otherwise `nil`. + -- The value found at the given key in the metatable of *x* must be a + -- function or have its own `__call` metamethod to qualify as a + -- callable. Any other value found at key *n* will cause this function + -- to return `nil`. + -- @function getmetamethod + -- @param x item to act on + -- @string n name of metamethod to lookup + -- @treturn callable|nil callable metamethod, or `nil` if no metamethod + -- @usage + -- clone = std.getmetamethod(std.object.prototype, '__call') + getmetamethod = X('getmetamethod(?any, string)', getmetamethod), + + + --- Module Functions + -- @section modulefuncs + + --- Enhance core `require` to assert version number compatibility. + -- By default match against the last substring of(dot-delimited) + -- digits in the module version string. + -- @function require + -- @string module module to require + -- @string[opt] min lowest acceptable version + -- @string[opt] too_big lowest version that is too big + -- @string[opt] pattern to match version in `module.version` or + -- `module._VERSION`(default: `'([%.%d]+)%D*$'`) + -- @usage + -- -- posix.version == 'posix library for Lua 5.2 / 32' + -- posix = require('posix', '29') + require = X('require(string, ?string, ?string, ?string)', _require), + + --- Iterator Functions + -- @section iteratorfuncs + + --- An iterator over all values of a table. + -- If *t* has a `__pairs` metamethod, use that to iterate. + -- @function elems + -- @tparam table t a table + -- @treturn function iterator function + -- @treturn table *t*, the table being iterated over + -- @return *key*, the previous iteration key + -- @see ielems + -- @see pairs + -- @usage + -- --> foo + -- --> bar + -- --> baz + -- --> 5 + -- std.functional.map(print, std.ielems, {'foo', 'bar', [4]='baz', d=5}) + elems = X('elems(table)', elems), + + --- An iterator over the integer keyed elements of a table. + -- + -- If *t* has a `__len` metamethod, iterate up to the index it + -- returns, otherwise up to the first `nil`. + -- + -- This function does **not** support the Lua 5.2 `__ipairs` metamethod. + -- @function ielems + -- @tparam table t a table + -- @treturn function iterator function + -- @treturn table *t*, the table being iterated over + -- @treturn int *index*, the previous iteration index + -- @see elems + -- @see ipairs + -- @usage + -- --> foo + -- --> bar + -- std.functional.map(print, std.ielems, {'foo', 'bar', [4]='baz', d=5}) + ielems = X('ielems(table)', ielems), + + --- An iterator over integer keyed pairs of a sequence. + -- + -- Like Lua 5.1 and 5.3, this iterator returns successive key-value + -- pairs with integer keys starting at 1, up to the first `nil` valued + -- pair. + -- + -- If there is a `_len` metamethod, keep iterating up to and including + -- that element, regardless of any intervening `nil` values. + -- + -- This function does **not** support the Lua 5.2 `__ipairs` metamethod. + -- @function ipairs + -- @tparam table t a table + -- @treturn function iterator function + -- @treturn table *t*, the table being iterated over + -- @treturn int *index*, the previous iteration index + -- @see ielems + -- @see npairs + -- @see pairs + -- @usage + -- --> 1 foo + -- --> 2 bar + -- std.functional.map(print, std.ipairs, {'foo', 'bar', [4]='baz', d=5}) + ipairs = X('ipairs(table)', ipairs), + + --- Ordered iterator for integer keyed values. + -- Like ipairs, but does not stop until the __len or maxn of *t*. + -- @function npairs + -- @tparam table t a table + -- @treturn function iterator function + -- @treturn table t + -- @see ipairs + -- @see rnpairs + -- @usage + -- --> 1 foo + -- --> 2 bar + -- --> 3 nil + -- --> 4 baz + -- std.functional.map(print, std.npairs, {'foo', 'bar', [4]='baz', d=5}) + npairs = X('npairs(table)', npairs), + + --- Enhance core `pairs` to respect `__pairs` even in Lua 5.1. + -- @function pairs + -- @tparam table t a table + -- @treturn function iterator function + -- @treturn table *t*, the table being iterated over + -- @return *key*, the previous iteration key + -- @see elems + -- @see ipairs + -- @usage + -- --> 1 foo + -- --> 2 bar + -- --> 4 baz + -- --> d 5 + -- std.functional.map(print, std.pairs, {'foo', 'bar', [4]='baz', d=5}) + pairs = X('pairs(table)', pairs), + + --- An iterator like ipairs, but in reverse. + -- Apart from the order of the elements returned, this function follows + -- the same rules as @{ipairs} for determining first and last elements. + -- @function ripairs + -- @tparam table t any table + -- @treturn function iterator function + -- @treturn table *t* + -- @treturn number `#t + 1` + -- @see ipairs + -- @see rnpairs + -- @usage + -- --> 2 bar + -- --> 1 foo + -- std.functional.map(print, std.ripairs, {'foo', 'bar', [4]='baz', d=5}) + ripairs = X('ripairs(table)', ripairs), + + --- An iterator like npairs, but in reverse. + -- Apart from the order of the elements returned, this function follows + -- the same rules as @{npairs} for determining first and last elements. + -- @function rnpairs + -- @tparam table t a table + -- @treturn function iterator function + -- @treturn table t + -- @see npairs + -- @see ripairs + -- @usage + -- --> 4 baz + -- --> 3 nil + -- --> 2 bar + -- --> 1 foo + -- std.functional.map(print, std.rnpairs, {'foo', 'bar', [4]='baz', d=5}) + rnpairs = X('rnpairs(table)', rnpairs), +} + + +--- Metamethods +-- @section Metamethods + +return setmetatable(M, { + --- Lazy loading of stdlib modules. + -- Don't load everything on initial startup, wait until first attempt + -- to access a submodule, and then load it on demand. + -- @function __index + -- @string name submodule name + -- @treturn table|nil the submodule that was loaded to satisfy the missing + -- `name`, otherwise `nil` if nothing was found + -- @usage + -- local std = require 'std' + -- local Object = std.object.prototype + __index = function(self, name) + local ok, t = pcall(require, 'std.' .. name) + if ok then + rawset(self, name, t) + return t + end + end, +}) diff --git a/Data/BuiltIn/Libraries/lua-stdlib/io.lua b/Data/BuiltIn/Libraries/lua-stdlib/io.lua new file mode 100644 index 0000000..1a2b79f --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-stdlib/io.lua @@ -0,0 +1,322 @@ +--[[ + General Lua Libraries for Lua 5.1, 5.2 & 5.3 + Copyright (C) 2002-2018 stdlib authors +]] +--[[-- + Additions to the core io module. + + The module table returned by `std.io` also contains all of the entries from + the core `io` module table. An hygienic way to import this module, then, + is simply to override core `io` locally: + + local io = require 'std.io' + + @corelibrary std.io +]] + + +local _ = require 'std._base' + +local argscheck = _.typecheck and _.typecheck.argscheck +local catfile = _.io.catfile +local leaves = _.tree.leaves +local split = _.string.split + +_ = nil + + +local _ENV = require 'std.normalize' { + 'io', + _G = _G, -- FIXME: don't use the host _G as an API! + concat = 'table.concat', + dirsep = 'package.dirsep', + format = 'string.format', + gsub = 'string.gsub', + input = 'io.input', + insert = 'table.insert', + io_type = 'io.type', + merge = 'table.merge', + open = 'io.open', + output = 'io.output', + popen = 'io.popen', + stderr = 'io.stderr', + stdin = 'io.stdin', + write = 'io.write', +} + + +--[[ =============== ]]-- +--[[ Implementation. ]]-- +--[[ =============== ]]-- + + +local M + + +local function input_handle(h) + if h == nil then + return input() + elseif type(h) == 'string' then + return open(h) + end + return h +end + + +local function slurp(file) + local h, err = input_handle(file) + if h == nil then + argerror('std.io.slurp', 1, err, 2) + end + + if h then + local s = h:read('*a') + h:close() + return s + end +end + + +local function readlines(file) + local h, err = input_handle(file) + if h == nil then + argerror('std.io.readlines', 1, err, 2) + end + + local l = {} + for line in h:lines() do + l[#l + 1] = line + end + h:close() + return l +end + + +local function writelines(h, ...) + if io_type(h) ~= 'file' then + write(h, '\n') + h = output() + end + for v in leaves(ipairs, {...}) do + h:write(v, '\n') + end +end + + +local function process_files(fn) + -- N.B. 'arg' below refers to the global array of command-line args + if len(arg) == 0 then + insert(arg, '-') + end + for i, v in ipairs(arg) do + if v == '-' then + input(stdin) + else + input(v) + end + fn(v, i) + end +end + + +local function warnfmt(msg, ...) + local prefix = '' + local prog = rawget(_G, 'prog') or {} + local opts = rawget(_G, 'opts') or {} + if prog.name then + prefix = prog.name .. ':' + if prog.line then + prefix = prefix .. str(prog.line) .. ':' + end + elseif prog.file then + prefix = prog.file .. ':' + if prog.line then + prefix = prefix .. str(prog.line) .. ':' + end + elseif opts.program then + prefix = opts.program .. ':' + if opts.line then + prefix = prefix .. str(opts.line) .. ':' + end + end + if #prefix > 0 then + prefix = prefix .. ' ' + end + return prefix .. format(msg, ...) +end + + +local function warn(msg, ...) + writelines(stderr, warnfmt(msg, ...)) +end + + + +--[[ ================= ]]-- +--[[ Public Interface. ]]-- +--[[ ================= ]]-- + + +local function X(decl, fn) + return argscheck and argscheck('std.io.' .. decl, fn) or fn +end + + +M = { + --- Diagnostic functions + -- @section diagnosticfuncs + + --- Die with error. + -- This function uses the same rules to build a message prefix + -- as @{warn}. + -- @function die + -- @string msg format string + -- @param ... additional arguments to plug format string specifiers + -- @see warn + -- @usage + -- die('oh noes!(%s)', tostring(obj)) + die = X('die(string, [any...])', function(...) + error(warnfmt(...), 0) + end), + + --- Give warning with the name of program and file(if any). + -- If there is a global `prog` table, prefix the message with + -- `prog.name` or `prog.file`, and `prog.line` if any. Otherwise + -- if there is a global `opts` table, prefix the message with + -- `opts.program` and `opts.line` if any. + -- @function warn + -- @string msg format string + -- @param ... additional arguments to plug format string specifiers + -- @see die + -- @usage + -- local OptionParser = require 'std.optparse' + -- local parser = OptionParser 'eg 0\nUsage: eg\n' + -- _G.arg, _G.opts = parser:parse(_G.arg) + -- if not _G.opts.keep_going then + -- require 'std.io'.warn 'oh noes!' + -- end + warn = X('warn(string, [any...])', warn), + + + --- Path Functions + -- @section pathfuncs + + --- Concatenate directory names into a path. + -- @function catdir + -- @string ... path components + -- @return path without trailing separator + -- @see catfile + -- @usage + -- dirpath = catdir('', 'absolute', 'directory') + catdir = X('catdir(string...)', function(...) + return(gsub(concat({...}, dirsep), '^$', dirsep)) + end), + + --- Concatenate one or more directories and a filename into a path. + -- @function catfile + -- @string ... path components + -- @treturn string path + -- @see catdir + -- @see splitdir + -- @usage + -- filepath = catfile('relative', 'path', 'filename') + catfile = X('catfile(string...)', catfile), + + --- Remove the last dirsep delimited element from a path. + -- @function dirname + -- @string path file path + -- @treturn string a new path with the last dirsep and following + -- truncated + -- @usage + -- dir = dirname '/base/subdir/filename' + dirname = X('dirname(string)', function(path) + return(gsub(path, catfile('', '[^', ']*$'), '')) + end), + + --- Split a directory path into components. + -- Empty components are retained: the root directory becomes `{'', ''}`. + -- @function splitdir + -- @param path path + -- @return list of path components + -- @see catdir + -- @usage + -- dir_components = splitdir(filepath) + splitdir = X('splitdir(string)', function(path) + return split(path, dirsep) + end), + + + --- IO Functions + -- @section iofuncs + + --- Process files specified on the command-line. + -- Each filename is made the default input source with `io.input`, and + -- then the filename and argument number are passed to the callback + -- function. In list of filenames, `-` means `io.stdin`. If no + -- filenames were given, behave as if a single `-` was passed. + -- @todo Make the file list an argument to the function. + -- @function process_files + -- @tparam fileprocessor fn function called for each file argument + -- @usage + -- #! /usr/bin/env lua + -- -- minimal cat command + -- local io = require 'std.io' + -- io.process_files(function() io.write(io.slurp()) end) + process_files = X('process_files(function)', process_files), + + --- Read a file or file handle into a list of lines. + -- The lines in the returned list are not `\n` terminated. + -- @function readlines + -- @tparam[opt=io.input()] file|string file file handle or name; + -- if file is a file handle, that file is closed after reading + -- @treturn list lines + -- @usage + -- list = readlines '/etc/passwd' + readlines = X('readlines(?file|string)', readlines), + + --- Perform a shell command and return its output. + -- @function shell + -- @string c command + -- @treturn string output, or nil if error + -- @see os.execute + -- @usage + -- users = shell [[cat /etc/passwd | awk -F: '{print $1;}']] + shell = X('shell(string)', function(c) return slurp(popen(c)) end), + + --- Slurp a file handle. + -- @function slurp + -- @tparam[opt=io.input()] file|string file file handle or name; + -- if file is a file handle, that file is closed after reading + -- @return contents of file or handle, or nil if error + -- @see process_files + -- @usage + -- contents = slurp(filename) + slurp = X('slurp(?file|string)', slurp), + + --- Write values adding a newline after each. + -- @function writelines + -- @tparam[opt=io.output()] file h open writable file handle; + -- the file is **not** closed after writing + -- @tparam string|number ... values to write(as for write) + -- @usage + -- writelines(io.stdout, 'first line', 'next line') + writelines = X('writelines(?file|string|number, [string|number...])', writelines), +} + + +return merge(io, M) + + + +--- Types +-- @section Types + +--- Signature of @{process_files} callback function. +-- @function fileprocessor +-- @string filename filename +-- @int i argument number of *filename* +-- @usage +-- local fileprocessor = function(filename, i) +-- io.write(tostring(i) .. ':\n===\n' .. io.slurp(filename) .. '\n') +-- end +-- io.process_files(fileprocessor) diff --git a/Data/BuiltIn/Libraries/lua-stdlib/lib/std/_base.lua b/Data/BuiltIn/Libraries/lua-stdlib/lib/std/_base.lua new file mode 100644 index 0000000..0c623d4 --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-stdlib/lib/std/_base.lua @@ -0,0 +1,204 @@ +--[[ + General Lua Libraries for Lua 5.1, 5.2 & 5.3 + Copyright (C) 2002-2018 stdlib authors +]] +--[[-- + Prevent dependency loops with key function implementations. + + A few key functions are used in several stdlib modules; we implement those + functions in this internal module to prevent dependency loops in the first + instance, and to minimise coupling between modules where the use of one of + these functions might otherwise load a whole selection of other supporting + modules unnecessarily. + + Although the implementations are here for logistical reasons, we re-export + them from their respective logical modules so that the api is not affected + as far as client code is concerned. The functions in this file do not make + use of `argcheck` or similar, because we know that they are only called by + other stdlib functions which have already performed the necessary checking + and neither do we want to slow everything down by recheckng those argument + types here. + + This implies that when re-exporting from another module when argument type + checking is in force, we must export a wrapper function that can check the + user's arguments fully at the API boundary. +]] + + +local _ENV = require 'std.normalize' { + concat = 'table.concat', + dirsep = 'package.dirsep', + find = 'string.find', + gsub = 'string.gsub', + insert = 'table.insert', + min = 'math.min', + shallow_copy = 'table.merge', + sort = 'table.sort', + sub = 'string.sub', + table_maxn = table.maxn, + wrap = 'coroutine.wrap', + yield = 'coroutine.yield', +} + + + +--[[ ============================ ]]-- +--[[ Enhanced Core Lua functions. ]]-- +--[[ ============================ ]]-- + + +-- These come as early as possible, because we want the rest of the code +-- in this file to use these versions over the core Lua implementation +-- (which have slightly varying semantics between releases). + + +local maxn = table_maxn or function(t) + local n = 0 + for k in pairs(t) do + if type(k) == 'number' and k > n then + n = k + end + end + return n +end + + + +--[[ ============================ ]]-- +--[[ Shared Stdlib API functions. ]]-- +--[[ ============================ ]]-- + + +-- No need to recurse because functables are second class citizens in +-- Lua: +-- func = function() print 'called' end +-- func() --> 'called' +-- functable=setmetatable({}, {__call=func}) +-- functable() --> 'called' +-- nested=setmetatable({}, {__call=functable}) +-- nested() +-- --> stdin:1: attempt to call a table value(global 'd') +-- --> stack traceback: +-- --> stdin:1: in main chunk +-- --> [C]: in ? +local function callable(x) + if type(x) == 'function' then + return x + end + return (getmetatable(x) or {}).__call +end + + +local function catfile(...) + return concat({...}, dirsep) +end + + +local function compare(l, m) + local lenl, lenm = len(l), len(m) + for i = 1, min(lenl, lenm) do + local li, mi = tonumber(l[i]), tonumber(m[i]) + if li == nil or mi == nil then + li, mi = l[i], m[i] + end + if li < mi then + return -1 + elseif li > mi then + return 1 + end + end + if lenl < lenm then + return -1 + elseif lenl > lenm then + return 1 + end + return 0 +end + + +local function escape_pattern(s) + return (gsub(s, '[%^%$%(%)%%%.%[%]%*%+%-%?]', '%%%0')) +end + + +local function invert(t) + local i = {} + for k, v in pairs(t) do + i[v] = k + end + return i +end + + +local function leaves(it, tr) + local function visit(n) + if type(n) == 'table' then + for _, v in it(n) do + visit(v) + end + else + yield(n) + end + end + return wrap(visit), tr +end + + +local function split(s, sep) + local r, patt = {} + if sep == '' then + patt = '(.)' + insert(r, '') + else + patt = '(.-)' ..(sep or '%s+') + end + local b, slen = 0, len(s) + while b <= slen do + local e, n, m = find(s, patt, b + 1) + insert(r, m or sub(s, b + 1, slen)) + b = n or slen + 1 + end + return r +end + + +--[[ ============= ]]-- +--[[ Internal API. ]]-- +--[[ ============= ]]-- + + +-- For efficient use within stdlib, these functions have no type-checking. +-- In debug mode, type-checking wrappers are re-exported from the public- +-- facing modules as necessary. +-- +-- Also, to provide some sanity, we mirror the subtable layout of stdlib +-- public API here too, which means everything looks relatively normal +-- when importing the functions into stdlib implementation modules. +return { + io = { + catfile = catfile, + }, + + list = { + compare = compare, + }, + + object = { + Module = Module, + mapfields = mapfields, + }, + + string = { + escape_pattern = escape_pattern, + split = split, + }, + + table = { + invert = invert, + maxn = maxn, + }, + + tree = { + leaves = leaves, + }, +} diff --git a/Data/BuiltIn/Libraries/lua-stdlib/lib/std/debug.lua b/Data/BuiltIn/Libraries/lua-stdlib/lib/std/debug.lua new file mode 100644 index 0000000..cab29ff --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-stdlib/lib/std/debug.lua @@ -0,0 +1,156 @@ +--[[ + General Lua Libraries for Lua 5.1, 5.2 & 5.3 + Copyright (C) 2002-2018 stdlib authors +]] +--[[-- + Additions to the core debug module. + + The module table returned by `std.debug` also contains all of the entries + from the core debug table. An hygienic way to import this module, then, is + simply to override the core `debug` locally: + + local debug = require 'std.debug' + + @corelibrary std.debug +]] + + +local _ENV = require 'std.normalize' { + 'debug', + _debug = require 'std._debug', + concat = 'table.concat', + huge = 'math.huge', + max = 'math.max', + merge = 'table.merge', + stderr = 'io.stderr', +} + + + +--[[ =============== ]]-- +--[[ Implementation. ]]-- +--[[ =============== ]]-- + + +local function say(n, ...) + local level, argt = n, {...} + if type(n) ~= 'number' then + level, argt = 1, {n, ...} + end + if _debug.level ~= huge and + ((type(_debug.level) == 'number' and _debug.level >= level) or level <= 1) + then + local t = {} + for k, v in pairs(argt) do + t[k] = str(v) + end + stderr:write(concat(t, '\t') .. '\n') + end +end + + +local level = 0 + +local function trace(event) + local t = debug.getinfo(3) + local s = ' >>> ' + for i = 1, level do + s = s .. ' ' + end + if t ~= nil and t.currentline >= 0 then + s = s .. t.short_src .. ':' .. t.currentline .. ' ' + end + t = debug.getinfo(2) + if event == 'call' then + level = level + 1 + else + level = max(level - 1, 0) + end + if t.what == 'main' then + if event == 'call' then + s = s .. 'begin ' .. t.short_src + else + s = s .. 'end ' .. t.short_src + end + elseif t.what == 'Lua' then + s = s .. event .. ' ' ..(t.name or '(Lua)') .. ' <' .. + t.linedefined .. ':' .. t.short_src .. '>' + else + s = s .. event .. ' ' ..(t.name or '(C)') .. ' [' .. t.what .. ']' + end + stderr:write(s .. '\n') +end + +-- Set hooks according to _debug +if _debug.call then + debug.sethook(trace, 'cr') +end + + + +local M = { + --- Function Environments + -- @section environments + + --- Extend `debug.getfenv` to unwrap functables correctly. + -- @function getfenv + -- @tparam int|function|functable fn target function, or stack level + -- @treturn table environment of *fn* + getfenv = getfenv, + + --- Extend `debug.setfenv` to unwrap functables correctly. + -- @function setfenv + -- @tparam function|functable fn target function + -- @tparam table env new function environment + -- @treturn function *fn* + setfenv = setfenv, + + + --- Functions + -- @section functions + + --- Print a debugging message to `io.stderr`. + -- Display arguments passed through `std.tostring` and separated by tab + -- characters when `std._debug` hinting is `true` and *n* is 1 or less; + -- or `std._debug.level` is a number greater than or equal to *n*. If + -- `std._debug` hinting is false or nil, nothing is written. + -- @function say + -- @int[opt=1] n debugging level, smaller is higher priority + -- @param ... objects to print(as for print) + -- @usage + -- local _debug = require 'std._debug' + -- _debug.level = 3 + -- say(2, '_debug status level:', _debug.level) + say = say, + + --- Trace function calls. + -- Use as debug.sethook(trace, 'cr'), which is done automatically + -- when `std._debug.call` is set. + -- Based on test/trace-calls.lua from the Lua distribution. + -- @function trace + -- @string event event causing the call + -- @usage + -- local _debug = require 'std._debug' + -- _debug.call = true + -- local debug = require 'std.debug' + trace = trace, +} + + +--- Metamethods +-- @section metamethods + +--- Equivalent to calling `debug.say(1, ...)` +-- @function __call +-- @see say +-- @usage +-- local debug = require 'std.debug' +-- debug 'oh noes!' +local metatable = { + __call = function(self, ...) + M.say(1, ...) + end, +} + + +return setmetatable(merge(debug, M), metatable) diff --git a/Data/BuiltIn/Libraries/lua-stdlib/lib/std/init.lua b/Data/BuiltIn/Libraries/lua-stdlib/lib/std/init.lua new file mode 100644 index 0000000..732d41f --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-stdlib/lib/std/init.lua @@ -0,0 +1,389 @@ +--[[ + General Lua Libraries for Lua 5.1, 5.2 & 5.3 + Copyright (C) 2002-2018 stdlib authors +]] +--[[-- + Enhanced Lua core functions, and others. + + After requiring this module, simply referencing symbols in the submodule + hierarchy will load the necessary modules on demand. There are no + changes to any global symbols, or monkey patching of core module tables + and metatables. + + @todo Write a style guide(indenting/wrapping, capitalisation, + function and variable names); library functions should call + error, not die; OO vs non-OO(a thorny problem). + @todo pre-compile. + @corefunction std +]] + + +local _ = require 'std._base' + +local argscheck = _.typecheck and _.typecheck.argscheck +local compare = _.list.compare +local maxn = _.table.maxn +local split = _.string.split + +_ = nil + + +local _ENV = require 'std.normalize' { + format = 'string.format', + match = 'string.match', +} + + + +--[[ =============== ]]-- +--[[ Implementation. ]]-- +--[[ =============== ]]-- + + +local M + + +local function _assert(expect, fmt, arg1, ...) + local msg =(arg1 ~= nil) and format(fmt, arg1, ...) or fmt or '' + return expect or error(msg, 2) +end + + +local function elems(t) + -- capture pairs iterator initial state + local fn, istate, ctrl = pairs(t) + return function(state, _) + local v + ctrl, v = fn(state, ctrl) + if ctrl then + return v + end + end, istate, true -- wrapped initial state +end + + +local function eval(s) + return load('return ' .. s)() +end + + +local function ielems(t) + -- capture pairs iterator initial state + local fn, istate, ctrl = ipairs(t) + return function(state, _) + local v + ctrl, v = fn(state, ctrl) + if ctrl then + return v + end + end, istate, true -- wrapped initial state +end + + +local function npairs(t) + local m = getmetamethod(t, '__len') + local i, n = 0, m and m(t) or maxn(t) + return function(t) + i = i + 1 + if i <= n then + return i, t[i] + end + end, + t, i +end + + +local function ripairs(t) + local oob = 1 + while t[oob] ~= nil do + oob = oob + 1 + end + + return function(t, n) + n = n - 1 + if n > 0 then + return n, t[n] + end + end, t, oob +end + + +local function rnpairs(t) + local m = getmetamethod(t, '__len') + local oob =(m and m(t) or maxn(t)) + 1 + + return function(t, n) + n = n - 1 + if n > 0 then + return n, t[n] + end + end, t, oob +end + + +local vconvert = setmetatable({ + string = function(x) + return split(x, '%.') + end, + number = function(x) + return {x} + end, + table = function(x) + return x + end, +}, { + __call = function(self, x) + local fn = self[type(x)] or function() + return 0 + end + return fn(x) + end, +}) + + +local function vcompare(a, b) + return compare(vconvert(a), vconvert(b)) +end + + +local function _require(module, min, too_big, pattern) + pattern = pattern or '([%.%d]+)%D*$' + + local s, m = '', require(module) + if type(m) == 'table' then + s = tostring(m.version or m._VERSION or '') + end + local v = match(s, pattern) or 0 + if min then + _assert(vcompare(v, min) >= 0, "require '" .. module .. + "' with at least version " .. min .. ', but found version ' .. v) + end + if too_big then + _assert(vcompare(v, too_big) < 0, "require '" .. module .. + "' with version less than " .. too_big .. ', but found version ' .. v) + end + return m +end + + + +--[[ ================= ]]-- +--[[ Public Interface. ]]-- +--[[ ================= ]]-- + + +local function X(decl, fn) + return argscheck and argscheck('std.' .. decl, fn) or fn +end + +M = { + --- Release version string. + -- @field version + + + --- Core Functions + -- @section corefuncs + + --- Enhance core `assert` to also allow formatted arguments. + -- @function assert + -- @param expect expression, expected to be *truthy* + -- @string[opt=''] f format string + -- @param[opt] ... arguments to format + -- @return value of *expect*, if *truthy* + -- @usage + -- std.assert(expect == nil, '100% unexpected!') + -- std.assert(expect == 'expect', '%s the unexpected!', expect) + assert = X('assert(?any, ?string, [any...])', _assert), + + --- Evaluate a string as Lua code. + -- @function eval + -- @string s string of Lua code + -- @return result of evaluating `s` + -- @usage + -- --> 2 + -- std.eval 'math.min(2, 10)' + eval = X('eval(string)', eval), + + --- Return named metamethod, if any, otherwise `nil`. + -- The value found at the given key in the metatable of *x* must be a + -- function or have its own `__call` metamethod to qualify as a + -- callable. Any other value found at key *n* will cause this function + -- to return `nil`. + -- @function getmetamethod + -- @param x item to act on + -- @string n name of metamethod to lookup + -- @treturn callable|nil callable metamethod, or `nil` if no metamethod + -- @usage + -- clone = std.getmetamethod(std.object.prototype, '__call') + getmetamethod = X('getmetamethod(?any, string)', getmetamethod), + + + --- Module Functions + -- @section modulefuncs + + --- Enhance core `require` to assert version number compatibility. + -- By default match against the last substring of(dot-delimited) + -- digits in the module version string. + -- @function require + -- @string module module to require + -- @string[opt] min lowest acceptable version + -- @string[opt] too_big lowest version that is too big + -- @string[opt] pattern to match version in `module.version` or + -- `module._VERSION`(default: `'([%.%d]+)%D*$'`) + -- @usage + -- -- posix.version == 'posix library for Lua 5.2 / 32' + -- posix = require('posix', '29') + require = X('require(string, ?string, ?string, ?string)', _require), + + --- Iterator Functions + -- @section iteratorfuncs + + --- An iterator over all values of a table. + -- If *t* has a `__pairs` metamethod, use that to iterate. + -- @function elems + -- @tparam table t a table + -- @treturn function iterator function + -- @treturn table *t*, the table being iterated over + -- @return *key*, the previous iteration key + -- @see ielems + -- @see pairs + -- @usage + -- --> foo + -- --> bar + -- --> baz + -- --> 5 + -- std.functional.map(print, std.ielems, {'foo', 'bar', [4]='baz', d=5}) + elems = X('elems(table)', elems), + + --- An iterator over the integer keyed elements of a table. + -- + -- If *t* has a `__len` metamethod, iterate up to the index it + -- returns, otherwise up to the first `nil`. + -- + -- This function does **not** support the Lua 5.2 `__ipairs` metamethod. + -- @function ielems + -- @tparam table t a table + -- @treturn function iterator function + -- @treturn table *t*, the table being iterated over + -- @treturn int *index*, the previous iteration index + -- @see elems + -- @see ipairs + -- @usage + -- --> foo + -- --> bar + -- std.functional.map(print, std.ielems, {'foo', 'bar', [4]='baz', d=5}) + ielems = X('ielems(table)', ielems), + + --- An iterator over integer keyed pairs of a sequence. + -- + -- Like Lua 5.1 and 5.3, this iterator returns successive key-value + -- pairs with integer keys starting at 1, up to the first `nil` valued + -- pair. + -- + -- If there is a `_len` metamethod, keep iterating up to and including + -- that element, regardless of any intervening `nil` values. + -- + -- This function does **not** support the Lua 5.2 `__ipairs` metamethod. + -- @function ipairs + -- @tparam table t a table + -- @treturn function iterator function + -- @treturn table *t*, the table being iterated over + -- @treturn int *index*, the previous iteration index + -- @see ielems + -- @see npairs + -- @see pairs + -- @usage + -- --> 1 foo + -- --> 2 bar + -- std.functional.map(print, std.ipairs, {'foo', 'bar', [4]='baz', d=5}) + ipairs = X('ipairs(table)', ipairs), + + --- Ordered iterator for integer keyed values. + -- Like ipairs, but does not stop until the __len or maxn of *t*. + -- @function npairs + -- @tparam table t a table + -- @treturn function iterator function + -- @treturn table t + -- @see ipairs + -- @see rnpairs + -- @usage + -- --> 1 foo + -- --> 2 bar + -- --> 3 nil + -- --> 4 baz + -- std.functional.map(print, std.npairs, {'foo', 'bar', [4]='baz', d=5}) + npairs = X('npairs(table)', npairs), + + --- Enhance core `pairs` to respect `__pairs` even in Lua 5.1. + -- @function pairs + -- @tparam table t a table + -- @treturn function iterator function + -- @treturn table *t*, the table being iterated over + -- @return *key*, the previous iteration key + -- @see elems + -- @see ipairs + -- @usage + -- --> 1 foo + -- --> 2 bar + -- --> 4 baz + -- --> d 5 + -- std.functional.map(print, std.pairs, {'foo', 'bar', [4]='baz', d=5}) + pairs = X('pairs(table)', pairs), + + --- An iterator like ipairs, but in reverse. + -- Apart from the order of the elements returned, this function follows + -- the same rules as @{ipairs} for determining first and last elements. + -- @function ripairs + -- @tparam table t any table + -- @treturn function iterator function + -- @treturn table *t* + -- @treturn number `#t + 1` + -- @see ipairs + -- @see rnpairs + -- @usage + -- --> 2 bar + -- --> 1 foo + -- std.functional.map(print, std.ripairs, {'foo', 'bar', [4]='baz', d=5}) + ripairs = X('ripairs(table)', ripairs), + + --- An iterator like npairs, but in reverse. + -- Apart from the order of the elements returned, this function follows + -- the same rules as @{npairs} for determining first and last elements. + -- @function rnpairs + -- @tparam table t a table + -- @treturn function iterator function + -- @treturn table t + -- @see npairs + -- @see ripairs + -- @usage + -- --> 4 baz + -- --> 3 nil + -- --> 2 bar + -- --> 1 foo + -- std.functional.map(print, std.rnpairs, {'foo', 'bar', [4]='baz', d=5}) + rnpairs = X('rnpairs(table)', rnpairs), +} + + +--- Metamethods +-- @section Metamethods + +return setmetatable(M, { + --- Lazy loading of stdlib modules. + -- Don't load everything on initial startup, wait until first attempt + -- to access a submodule, and then load it on demand. + -- @function __index + -- @string name submodule name + -- @treturn table|nil the submodule that was loaded to satisfy the missing + -- `name`, otherwise `nil` if nothing was found + -- @usage + -- local std = require 'std' + -- local Object = std.object.prototype + __index = function(self, name) + local ok, t = pcall(require, 'std.' .. name) + if ok then + rawset(self, name, t) + return t + end + end, +}) diff --git a/Data/BuiltIn/Libraries/lua-stdlib/lib/std/io.lua b/Data/BuiltIn/Libraries/lua-stdlib/lib/std/io.lua new file mode 100644 index 0000000..1a2b79f --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-stdlib/lib/std/io.lua @@ -0,0 +1,322 @@ +--[[ + General Lua Libraries for Lua 5.1, 5.2 & 5.3 + Copyright (C) 2002-2018 stdlib authors +]] +--[[-- + Additions to the core io module. + + The module table returned by `std.io` also contains all of the entries from + the core `io` module table. An hygienic way to import this module, then, + is simply to override core `io` locally: + + local io = require 'std.io' + + @corelibrary std.io +]] + + +local _ = require 'std._base' + +local argscheck = _.typecheck and _.typecheck.argscheck +local catfile = _.io.catfile +local leaves = _.tree.leaves +local split = _.string.split + +_ = nil + + +local _ENV = require 'std.normalize' { + 'io', + _G = _G, -- FIXME: don't use the host _G as an API! + concat = 'table.concat', + dirsep = 'package.dirsep', + format = 'string.format', + gsub = 'string.gsub', + input = 'io.input', + insert = 'table.insert', + io_type = 'io.type', + merge = 'table.merge', + open = 'io.open', + output = 'io.output', + popen = 'io.popen', + stderr = 'io.stderr', + stdin = 'io.stdin', + write = 'io.write', +} + + +--[[ =============== ]]-- +--[[ Implementation. ]]-- +--[[ =============== ]]-- + + +local M + + +local function input_handle(h) + if h == nil then + return input() + elseif type(h) == 'string' then + return open(h) + end + return h +end + + +local function slurp(file) + local h, err = input_handle(file) + if h == nil then + argerror('std.io.slurp', 1, err, 2) + end + + if h then + local s = h:read('*a') + h:close() + return s + end +end + + +local function readlines(file) + local h, err = input_handle(file) + if h == nil then + argerror('std.io.readlines', 1, err, 2) + end + + local l = {} + for line in h:lines() do + l[#l + 1] = line + end + h:close() + return l +end + + +local function writelines(h, ...) + if io_type(h) ~= 'file' then + write(h, '\n') + h = output() + end + for v in leaves(ipairs, {...}) do + h:write(v, '\n') + end +end + + +local function process_files(fn) + -- N.B. 'arg' below refers to the global array of command-line args + if len(arg) == 0 then + insert(arg, '-') + end + for i, v in ipairs(arg) do + if v == '-' then + input(stdin) + else + input(v) + end + fn(v, i) + end +end + + +local function warnfmt(msg, ...) + local prefix = '' + local prog = rawget(_G, 'prog') or {} + local opts = rawget(_G, 'opts') or {} + if prog.name then + prefix = prog.name .. ':' + if prog.line then + prefix = prefix .. str(prog.line) .. ':' + end + elseif prog.file then + prefix = prog.file .. ':' + if prog.line then + prefix = prefix .. str(prog.line) .. ':' + end + elseif opts.program then + prefix = opts.program .. ':' + if opts.line then + prefix = prefix .. str(opts.line) .. ':' + end + end + if #prefix > 0 then + prefix = prefix .. ' ' + end + return prefix .. format(msg, ...) +end + + +local function warn(msg, ...) + writelines(stderr, warnfmt(msg, ...)) +end + + + +--[[ ================= ]]-- +--[[ Public Interface. ]]-- +--[[ ================= ]]-- + + +local function X(decl, fn) + return argscheck and argscheck('std.io.' .. decl, fn) or fn +end + + +M = { + --- Diagnostic functions + -- @section diagnosticfuncs + + --- Die with error. + -- This function uses the same rules to build a message prefix + -- as @{warn}. + -- @function die + -- @string msg format string + -- @param ... additional arguments to plug format string specifiers + -- @see warn + -- @usage + -- die('oh noes!(%s)', tostring(obj)) + die = X('die(string, [any...])', function(...) + error(warnfmt(...), 0) + end), + + --- Give warning with the name of program and file(if any). + -- If there is a global `prog` table, prefix the message with + -- `prog.name` or `prog.file`, and `prog.line` if any. Otherwise + -- if there is a global `opts` table, prefix the message with + -- `opts.program` and `opts.line` if any. + -- @function warn + -- @string msg format string + -- @param ... additional arguments to plug format string specifiers + -- @see die + -- @usage + -- local OptionParser = require 'std.optparse' + -- local parser = OptionParser 'eg 0\nUsage: eg\n' + -- _G.arg, _G.opts = parser:parse(_G.arg) + -- if not _G.opts.keep_going then + -- require 'std.io'.warn 'oh noes!' + -- end + warn = X('warn(string, [any...])', warn), + + + --- Path Functions + -- @section pathfuncs + + --- Concatenate directory names into a path. + -- @function catdir + -- @string ... path components + -- @return path without trailing separator + -- @see catfile + -- @usage + -- dirpath = catdir('', 'absolute', 'directory') + catdir = X('catdir(string...)', function(...) + return(gsub(concat({...}, dirsep), '^$', dirsep)) + end), + + --- Concatenate one or more directories and a filename into a path. + -- @function catfile + -- @string ... path components + -- @treturn string path + -- @see catdir + -- @see splitdir + -- @usage + -- filepath = catfile('relative', 'path', 'filename') + catfile = X('catfile(string...)', catfile), + + --- Remove the last dirsep delimited element from a path. + -- @function dirname + -- @string path file path + -- @treturn string a new path with the last dirsep and following + -- truncated + -- @usage + -- dir = dirname '/base/subdir/filename' + dirname = X('dirname(string)', function(path) + return(gsub(path, catfile('', '[^', ']*$'), '')) + end), + + --- Split a directory path into components. + -- Empty components are retained: the root directory becomes `{'', ''}`. + -- @function splitdir + -- @param path path + -- @return list of path components + -- @see catdir + -- @usage + -- dir_components = splitdir(filepath) + splitdir = X('splitdir(string)', function(path) + return split(path, dirsep) + end), + + + --- IO Functions + -- @section iofuncs + + --- Process files specified on the command-line. + -- Each filename is made the default input source with `io.input`, and + -- then the filename and argument number are passed to the callback + -- function. In list of filenames, `-` means `io.stdin`. If no + -- filenames were given, behave as if a single `-` was passed. + -- @todo Make the file list an argument to the function. + -- @function process_files + -- @tparam fileprocessor fn function called for each file argument + -- @usage + -- #! /usr/bin/env lua + -- -- minimal cat command + -- local io = require 'std.io' + -- io.process_files(function() io.write(io.slurp()) end) + process_files = X('process_files(function)', process_files), + + --- Read a file or file handle into a list of lines. + -- The lines in the returned list are not `\n` terminated. + -- @function readlines + -- @tparam[opt=io.input()] file|string file file handle or name; + -- if file is a file handle, that file is closed after reading + -- @treturn list lines + -- @usage + -- list = readlines '/etc/passwd' + readlines = X('readlines(?file|string)', readlines), + + --- Perform a shell command and return its output. + -- @function shell + -- @string c command + -- @treturn string output, or nil if error + -- @see os.execute + -- @usage + -- users = shell [[cat /etc/passwd | awk -F: '{print $1;}']] + shell = X('shell(string)', function(c) return slurp(popen(c)) end), + + --- Slurp a file handle. + -- @function slurp + -- @tparam[opt=io.input()] file|string file file handle or name; + -- if file is a file handle, that file is closed after reading + -- @return contents of file or handle, or nil if error + -- @see process_files + -- @usage + -- contents = slurp(filename) + slurp = X('slurp(?file|string)', slurp), + + --- Write values adding a newline after each. + -- @function writelines + -- @tparam[opt=io.output()] file h open writable file handle; + -- the file is **not** closed after writing + -- @tparam string|number ... values to write(as for write) + -- @usage + -- writelines(io.stdout, 'first line', 'next line') + writelines = X('writelines(?file|string|number, [string|number...])', writelines), +} + + +return merge(io, M) + + + +--- Types +-- @section Types + +--- Signature of @{process_files} callback function. +-- @function fileprocessor +-- @string filename filename +-- @int i argument number of *filename* +-- @usage +-- local fileprocessor = function(filename, i) +-- io.write(tostring(i) .. ':\n===\n' .. io.slurp(filename) .. '\n') +-- end +-- io.process_files(fileprocessor) diff --git a/Data/BuiltIn/Libraries/lua-stdlib/lib/std/math.lua b/Data/BuiltIn/Libraries/lua-stdlib/lib/std/math.lua new file mode 100644 index 0000000..d955862 --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-stdlib/lib/std/math.lua @@ -0,0 +1,92 @@ +--[[ + General Lua Libraries for Lua 5.1, 5.2 & 5.3 + Copyright (C) 2002-2018 stdlib authors +]] +--[[-- + Additions to the core math module. + + The module table returned by `std.math` also contains all of the entries from + the core math table. An hygienic way to import this module, then, is simply + to override the core `math` locally: + + local math = require 'std.math' + + @corelibrary std.math +]] + + +local _ = require 'std._base' + +local argscheck = _.typecheck and _.typecheck.argscheck + +_ = nil + + +local _ENV = require 'std.normalize' { + 'math', + merge = 'table.merge', +} + + + +--[[ ================= ]]-- +--[[ Implementatation. ]]-- +--[[ ================= ]]-- + + +local M + + +local _floor = math.floor + +local function floor(n, p) + if(p or 0) == 0 then + return _floor(n) + end + local e = 10 ^ p + return _floor(n * e) / e +end + + +local function round(n, p) + local e = 10 ^(p or 0) + return _floor(n * e + 0.5) / e +end + + + +--[[ ================= ]]-- +--[[ Public Interface. ]]-- +--[[ ================= ]]-- + + +local function X(decl, fn) + return argscheck and argscheck('std.math.' .. decl, fn) or fn +end + + +M = { + --- Core Functions + -- @section corefuncs + + --- Extend `math.floor` to take the number of decimal places. + -- @function floor + -- @number n number + -- @int[opt=0] p number of decimal places to truncate to + -- @treturn number `n` truncated to `p` decimal places + -- @usage + -- tenths = floor(magnitude, 1) + floor = X('floor(number, ?int)', floor), + + --- Round a number to a given number of decimal places. + -- @function round + -- @number n number + -- @int[opt=0] p number of decimal places to round to + -- @treturn number `n` rounded to `p` decimal places + -- @usage + -- roughly = round(exactly, 2) + round = X('round(number, ?int)', round), +} + + +return merge(math, M) diff --git a/Data/BuiltIn/Libraries/lua-stdlib/lib/std/package.lua b/Data/BuiltIn/Libraries/lua-stdlib/lib/std/package.lua new file mode 100644 index 0000000..e3e8243 --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-stdlib/lib/std/package.lua @@ -0,0 +1,263 @@ +--[[ + General Lua Libraries for Lua 5.1, 5.2 & 5.3 + Copyright (C) 2002-2018 stdlib authors +]] +--[[-- + Additions to the core package module. + + The module table returned by `std.package` also contains all of the entries + from the core `package` table. An hygienic way to import this module, then, is + simply to override core `package` locally: + + local package = require 'std.package' + + Manage `package.path` with normalization, duplicate removal, + insertion & removal of elements and automatic folding of '/' and '?' + onto `package.dirsep` and `package.pathmark`, for easy addition of + new paths. For example, instead of all this: + + lib = std.io.catfile('.', 'lib', package.pathmark .. '.lua') + paths = std.string.split(package.path, package.pathsep) + for i, path in ipairs(paths) do + -- ... lots of normalization code... + end + i = 1 + while i <= #paths do + if paths[i] == lib then + table.remove(paths, i) + else + i = i + 1 + end + end + table.insert(paths, 1, lib) + package.path = table.concat(paths, package.pathsep) + + You can now write just: + + package.path = package.normalize('./lib/?.lua', package.path) + + @corelibrary std.package +]] + + +local _ = require 'std._base' + +local argscheck = _.typecheck and _.typecheck.argscheck +local catfile = _.io.catfile +local escape_pattern = _.string.escape_pattern +local invert = _.table.invert +local split = _.string.split + +_ = nil + +local _ENV = require 'std.normalize' { + 'package', + concat = 'table.concat', + dirsep = 'package.dirsep', + gsub = 'string.gsub', + merge = 'table.merge', + pathmark = 'package.pathmark', + pathsep = 'package.pathsep', + string_find = 'string.find', + table_insert = 'table.insert', + table_remove = 'table.remove', +} + + + +--[[ =============== ]]-- +--[[ Implementation. ]]-- +--[[ =============== ]]-- + + +--- Make named constants for `package.config` +-- (undocumented in 5.1; see luaconf.h for C equivalents). +-- @table package +-- @string dirsep directory separator +-- @string pathsep path separator +-- @string pathmark string that marks substitution points in a path template +-- @string execdir(Windows only) replaced by the executable's directory in a path +-- @string igmark Mark to ignore all before it when building `luaopen_` function name. + + +local function pathsub(path) + return gsub(path, '%%?.', function(capture) + if capture == '?' then + return pathmark + elseif capture == '/' then + return dirsep + else + return gsub(capture, '^%%', '', 1) + end + end) +end + + +local function find(pathstrings, patt, init, plain) + local paths = split(pathstrings, pathsep) + if plain then + patt = escape_pattern(patt) + end + init = init or 1 + if init < 0 then + init = #paths - init + end + for i = init, #paths do + if string_find(paths[i], patt) then + return i, paths[i] + end + end +end + + +local function normalize(...) + local i, paths, pathstrings = 1, {}, concat({...}, pathsep) + for _, path in ipairs(split(pathstrings, pathsep)) do + path = gsub(pathsub(path), catfile('^[^', ']'), catfile('.', '%0')) + path = gsub(path, catfile('', '%.', ''), dirsep) + path = gsub(path, catfile('', '%.$'), '') + path = gsub(path, catfile('^%.', '%..', ''), catfile('..', '')) + path = gsub(path, catfile('', '$'), '') + + -- Carefully remove redundant /foo/../ matches. + repeat + local again = false + path = gsub(path, catfile('', '([^', ']+)', '%.%.', ''), + function(dir1) + if dir1 == '..' then -- don't remove /../../ + return catfile('', '..', '..', '') + else + again = true + return dirsep + end + end) + path = gsub(path, catfile('', '([^', ']+)', '%.%.$'), + function(dir1) + if dir1 == '..' then -- don't remove /../.. + return catfile('', '..', '..') + else + again = true + return '' + end + end) + until again == false + + -- Build an inverted table of elements to eliminate duplicates after + -- normalization. + if not paths[path] then + paths[path], i = i, i + 1 + end + end + return concat(invert(paths), pathsep) +end + + +local function insert(pathstrings, ...) + local paths = split(pathstrings, pathsep) + table_insert(paths, ...) + return normalize(unpack(paths, 1, len(paths))) +end + + +local function mappath(pathstrings, callback, ...) + for _, path in ipairs(split(pathstrings, pathsep)) do + local r = callback(path, ...) + if r ~= nil then + return r + end + end +end + + +local function remove(pathstrings, pos) + local paths = split(pathstrings, pathsep) + table_remove(paths, pos) + return concat(paths, pathsep) +end + + + +--[[ ================= ]]-- +--[[ Public Interface. ]]-- +--[[ ================= ]]-- + + +local function X(decl, fn) + return argscheck and argscheck('std.package.' .. decl, fn) or fn +end + + +local M = { + --- Look for a path segment match of *patt* in *pathstrings*. + -- @function find + -- @string pathstrings `pathsep` delimited path elements + -- @string patt a Lua pattern to search for in *pathstrings* + -- @int[opt=1] init element(not byte index!) to start search at. + -- Negative numbers begin counting backwards from the last element + -- @bool[opt=false] plain unless false, treat *patt* as a plain + -- string, not a pattern. Note that if *plain* is given, then *init* + -- must be given as well. + -- @return the matching element number(not byte index!) and full text + -- of the matching element, if any; otherwise nil + -- @usage + -- i, s = find(package.path, '^[^' .. package.dirsep .. '/]') + find = X('find(string, string, ?int, ?boolean|:plain)', find), + + --- Insert a new element into a `package.path` like string of paths. + -- @function insert + -- @string pathstrings a `package.path` like string + -- @int[opt=n+1] pos element index at which to insert *value*, where `n` is + -- the number of elements prior to insertion + -- @string value new path element to insert + -- @treturn string a new string with the new element inserted + -- @usage + -- package.path = insert(package.path, 1, install_dir .. '/?.lua') + insert = X('insert(string, [int], string)', insert), + + --- Call a function with each element of a path string. + -- @function mappath + -- @string pathstrings a `package.path` like string + -- @tparam mappathcb callback function to call for each element + -- @param ... additional arguments passed to *callback* + -- @return nil, or first non-nil returned by *callback* + -- @usage + -- mappath(package.path, searcherfn, transformfn) + mappath = X('mappath(string, function, [any...])', mappath), + + --- Normalize a path list. + -- Removing redundant `.` and `..` directories, and keep only the first + -- instance of duplicate elements. Each argument can contain any number + -- of `pathsep` delimited elements; wherein characters are subject to + -- `/` and `?` normalization, converting `/` to `dirsep` and `?` to + -- `pathmark`(unless immediately preceded by a `%` character). + -- @function normalize + -- @param ... path elements + -- @treturn string a single normalized `pathsep` delimited paths string + -- @usage + -- package.path = normalize(user_paths, sys_paths, package.path) + normalize = X('normalize(string...)', normalize), + + --- Remove any element from a `package.path` like string of paths. + -- @function remove + -- @string pathstrings a `package.path` like string + -- @int[opt=n] pos element index from which to remove an item, where `n` + -- is the number of elements prior to removal + -- @treturn string a new string with given element removed + -- @usage + -- package.path = remove(package.path) + remove = X('remove(string, ?int)', remove), +} + + +return merge(package, M) + + +--- Types +-- @section Types + +--- Function signature of a callback for @{mappath}. +-- @function mappathcb +-- @string element an element from a `pathsep` delimited string of +-- paths +-- @param ... additional arguments propagated from @{mappath} +-- @return non-nil to break, otherwise continue with the next element diff --git a/Data/BuiltIn/Libraries/lua-stdlib/lib/std/string.lua b/Data/BuiltIn/Libraries/lua-stdlib/lib/std/string.lua new file mode 100644 index 0000000..6ad9014 --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-stdlib/lib/std/string.lua @@ -0,0 +1,505 @@ +--[[ + General Lua Libraries for Lua 5.1, 5.2 & 5.3 + Copyright (C) 2002-2018 stdlib authors +]] +--[[-- + Additions to the core string module. + + The module table returned by `std.string` also contains all of the entries + from the core string table. An hygienic way to import this module, then, is + simply to override the core `string` locally: + + local string = require 'std.string' + + @corelibrary std.string +]] + + +local _ = require 'std._base' + +local argscheck = _.typecheck and _.std.typecheck.argscheck +local escape_pattern = _.string.escape_pattern +local split = _.string.split + +_ = nil + + +local _ENV = require 'std.normalize' { + 'string', + abs = 'math.abs', + concat = 'table.concat', + find = 'string.find', + floor = 'math.floor', + format = 'string.format', + gsub = 'string.gsub', + insert = 'table.insert', + match = 'string.match', + merge = 'table.merge', + render = 'string.render', + sort = 'table.sort', + sub = 'string.sub', + upper = 'string.upper', +} + + + +--[[ =============== ]]-- +--[[ Implementation. ]]-- +--[[ =============== ]]-- + + +local M + + +local function toqstring(x, xstr) + if type(x) ~= 'string' then + return xstr + end + return format('%q', x) +end + + +local concatvfns = { + elem = tostring, + term = function(x) + return type(x) ~= 'table' or getmetamethod(x, '__tostring') + end, + sort = function(keys) + return keys + end, + open = function(x) return '{' end, + close = function(x) return '}' end, + pair = function(x, kp, vp, k, v, kstr, vstr, seqp) + return toqstring(k, kstr) .. '=' .. toqstring(v, vstr) + end, + sep = function(x, kp, vp, kn, vn, seqp) + return kp ~= nil and kn ~= nil and ',' or '' + end, +} + + +local function __concat(s, o) + -- Don't use normalize.str here, because we don't want ASCII escape rendering. + return render(s, concatvfns) .. render(o, concatvfns) +end + + +local function __index(s, i) + if type(i) == 'number' then + return sub(s, i, i) + else + -- Fall back to module metamethods + return M[i] + end +end + + +local _format = string.format + +local function format(f, arg1, ...) + return(arg1 ~= nil) and _format(f, arg1, ...) or f +end + + +local function tpack(from, to, ...) + return from, to, {...} +end + +local function tfind(s, ...) + return tpack(find(s, ...)) +end + + +local function finds(s, p, i, ...) + i = i or 1 + local l = {} + local from, to, r + repeat + from, to, r = tfind(s, p, i, ...) + if from ~= nil then + insert(l, {from, to, capt=r}) + i = to + 1 + end + until not from + return l +end + + +local function caps(s) + return(gsub(s, '(%w)([%w]*)', function(l, ls) + return upper(l) .. ls + end)) +end + + +local function escape_shell(s) + return(gsub(s, '([ %(%)%\\%[%]\'"])', '\\%1')) +end + + +local function ordinal_suffix(n) + n = abs(n) % 100 + local d = n % 10 + if d == 1 and n ~= 11 then + return 'st' + elseif d == 2 and n ~= 12 then + return 'nd' + elseif d == 3 and n ~= 13 then + return 'rd' + else + return 'th' + end +end + + +local function pad(s, w, p) + p = string.rep(p or ' ', abs(w)) + if w < 0 then + return string.sub(p .. s, w) + end + return string.sub(s .. p, 1, w) +end + + +local function wrap(s, w, ind, ind1) + w = w or 78 + ind = ind or 0 + ind1 = ind1 or ind + assert(ind1 < w and ind < w, + 'the indents must be less than the line width') + local r = {string.rep(' ', ind1)} + local i, lstart, lens = 1, ind1, len(s) + while i <= lens do + local j = i + w - lstart + while len(s[j]) > 0 and s[j] ~= ' ' and j > i do + j = j - 1 + end + local ni = j + 1 + while s[j] == ' ' do + j = j - 1 + end + insert(r, sub(s, i, j)) + i = ni + if i < lens then + insert(r, '\n' .. string.rep(' ', ind)) + lstart = ind + end + end + return concat(r) +end + + +local function numbertosi(n) + local SIprefix = { + [-8]='y', [-7]='z', [-6]='a', [-5]='f', + [-4]='p', [-3]='n', [-2]='mu', [-1]='m', + [0]='', [1]='k', [2]='M', [3]='G', + [4]='T', [5]='P', [6]='E', [7]='Z', + [8]='Y' + } + local t = _format('% #.2e', n) + local _, _, m, e = find(t, '.(.%...)e(.+)') + local man, exp = tonumber(m), tonumber(e) + local siexp = floor(exp / 3) + local shift = exp - siexp * 3 + local s = SIprefix[siexp] or 'e' .. tostring(siexp) + man = man *(10 ^ shift) + return _format('%0.f', man) .. s +end + + +-- Ordor numbers first then asciibetically. +local function keycmp(a, b) + if type(a) == 'number' then + return type(b) ~= 'number' or a < b + end + return type(b) ~= 'number' and tostring(a) < tostring(b) +end + + +local render_fallbacks = { + __index = concatvfns, +} + + +local function prettytostring(x, indent, spacing) + indent = indent or '\t' + spacing = spacing or '' + return render(x, setmetatable({ + elem = function(x) + if type(x) ~= 'string' then + return tostring(x) + end + return format('%q', x) + end, + + sort = function(keylist) + sort(keylist, keycmp) + return keylist + end, + + open = function() + local s = spacing .. '{' + spacing = spacing .. indent + return s + end, + + close = function() + spacing = string.gsub(spacing, indent .. '$', '') + return spacing .. '}' + end, + + pair = function(x, _, _, k, v, kstr, vstr) + local type_k = type(k) + local s = spacing + if type_k ~= 'string' or match(k, '[^%w_]') then + s = s .. '[' + if type_k == 'table' then + s = s .. '\n' + end + s = s .. kstr + if type_k == 'table' then + s = s .. '\n' + end + s = s .. ']' + else + s = s .. k + end + s = s .. ' =' + if type(v) == 'table' then + s = s .. '\n' + else + s = s .. ' ' + end + s = s .. vstr + return s + end, + + sep = function(_, k) + local s = '\n' + if k then + s = ',' .. s + end + return s + end, + }, render_fallbacks)) +end + + +local function trim(s, r) + r = r or '%s+' + return (gsub(gsub(s, '^' .. r, ''), r .. '$', '')) +end + + + +--[[ ================= ]]-- +--[[ Public Interface. ]]-- +--[[ ================= ]]-- + + +local function X(decl, fn) + return argscheck and argscheck('std.string.' .. decl, fn) or fn +end + +M = { + --- Metamethods + -- @section metamethods + + --- String concatenation operation. + -- @function __concat + -- @string s initial string + -- @param o object to stringify and concatenate + -- @return s .. tostring(o) + -- @usage + -- local string = setmetatable('', require 'std.string') + -- concatenated = 'foo' .. {'bar'} + __concat = __concat, + + --- String subscript operation. + -- @function __index + -- @string s string + -- @tparam int|string i index or method name + -- @return `sub(s, i, i)` if i is a number, otherwise + -- fall back to a `std.string` metamethod(if any). + -- @usage + -- getmetatable('').__index = require 'std.string'.__index + -- third =('12345')[3] + __index = __index, + + + --- Core Functions + -- @section corefuncs + + --- Capitalise each word in a string. + -- @function caps + -- @string s any string + -- @treturn string *s* with each word capitalized + -- @usage + -- userfullname = caps(input_string) + caps = X('caps(string)', caps), + + --- Remove any final newline from a string. + -- @function chomp + -- @string s any string + -- @treturn string *s* with any single trailing newline removed + -- @usage + -- line = chomp(line) + chomp = X('chomp(string)', function(s) + return(gsub(s, '\n$', '')) + end), + + --- Escape a string to be used as a pattern. + -- @function escape_pattern + -- @string s any string + -- @treturn string *s* with active pattern characters escaped + -- @usage + -- substr = match(inputstr, escape_pattern(literal)) + escape_pattern = X('escape_pattern(string)', escape_pattern), + + --- Escape a string to be used as a shell token. + -- Quotes spaces, parentheses, brackets, quotes, apostrophes and + -- whitespace. + -- @function escape_shell + -- @string s any string + -- @treturn string *s* with active shell characters escaped + -- @usage + -- os.execute('echo ' .. escape_shell(outputstr)) + escape_shell = X('escape_shell(string)', escape_shell), + + --- Repeatedly `string.find` until target string is exhausted. + -- @function finds + -- @string s target string + -- @string pattern pattern to match in *s* + -- @int[opt=1] init start position + -- @bool[opt] plain inhibit magic characters + -- @return list of `{from, to; capt={captures}}` + -- @see std.string.tfind + -- @usage + -- for t in std.elems(finds('the target string', '%S+')) do + -- print(tostring(t.capt)) + -- end + finds = X('finds(string, string, ?int, ?boolean|:plain)', finds), + + --- Extend to work better with one argument. + -- If only one argument is passed, no formatting is attempted. + -- @function format + -- @string f format string + -- @param[opt] ... arguments to format + -- @return formatted string + -- @usage + -- print(format '100% stdlib!') + format = X('format(string, [any...])', format), + + --- Remove leading matter from a string. + -- @function ltrim + -- @string s any string + -- @string[opt='%s+'] r leading pattern + -- @treturn string *s* with leading *r* stripped + -- @usage + -- print('got: ' .. ltrim(userinput)) + ltrim = X('ltrim(string, ?string)', function(s, r) + return (gsub(s, '^' ..(r or '%s+'), '')) + end), + + --- Write a number using SI suffixes. + -- The number is always written to 3 s.f. + -- @function numbertosi + -- @tparam number|string n any numeric value + -- @treturn string *n* simplifed using largest available SI suffix. + -- @usage + -- print(numbertosi(bitspersecond) .. 'bps') + numbertosi = X('numbertosi(number|string)', numbertosi), + + --- Return the English suffix for an ordinal. + -- @function ordinal_suffix + -- @tparam int|string n any integer value + -- @treturn string English suffix for *n* + -- @usage + -- local now = os.date '*t' + -- print('%d%s day of the week', now.day, ordinal_suffix(now.day)) + ordinal_suffix = X('ordinal_suffix(int|string)', ordinal_suffix), + + --- Justify a string. + -- When the string is longer than w, it is truncated(left or right + -- according to the sign of w). + -- @function pad + -- @string s a string to justify + -- @int w width to justify to(-ve means right-justify; +ve means + -- left-justify) + -- @string[opt=' '] p string to pad with + -- @treturn string *s* justified to *w* characters wide + -- @usage + -- print(pad(trim(outputstr, 78)) .. '\n') + pad = X('pad(string, int, ?string)', pad), + + --- Pretty-print a table, or other object. + -- @function prettytostring + -- @param x object to convert to string + -- @string[opt='\t'] indent indent between levels + -- @string[opt=''] spacing space before every line + -- @treturn string pretty string rendering of *x* + -- @usage + -- print(prettytostring(std, ' ')) + prettytostring = X('prettytostring(?any, ?string, ?string)', prettytostring), + + --- Remove trailing matter from a string. + -- @function rtrim + -- @string s any string + -- @string[opt='%s+'] r trailing pattern + -- @treturn string *s* with trailing *r* stripped + -- @usage + -- print('got: ' .. rtrim(userinput)) + rtrim = X('rtrim(string, ?string)', function(s, r) + return (gsub(s, (r or '%s+') .. '$', '')) + end), + + --- Split a string at a given separator. + -- Separator is a Lua pattern, so you have to escape active characters, + -- `^$()%.[]*+-?` with a `%` prefix to match a literal character in *s*. + -- @function split + -- @string s to split + -- @string[opt='%s+'] sep separator pattern + -- @return list of strings + -- @usage + -- words = split 'a very short sentence' + split = X('split(string, ?string)', split), + + --- Do `string.find`, returning a table of captures. + -- @function tfind + -- @string s target string + -- @string pattern pattern to match in *s* + -- @int[opt=1] init start position + -- @bool[opt] plain inhibit magic characters + -- @treturn int start of match + -- @treturn int end of match + -- @treturn table list of captured strings + -- @see std.string.finds + -- @usage + -- b, e, captures = tfind('the target string', '%s', 10) + tfind = X('tfind(string, string, ?int, ?boolean|:plain)', tfind), + + --- Remove leading and trailing matter from a string. + -- @function trim + -- @string s any string + -- @string[opt='%s+'] r trailing pattern + -- @treturn string *s* with leading and trailing *r* stripped + -- @usage + -- print('got: ' .. trim(userinput)) + trim = X('trim(string, ?string)', trim), + + --- Wrap a string into a paragraph. + -- @function wrap + -- @string s a paragraph of text + -- @int[opt=78] w width to wrap to + -- @int[opt=0] ind indent + -- @int[opt=ind] ind1 indent of first line + -- @treturn string *s* wrapped to *w* columns + -- @usage + -- print(wrap(copyright, 72, 4)) + wrap = X('wrap(string, ?int, ?int, ?int)', wrap), +} + + +return merge(string, M) + diff --git a/Data/BuiltIn/Libraries/lua-stdlib/lib/std/table.lua b/Data/BuiltIn/Libraries/lua-stdlib/lib/std/table.lua new file mode 100644 index 0000000..7bda608 --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-stdlib/lib/std/table.lua @@ -0,0 +1,439 @@ +--[[ + General Lua Libraries for Lua 5.1, 5.2 & 5.3 + Copyright (C) 2002-2018 stdlib authors +]] +--[[-- + Extensions to the core table module. + + The module table returned by `std.table` also contains all of the entries from + the core table module. An hygienic way to import this module, then, is simply + to override the core `table` locally: + + local table = require 'std.table' + + @corelibrary std.table +]] + + +local _ = require 'std._base' + +local argscheck = _.typecheck and _.typecheck.argscheck +local invert = _.table.invert +local maxn = _.table.maxn + +_ = nil + +local _ENV = require 'std.normalize' { + 'table', + merge = 'table.merge', + min = 'math.min', +} + + + +--[[ =============== ]]-- +--[[ Implementation. ]]-- +--[[ =============== ]]-- + + +local M + + +local function merge_allfields(t, u, map, nometa) + if type(map) ~= 'table' then + map, nometa = nil, map + end + + if not nometa then + setmetatable(t, getmetatable(u)) + end + if map then + for k, v in pairs(u) do + t[map[k] or k] = v + end + else + for k, v in pairs(u) do + t[k] = v + end + end + return t +end + + +local function merge_namedfields(t, u, keys, nometa) + if type(keys) ~= 'table' then + keys, nometa = nil, keys + end + + if not nometa then + setmetatable(t, getmetatable(u)) + end + for _, k in pairs(keys or {}) do + t[k] = u[k] + end + return t +end + + +local function depair(ls) + local t = {} + for _, v in ipairs(ls) do + t[v[1]] = v[2] + end + return t +end + + +local function enpair(t) + local tt = {} + for i, v in pairs(t) do + tt[#tt + 1] = {i, v} + end + return tt +end + + +local _insert = table.insert + +local function insert(t, pos, v) + if v == nil then + pos, v = len(t) + 1, pos + end + if pos < 1 or pos > len(t) + 1 then + argerror('std.table.insert', 2, 'position ' .. pos .. ' out of bounds', 2) + end + _insert(t, pos, v) + return t +end + + +local function keys(t) + local l = {} + for k in pairs(t) do + l[#l + 1] = k + end + return l +end + + +local function new(x, t) + return setmetatable(t or {}, {__index = function(t, i) + return x + end}) +end + + +local function project(fkey, tt) + local r = {} + for _, t in ipairs(tt) do + r[#r + 1] = t[fkey] + end + return r +end + + +local function size(t) + local n = 0 + for _ in pairs(t) do + n = n + 1 + end + return n +end + + +-- Preserve core table sort function. +local _sort = table.sort + +local function sort(t, c) + _sort(t, c) + return t +end + + +local _remove = table.remove + +local function remove(t, pos) + local lent = len(t) + pos = pos or lent + if pos < min(1, lent) or pos > lent + 1 then -- +1? whu? that's what 5.2.3 does!?! + argerror('std.table.remove', 2, 'position ' .. pos .. ' out of bounds', 2) + end + return _remove(t, pos) +end + + +local _unpack = unpack + +local function unpack(t, i, j) + if j == nil then + -- if j was not given, respect __len, otherwise use maxn + local m = getmetamethod(t, '__len') + j = m and m(t) or maxn(t) + end + return _unpack(t, tonumber(i) or 1, tonumber(j)) +end + + +local function values(t) + local l = {} + for _, v in pairs(t) do + l[#l + 1] = v + end + return l +end + + + +--[[ ================= ]]-- +--[[ Public Interface. ]]-- +--[[ ================= ]]-- + + +local function X(decl, fn) + return argscheck and argscheck('std.table.' .. decl, fn) or fn +end + +M = { + --- Core Functions + -- @section corefuncs + + --- Enhance core *table.insert* to return its result. + -- If *pos* is not given, respect `__len` metamethod when calculating + -- default append. Also, diagnose out of bounds *pos* arguments + -- consistently on any supported version of Lua. + -- @function insert + -- @tparam table t a table + -- @int[opt=len(t)] pos index at which to insert new element + -- @param v value to insert into *t* + -- @treturn table *t* + -- @usage + -- --> {1, 'x', 2, 3, 'y'} + -- insert(insert({1, 2, 3}, 2, 'x'), 'y') + insert = X('insert(table, [int], any)', insert), + + --- Largest integer key in a table. + -- @function maxn + -- @tparam table t a table + -- @treturn int largest integer key in *t* + -- @usage + -- --> 42 + -- maxn {'a', b='c', 99, [42]='x', 'x', [5]=67} + maxn = X('maxn(table)', maxn), + + --- Turn a tuple into a list, with tuple-size in field `n` + -- @function pack + -- @param ... tuple + -- @return list-like table, with tuple-size in field `n` + -- @usage + -- --> {1, 2, 'ax', n=3} + -- pack(find('ax1', '(%D+)')) + pack = pack, + + --- Enhance core *table.remove* to respect `__len` when *pos* is omitted. + -- Also, diagnose out of bounds *pos* arguments consistently on any supported + -- version of Lua. + -- @function remove + -- @tparam table t a table + -- @int[opt=len(t)] pos index from which to remove an element + -- @return removed value, or else `nil` + -- @usage + -- --> {1, 2, 5} + -- t = {1, 2, 'x', 5} + -- remove(t, 3) == 'x' and t + remove = X('remove(table, ?int)', remove), + + --- Enhance core *table.sort* to return its result. + -- @function sort + -- @tparam table t unsorted table + -- @tparam[opt=std.operator.lt] comparator c ordering function callback + -- @return *t* with keys sorted according to *c* + -- @usage + -- table.concat(sort(object)) + sort = X('sort(table, ?function)', sort), + + --- Enhance core *table.unpack* to always unpack up to __len or maxn. + -- @function unpack + -- @tparam table t table to act on + -- @int[opt=1] i first index to unpack + -- @int[opt=table.maxn(t)] j last index to unpack + -- @return ... values of numeric indices of *t* + -- @usage + -- return unpack(results_table) + unpack = X('unpack(table, ?int, ?int)', unpack), + + + --- Accessor Functions + -- @section accessorfuncs + + --- Make a shallow copy of a table, including any metatable. + -- @function clone + -- @tparam table t source table + -- @tparam[opt={}] table map table of `{old_key=new_key, ...}` + -- @bool[opt] nometa if non-nil don't copy metatable + -- @return copy of *t*, also sharing *t*'s metatable unless *nometa* + -- is true, and with keys renamed according to *map* + -- @see merge + -- @see clone_select + -- @usage + -- shallowcopy = clone(original, {rename_this='to_this'}, ':nometa') + clone = X('clone(table, [table], ?boolean|:nometa)', function(...) + return merge_allfields({}, ...) + end), + + --- Make a partial clone of a table. + -- + -- Like `clone`, but does not copy any fields by default. + -- @function clone_select + -- @tparam table t source table + -- @tparam[opt={}] table keys list of keys to copy + -- @bool[opt] nometa if non-nil don't copy metatable + -- @treturn table copy of fields in *selection* from *t*, also sharing *t*'s + -- metatable unless *nometa* + -- @see clone + -- @see merge_select + -- @usage + -- partialcopy = clone_select(original, {'this', 'and_this'}, true) + clone_select = X('clone_select(table, [table], ?boolean|:nometa)', function(...) + return merge_namedfields({}, ...) + end), + + --- Turn a list of pairs into a table. + -- @todo Find a better name. + -- @function depair + -- @tparam table ls list of lists + -- @treturn table a flat table with keys and values from *ls* + -- @see enpair + -- @usage + -- --> {a=1, b=2, c=3} + -- depair {{'a', 1}, {'b', 2}, {'c', 3}} + depair = X('depair(list of lists)', depair), + + --- Turn a table into a list of pairs. + -- @todo Find a better name. + -- @function enpair + -- @tparam table t a table `{i1=v1, ..., in=vn}` + -- @treturn table a new list of pairs containing `{{i1, v1}, ..., {in, vn}}` + -- @see depair + -- @usage + -- --> {{1, 'a'}, {2, 'b'}, {3, 'c'}} + -- enpair {'a', 'b', 'c'} + enpair = X('enpair(table)', enpair), + + --- Return whether table is empty. + -- @function empty + -- @tparam table t any table + -- @treturn boolean `true` if *t* is empty, otherwise `false` + -- @usage + -- if empty(t) then error 'ohnoes' end + empty = X('empty(table)', function(t) + return not next(t) + end), + + --- Make a table with a default value for unset keys. + -- @function new + -- @param[opt=nil] x default entry value + -- @tparam[opt={}] table t initial table + -- @treturn table table whose unset elements are *x* + -- @usage + -- t = new(0) + new = X('new(?any, ?table)', new), + + --- Project a list of fields from a list of tables. + -- @function project + -- @param fkey field to project + -- @tparam table tt a list of tables + -- @treturn table list of *fkey* fields from *tt* + -- @usage + -- --> {1, 3, 'yy'} + -- project('xx', {{'a', xx=1, yy='z'}, {'b', yy=2}, {'c', xx=3}, {xx='yy'}) + project = X('project(any, list of tables)', project), + + --- Find the number of elements in a table. + -- @function size + -- @tparam table t any table + -- @treturn int number of non-nil values in *t* + -- @usage + -- --> 3 + -- size {foo=true, bar=true, baz=false} + size = X('size(table)', size), + + --- Make the list of values of a table. + -- @function values + -- @tparam table t any table + -- @treturn table list of values in *t* + -- @see keys + -- @usage + -- --> {'a', 'c', 42} + -- values {'a', b='c', [-1]=42} + values = X('values(table)', values), + + + --- Mutator Functions + -- @section mutatorfuncs + + --- Invert a table. + -- @function invert + -- @tparam table t a table with `{k=v, ...}` + -- @treturn table inverted table `{v=k, ...}` + -- @usage + -- --> {a=1, b=2, c=3} + -- invert {'a', 'b', 'c'} + invert = X('invert(table)', invert), + + --- Make the list of keys in table. + -- @function keys + -- @tparam table t a table + -- @treturn table list of keys from *t* + -- @see values + -- @usage + -- globals = keys(_G) + keys = X('keys(table)', keys), + + --- Destructively merge one table's fields into another. + -- @function merge + -- @tparam table t destination table + -- @tparam table u table with fields to merge + -- @tparam[opt={}] table map table of `{old_key=new_key, ...}` + -- @bool[opt] nometa if `true` or ':nometa' don't copy metatable + -- @treturn table *t* with fields from *u* merged in + -- @see clone + -- @see merge_select + -- @usage + -- merge(_G, require 'std.debug', {say='log'}, ':nometa') + merge = X('merge(table, table, [table], ?boolean|:nometa)', merge_allfields), + + --- Destructively merge another table's named fields into *table*. + -- + -- Like `merge`, but does not merge any fields by default. + -- @function merge_select + -- @tparam table t destination table + -- @tparam table u table with fields to merge + -- @tparam[opt={}] table keys list of keys to copy + -- @bool[opt] nometa if `true` or ':nometa' don't copy metatable + -- @treturn table copy of fields in *selection* from *t*, also sharing *t*'s + -- metatable unless *nometa* + -- @see merge + -- @see clone_select + -- @usage + -- merge_select(_G, require 'std.debug', {'say'}, false) + merge_select = X('merge_select(table, table, [table], ?boolean|:nometa)', + merge_namedfields), +} + + +return merge(table, M) + + + +--- Types +-- @section Types + +--- Signature of a @{sort} comparator function. +-- @function comparator +-- @param a any object +-- @param b any object +-- @treturn boolean `true` if *a* sorts before *b*, otherwise `false` +-- @see sort +-- @usage +-- local reversor = function(a, b) return a > b end +-- sort(t, reversor) diff --git a/Data/BuiltIn/Libraries/lua-stdlib/math.lua b/Data/BuiltIn/Libraries/lua-stdlib/math.lua new file mode 100644 index 0000000..d955862 --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-stdlib/math.lua @@ -0,0 +1,92 @@ +--[[ + General Lua Libraries for Lua 5.1, 5.2 & 5.3 + Copyright (C) 2002-2018 stdlib authors +]] +--[[-- + Additions to the core math module. + + The module table returned by `std.math` also contains all of the entries from + the core math table. An hygienic way to import this module, then, is simply + to override the core `math` locally: + + local math = require 'std.math' + + @corelibrary std.math +]] + + +local _ = require 'std._base' + +local argscheck = _.typecheck and _.typecheck.argscheck + +_ = nil + + +local _ENV = require 'std.normalize' { + 'math', + merge = 'table.merge', +} + + + +--[[ ================= ]]-- +--[[ Implementatation. ]]-- +--[[ ================= ]]-- + + +local M + + +local _floor = math.floor + +local function floor(n, p) + if(p or 0) == 0 then + return _floor(n) + end + local e = 10 ^ p + return _floor(n * e) / e +end + + +local function round(n, p) + local e = 10 ^(p or 0) + return _floor(n * e + 0.5) / e +end + + + +--[[ ================= ]]-- +--[[ Public Interface. ]]-- +--[[ ================= ]]-- + + +local function X(decl, fn) + return argscheck and argscheck('std.math.' .. decl, fn) or fn +end + + +M = { + --- Core Functions + -- @section corefuncs + + --- Extend `math.floor` to take the number of decimal places. + -- @function floor + -- @number n number + -- @int[opt=0] p number of decimal places to truncate to + -- @treturn number `n` truncated to `p` decimal places + -- @usage + -- tenths = floor(magnitude, 1) + floor = X('floor(number, ?int)', floor), + + --- Round a number to a given number of decimal places. + -- @function round + -- @number n number + -- @int[opt=0] p number of decimal places to round to + -- @treturn number `n` rounded to `p` decimal places + -- @usage + -- roughly = round(exactly, 2) + round = X('round(number, ?int)', round), +} + + +return merge(math, M) diff --git a/Data/BuiltIn/Libraries/lua-stdlib/package.lua b/Data/BuiltIn/Libraries/lua-stdlib/package.lua new file mode 100644 index 0000000..e3e8243 --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-stdlib/package.lua @@ -0,0 +1,263 @@ +--[[ + General Lua Libraries for Lua 5.1, 5.2 & 5.3 + Copyright (C) 2002-2018 stdlib authors +]] +--[[-- + Additions to the core package module. + + The module table returned by `std.package` also contains all of the entries + from the core `package` table. An hygienic way to import this module, then, is + simply to override core `package` locally: + + local package = require 'std.package' + + Manage `package.path` with normalization, duplicate removal, + insertion & removal of elements and automatic folding of '/' and '?' + onto `package.dirsep` and `package.pathmark`, for easy addition of + new paths. For example, instead of all this: + + lib = std.io.catfile('.', 'lib', package.pathmark .. '.lua') + paths = std.string.split(package.path, package.pathsep) + for i, path in ipairs(paths) do + -- ... lots of normalization code... + end + i = 1 + while i <= #paths do + if paths[i] == lib then + table.remove(paths, i) + else + i = i + 1 + end + end + table.insert(paths, 1, lib) + package.path = table.concat(paths, package.pathsep) + + You can now write just: + + package.path = package.normalize('./lib/?.lua', package.path) + + @corelibrary std.package +]] + + +local _ = require 'std._base' + +local argscheck = _.typecheck and _.typecheck.argscheck +local catfile = _.io.catfile +local escape_pattern = _.string.escape_pattern +local invert = _.table.invert +local split = _.string.split + +_ = nil + +local _ENV = require 'std.normalize' { + 'package', + concat = 'table.concat', + dirsep = 'package.dirsep', + gsub = 'string.gsub', + merge = 'table.merge', + pathmark = 'package.pathmark', + pathsep = 'package.pathsep', + string_find = 'string.find', + table_insert = 'table.insert', + table_remove = 'table.remove', +} + + + +--[[ =============== ]]-- +--[[ Implementation. ]]-- +--[[ =============== ]]-- + + +--- Make named constants for `package.config` +-- (undocumented in 5.1; see luaconf.h for C equivalents). +-- @table package +-- @string dirsep directory separator +-- @string pathsep path separator +-- @string pathmark string that marks substitution points in a path template +-- @string execdir(Windows only) replaced by the executable's directory in a path +-- @string igmark Mark to ignore all before it when building `luaopen_` function name. + + +local function pathsub(path) + return gsub(path, '%%?.', function(capture) + if capture == '?' then + return pathmark + elseif capture == '/' then + return dirsep + else + return gsub(capture, '^%%', '', 1) + end + end) +end + + +local function find(pathstrings, patt, init, plain) + local paths = split(pathstrings, pathsep) + if plain then + patt = escape_pattern(patt) + end + init = init or 1 + if init < 0 then + init = #paths - init + end + for i = init, #paths do + if string_find(paths[i], patt) then + return i, paths[i] + end + end +end + + +local function normalize(...) + local i, paths, pathstrings = 1, {}, concat({...}, pathsep) + for _, path in ipairs(split(pathstrings, pathsep)) do + path = gsub(pathsub(path), catfile('^[^', ']'), catfile('.', '%0')) + path = gsub(path, catfile('', '%.', ''), dirsep) + path = gsub(path, catfile('', '%.$'), '') + path = gsub(path, catfile('^%.', '%..', ''), catfile('..', '')) + path = gsub(path, catfile('', '$'), '') + + -- Carefully remove redundant /foo/../ matches. + repeat + local again = false + path = gsub(path, catfile('', '([^', ']+)', '%.%.', ''), + function(dir1) + if dir1 == '..' then -- don't remove /../../ + return catfile('', '..', '..', '') + else + again = true + return dirsep + end + end) + path = gsub(path, catfile('', '([^', ']+)', '%.%.$'), + function(dir1) + if dir1 == '..' then -- don't remove /../.. + return catfile('', '..', '..') + else + again = true + return '' + end + end) + until again == false + + -- Build an inverted table of elements to eliminate duplicates after + -- normalization. + if not paths[path] then + paths[path], i = i, i + 1 + end + end + return concat(invert(paths), pathsep) +end + + +local function insert(pathstrings, ...) + local paths = split(pathstrings, pathsep) + table_insert(paths, ...) + return normalize(unpack(paths, 1, len(paths))) +end + + +local function mappath(pathstrings, callback, ...) + for _, path in ipairs(split(pathstrings, pathsep)) do + local r = callback(path, ...) + if r ~= nil then + return r + end + end +end + + +local function remove(pathstrings, pos) + local paths = split(pathstrings, pathsep) + table_remove(paths, pos) + return concat(paths, pathsep) +end + + + +--[[ ================= ]]-- +--[[ Public Interface. ]]-- +--[[ ================= ]]-- + + +local function X(decl, fn) + return argscheck and argscheck('std.package.' .. decl, fn) or fn +end + + +local M = { + --- Look for a path segment match of *patt* in *pathstrings*. + -- @function find + -- @string pathstrings `pathsep` delimited path elements + -- @string patt a Lua pattern to search for in *pathstrings* + -- @int[opt=1] init element(not byte index!) to start search at. + -- Negative numbers begin counting backwards from the last element + -- @bool[opt=false] plain unless false, treat *patt* as a plain + -- string, not a pattern. Note that if *plain* is given, then *init* + -- must be given as well. + -- @return the matching element number(not byte index!) and full text + -- of the matching element, if any; otherwise nil + -- @usage + -- i, s = find(package.path, '^[^' .. package.dirsep .. '/]') + find = X('find(string, string, ?int, ?boolean|:plain)', find), + + --- Insert a new element into a `package.path` like string of paths. + -- @function insert + -- @string pathstrings a `package.path` like string + -- @int[opt=n+1] pos element index at which to insert *value*, where `n` is + -- the number of elements prior to insertion + -- @string value new path element to insert + -- @treturn string a new string with the new element inserted + -- @usage + -- package.path = insert(package.path, 1, install_dir .. '/?.lua') + insert = X('insert(string, [int], string)', insert), + + --- Call a function with each element of a path string. + -- @function mappath + -- @string pathstrings a `package.path` like string + -- @tparam mappathcb callback function to call for each element + -- @param ... additional arguments passed to *callback* + -- @return nil, or first non-nil returned by *callback* + -- @usage + -- mappath(package.path, searcherfn, transformfn) + mappath = X('mappath(string, function, [any...])', mappath), + + --- Normalize a path list. + -- Removing redundant `.` and `..` directories, and keep only the first + -- instance of duplicate elements. Each argument can contain any number + -- of `pathsep` delimited elements; wherein characters are subject to + -- `/` and `?` normalization, converting `/` to `dirsep` and `?` to + -- `pathmark`(unless immediately preceded by a `%` character). + -- @function normalize + -- @param ... path elements + -- @treturn string a single normalized `pathsep` delimited paths string + -- @usage + -- package.path = normalize(user_paths, sys_paths, package.path) + normalize = X('normalize(string...)', normalize), + + --- Remove any element from a `package.path` like string of paths. + -- @function remove + -- @string pathstrings a `package.path` like string + -- @int[opt=n] pos element index from which to remove an item, where `n` + -- is the number of elements prior to removal + -- @treturn string a new string with given element removed + -- @usage + -- package.path = remove(package.path) + remove = X('remove(string, ?int)', remove), +} + + +return merge(package, M) + + +--- Types +-- @section Types + +--- Function signature of a callback for @{mappath}. +-- @function mappathcb +-- @string element an element from a `pathsep` delimited string of +-- paths +-- @param ... additional arguments propagated from @{mappath} +-- @return non-nil to break, otherwise continue with the next element diff --git a/Data/BuiltIn/Libraries/lua-stdlib/spec/debug_spec.yaml b/Data/BuiltIn/Libraries/lua-stdlib/spec/debug_spec.yaml new file mode 100644 index 0000000..5102723 --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-stdlib/spec/debug_spec.yaml @@ -0,0 +1,222 @@ +# General Lua Libraries for Lua 5.1, 5.2 & 5.3 +# Copyright (C) 2011-2018 stdlib authors + +before: | + base_module = 'debug' + this_module = 'std.debug' + global_table = '_G' + + extend_base = {'getfenv', 'setfenv', 'say', 'trace'} + + M = require(this_module) + + +specify std.debug: +- context when required: + - context by name: + - it does not touch the global table: + expect(show_apis {added_to=global_table, by=this_module}). + to_equal {} + - it does not touch the core debug table: + expect(show_apis {added_to=base_module, by=this_module}). + to_equal {} + - it contains apis from the core debug table: + expect(show_apis {from=base_module, not_in=this_module}). + to_contain.a_permutation_of(extend_base) + + - context via the std module: + - it does not touch the global table: + expect(show_apis {added_to=global_table, by='std'}). + to_equal {} + - it does not touch the core debug table: + expect(show_apis {added_to=base_module, by='std'}). + to_equal {} + + +- describe debug: + - it does nothing when std._debug is disabled: + expect(luaproc [[ + require 'std._debug'(false) + require 'std.debug'('nothing to see here') + ]]).not_to_contain_error 'nothing to see here' + - it writes to stderr when std._debug is not set: + expect(luaproc [[ + require 'std.debug'('debugging') + ]]).to_contain_error 'debugging' + - it writes to stderr when std._debug is enabled: + expect(luaproc [[ + require 'std._debug'(true) + require 'std.debug'('debugging') + ]]).to_contain_error 'debugging' + - it writes to stderr when std._debug.level is not set: + expect(luaproc [[ + require 'std._debug'() + require 'std.debug'('debugging') + ]]).to_contain_error 'debugging' + - it writes to stderr when std._debug.level is specified: + expect(luaproc [[ + require 'std._debug'.level = 0 + require 'std.debug'('debugging') + ]]).to_contain_error 'debugging' + expect(luaproc [[ + require 'std._debug'.level = 1 + require 'std.debug'('debugging') + ]]).to_contain_error 'debugging' + expect(luaproc [[ + require 'std._debug'.level = 2 + require 'std.debug'('debugging') + ]]).to_contain_error 'debugging' + + +- describe say: + - it uses normalize.str: + expect(luaproc [[require 'std.debug'.say {'debugging'}]]). + to_contain_error(require 'std.normalize'.str {'debugging'}) + - context when std._debug is disabled: + - before: + preamble = [[ + require 'std._debug'(false) + ]] + - it does nothing when message level is not set: + expect(luaproc(preamble .. [[ + require 'std.debug'.say 'nothing to see here' + ]])).not_to_contain_error 'nothing to see here' + - it does nothing when message is set: + for _, level in next, {-999, 0, 1, 2, 999} do + expect(luaproc(preamble .. [[ + require 'std.debug'.say(]] .. level .. [[, 'nothing to see here') + ]])).not_to_contain_error 'nothing to see here' + end + - context when std._debug is not set: + - it writes to stderr when message level is not set: + expect(luaproc [[ + require 'std.debug'.say 'debugging' + ]]).to_contain_error 'debugging' + - it writes to stderr when message level is 1 or lower: + for _, level in next, {-999, 0, 1} do + expect(luaproc([[ + require 'std.debug'.say(]] .. level .. [[, 'debugging') + ]])).to_contain_error 'debugging' + end + - it does nothing when message level is 2 or higher: + for _, level in next, {2, 999} do + expect(luaproc([[ + require 'std.debug'.say(]] .. level .. [[, 'nothing to see here') + ]])).not_to_contain_error 'nothing to see here' + end + - context when std._debug is enabled: + - before: + preamble = [[ + require 'std._debug'(true) + ]] + - it writes to stderr when message level is not set: + expect(luaproc(preamble .. [[ + require 'std.debug'.say 'debugging' + ]])).to_contain_error 'debugging' + - it writes to stderr when message level is 1 or lower: + for _, level in next, {-999, 0, 1} do + expect(luaproc(preamble .. [[ + require 'std.debug'.say(]] .. level .. [[, 'debugging') + ]])).to_contain_error 'debugging' + end + - it does nothing when message level is 2 or higher: + for _, level in next, {2, 999} do + expect(luaproc(preamble .. [[ + require 'std.debug'.say(]] .. level .. [[, 'nothing to see here') + ]])).not_to_contain_error 'nothing to see here' + end + - context when std._debug.level is not set: + - it writes to stderr when message level is not set: + expect(luaproc [[ + require 'std.debug'.say 'debugging' + ]]).to_contain_error 'debugging' + - it writes to stderr when message level is 1 or lower: + for _, level in next, {-999, 0, 1} do + expect(luaproc([[ + require 'std.debug'.say(]] .. level .. [[, 'debugging') + ]])).to_contain_error 'debugging' + end + - it does nothing when message level is 2 or higher: + for _, level in next, {2, 999} do + expect(luaproc([[ + require 'std.debug'.say(]] .. level .. [[, 'nothing to see here') + ]])).not_to_contain_error 'nothing to see here' + end + - context when std._debug.level is specified: + - it writes to stderr when message level is 1 or lower: + for _, level in next, {0, 1, 2} do + expect(luaproc([[ + require 'std._debug'.level = ]] .. level .. [[ + require 'std.debug'.say 'debugging' + ]])).to_contain_error 'debugging' + end + - it does nothing when message level is higher than debug level: + expect(luaproc [[ + require 'std._debug'.level = 2 + require 'std.debug'.say(3, 'nothing to see here') + ]]).not_to_contain_error 'nothing to see here' + - it writes to stderr when message level equals debug level: + expect(luaproc [[ + require 'std._debug'.level = 2 + require 'std.debug'.say(2, 'debugging') + ]]).to_contain_error 'debugging' + - it writes to stderr when message level is lower than debug level: + expect(luaproc [[ + require 'std._debug'.level = 2 + require 'std.debug'.say(1, 'debugging') + ]]).to_contain_error 'debugging' + + +- describe trace: + - before: + f = init(M, this_module, 'trace') + + - it does nothing when debug hint is disabled: + expect(luaproc [[ + require 'std._debug'(false) + require 'std.debug' + os.exit(0) + ]]).to_succeed_with '' + - it does nothing when debug hint is not set: + expect(luaproc [[ + require 'std.debug' + os.exit(0) + ]]).to_succeed_with '' + - it does nothing when debug hint is enabled: + expect(luaproc [[ + require 'std._debug'(true) + require 'std.debug' + os.exit(0) + ]]).to_succeed_with '' + - it enables automatically when std._debug.call is set: | + expect(luaproc [[ + require 'std._debug'.call = true + require 'std.debug' + os.exit(1) + ]]).to_fail_while_containing ':3 call exit' + - it is enabled manually with debug.sethook: | + expect(luaproc [[ + local debug = require 'std.debug' + debug.sethook(debug.trace, 'cr') + os.exit(1) + ]]).to_fail_while_containing ':3 call exit' + - it writes call trace log to standard error: | + expect(luaproc [[ + local debug = require 'std.debug' + debug.sethook(debug.trace, 'cr') + os.exit(0) + ]]).to_contain_error ':3 call exit' + - it traces lua calls: | + expect(luaproc [[ + local debug = require 'std.debug' -- line 1 + local function incr(i) return i + 1 end -- line 2 + debug.sethook(debug.trace, 'cr') -- line 3 + os.exit(incr(41)) -- line 4 + ]]).to_fail_while_matching '.*:4 call incr <2:.*:4 return incr <2:.*' + - it traces C api calls: | + expect(luaproc [[ + local debug = require 'std.debug' + local function incr(i) return i + 1 end + debug.sethook(debug.trace, 'cr') + os.exit(incr(41)) + ]]).to_fail_while_matching '.*:4 call exit %[C%]%s$' diff --git a/Data/BuiltIn/Libraries/lua-stdlib/spec/io_spec.yaml b/Data/BuiltIn/Libraries/lua-stdlib/spec/io_spec.yaml new file mode 100644 index 0000000..67b850f --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-stdlib/spec/io_spec.yaml @@ -0,0 +1,439 @@ +# General Lua Libraries for Lua 5.1, 5.2 & 5.3 +# Copyright (C) 2011-2018 stdlib authors + +before: | + base_module = 'io' + this_module = 'std.io' + global_table = '_G' + + extend_base = {'catdir', 'catfile', 'die', 'dirname', + 'process_files', 'readlines', 'shell', 'slurp', + 'splitdir', 'warn', 'writelines'} + + dirsep = string.match(package.config, '^([^\n]+)\n') + + M = require(this_module) + + +specify std.io: +- context when required: + - context by name: + - it does not touch the global table: + expect(show_apis {added_to=global_table, by=this_module}). + to_equal {} + - it does not touch the core io table: + expect(show_apis {added_to=base_module, by=this_module}). + to_equal {} + - it contains apis from the core io table: + expect(show_apis {from=base_module, not_in=this_module}). + to_contain.a_permutation_of(extend_base) + + - context via the std module: + - it does not touch the global table: + expect(show_apis {added_to=global_table, by='std'}). + to_equal {} + - it does not touch the core io table: + expect(show_apis {added_to=base_module, by='std'}). + to_equal {} + + +- describe catdir: + - before: | + f = M.catdir + + - context with bad arguments: + badargs.diagnose(f, 'std.io.catdir(string*)') + + - it treats initial empty string as root directory: + expect(f('')).to_be(dirsep) + expect(f('', '')).to_be(dirsep) + expect(f('', 'root')).to_be(dirsep .. 'root') + - it returns a single argument unchanged: + expect(f('hello')).to_be 'hello' + - it joins multiple arguments with platform directory separator: + expect(f('one', 'two')).to_be('one' .. dirsep .. 'two') + expect(f('1', '2', '3', '4', '5')). + to_be(table.concat({'1', '2', '3', '4', '5'}, dirsep)) + + +- describe catfile: + - before: + f = M.catfile + + - context with bad arguments: + badargs.diagnose(f, 'std.io.catfile(string*)') + + - it treats initial empty string as root directory: + expect(f('', '')).to_be(dirsep) + expect(f('', 'root')).to_be(dirsep .. 'root') + - it returns a single argument unchanged: + expect(f('')).to_be '' + expect(f('hello')).to_be 'hello' + - it joins multiple arguments with platform directory separator: + expect(f('one', 'two')).to_be('one' .. dirsep .. 'two') + expect(f('1', '2', '3', '4', '5')). + to_be(table.concat({'1', '2', '3', '4', '5'}, dirsep)) + + +- describe die: + - before: | + script = [[require 'std.io'.die "By 'eck!"]] + + f = M.die + + - context with bad arguments: + badargs.diagnose(f, 'std.io.die(string, ?any*)') + + - it outputs a message to stderr: | + expect(luaproc(script)).to_fail_while_matching ": By 'eck!\n" + - it ignores `prog.line` without `prog.file` or `prog.name`: | + script = [[prog = {line=125};]] .. script + expect(luaproc(script)).to_fail_while_matching ": By 'eck!\n" + - it ignores `opts.line` without `opts.program`: | + script = [[opts = {line=99};]] .. script + expect(luaproc(script)).to_fail_while_matching ": By 'eck!\n" + - it prefixes `prog.name` if any: | + script = [[prog = {name='name'};]] .. script + expect(luaproc(script)).to_fail_while_matching ": name: By 'eck!\n" + - it appends `prog.line` if any, to `prog.name`: | + script = [[prog = {line=125, name='name'};]] .. script + expect(luaproc(script)).to_fail_while_matching ": name:125: By 'eck!\n" + - it prefixes `prog.file` if any: | + script = [[prog = {file='file'};]] .. script + expect(luaproc(script)).to_fail_while_matching ": file: By 'eck!\n" + - it appends `prog.line` if any, to `prog.name`: | + script = [[prog = {file='file', line=125};]] .. script + expect(luaproc(script)).to_fail_while_matching ": file:125: By 'eck!\n" + - it prefers `prog.name` to `prog.file` or `opts.program`: | + script = [[ + prog = {file='file', name='name'} + opts = {program='program'} + ]] .. script + expect(luaproc(script)).to_fail_while_matching ": name: By 'eck!\n" + - it appends `prog.line` if any to `prog.name` over anything else: | + script = [[ + prog = {file='file', line=125, name='name'} + opts = {line=99, program='program'} + ]] .. script + expect(luaproc(script)).to_fail_while_matching ": name:125: By 'eck!\n" + - it prefers `prog.file` to `opts.program`: | + script = [[ + prog = {file='file'}; opts = {program='program'} + ]] .. script + expect(luaproc(script)).to_fail_while_matching ": file: By 'eck!\n" + - it appends `prog.line` if any to `prog.file` over using `opts`: | + script = [[ + prog = {file='file', line=125} + opts = {line=99, program='program'} + ]] .. script + expect(luaproc(script)).to_fail_while_matching ": file:125: By 'eck!\n" + - it prefixes `opts.program` if any: | + script = [[opts = {program='program'};]] .. script + expect(luaproc(script)).to_fail_while_matching ": program: By 'eck!\n" + - it appends `opts.line` if any, to `opts.program`: | + script = [[opts = {line=99, program='program'};]] .. script + expect(luaproc(script)).to_fail_while_matching ": program:99: By 'eck!\n" + + +- describe dirname: + - before: + f = M.dirname + path = table.concat({'', 'one', 'two', 'three'}, dirsep) + + - context with bad arguments: + badargs.diagnose(f, 'std.io.dirname(string)') + + - it removes final separator and following: + expect(f(path)).to_be(table.concat({'', 'one', 'two'}, dirsep)) + + +- describe process_files: + - before: + name = 'Makefile' + names = {'LICENSE.md', 'Makefile', 'README.md'} + ascript = [[ + require 'std.io'.process_files(function(a) + print(a) + end) + ]] + lscript = [[ + require 'std.io'.process_files('=print(_1)') + ]] + iscript = [[ + require 'std.io'.process_files(function(_, i) + print(i) + end) + ]] + catscript = [[ + require 'std.io'.process_files(function() + io.write(io.input():read '*a') + end) + ]] + + f = M.process_files + + - context with bad arguments: | + badargs.diagnose(f, 'std.io.process_files(func)') + + examples { + ["it diagnoses non-file 'arg' elements"] = function() + expect(luaproc(ascript, 'not-an-existing-file')).to_contain_error.any_of { + "cannot open file 'not-an-existing-file'", -- Lua 5.2 + "bad argument #1 to 'input' (not-an-existing-file:", -- Lua 5.1 + } + end + } + + - it defaults to `-` if no arguments were passed: + expect(luaproc(ascript)).to_output '-\n' + - it iterates over arguments with supplied function: + expect(luaproc(ascript, name)).to_output(name .. '\n') + expect(luaproc(ascript, names)). + to_output(table.concat(names, '\n') .. '\n') + - it passes argument numbers to supplied function: + expect(luaproc(iscript, names)).to_output '1\n2\n3\n' + - it sets each file argument as the default input: + expect(luaproc(catscript, name)).to_output(concat_file_content(name)) + expect(luaproc(catscript, names)). + to_output(concat_file_content(unpack(names))) + - it processes io.stdin if no arguments were passed: + ## FIXME: where does that closing newline come from?? + expect(luaproc(catscript, nil, 'some\nlines\nof input')).to_output 'some\nlines\nof input\n' + - it processes io.stdin for `-` argument: + ## FIXME: where does that closing newline come from?? + expect(luaproc(catscript, '-', 'some\nlines\nof input')).to_output 'some\nlines\nof input\n' + + +- describe readlines: + - before: | + name = 'Makefile' + h = io.open(name) + lines = {} + for l in h:lines() do + lines[#lines + 1] = l + end + h:close() + + defaultin = io.input() + + f, badarg = init(M, this_module, 'readlines') + - after: + if io.type(defaultin) ~= 'closed file' then + io.input(defaultin) + end + + - context with bad arguments: | + badargs.diagnose(f, 'std.io.readlines(?file|string)') + + if have_typecheck then + examples { + ['it diagnoses non-existent file'] = function() + expect(f 'not-an-existing-file'). + to_raise "bad argument #1 to 'std.io.readlines'(" -- system dependent error message + end + } + closed = io.open(name, 'r') closed:close() + examples { + ['it diagnoses closed file argument'] = function() + expect(f(closed)).to_raise(badarg(1, '?file|string', 'closed file')) + end + } + end + + - it closes file handle upon completion: + h = io.open(name) + expect(io.type(h)).not_to_be 'closed file' + f(h) + expect(io.type(h)).to_be 'closed file' + - it reads lines from an existing named file: + expect(f(name)).to_equal(lines) + - it reads lines from an open file handle: + expect(f(io.open(name))).to_equal(lines) + - it reads from default input stream with no arguments: + io.input(name) + expect(f()).to_equal(lines) + + +- describe shell: + - before: + f = M.shell + + - context with bad arguments: + badargs.diagnose(f, 'std.io.shell(string)') + + - it returns the output from a shell command string: + expect(f [[printf '%s\n' 'foo' 'bar']]).to_be 'foo\nbar\n' + + +- describe slurp: + - before: | + name = 'Makefile' + h = io.open(name) + content = h:read '*a' + h:close() + + defaultin = io.input() + f, badarg = init(M, this_module, 'slurp') + - after: + if io.type(defaultin) ~= 'closed file' then + io.input(defaultin) + end + + - context with bad arguments: | + badargs.diagnose(f, 'std.io.slurp(?file|string)') + + if have_typecheck then + examples { + ['it diagnoses non-existent file'] = function() + expect(f 'not-an-existing-file'). + to_raise "bad argument #1 to 'std.io.slurp'(" -- system dependent error message + end + } + closed = io.open(name, 'r') closed:close() + examples { + ['it diagnoses closed file argument'] = function() + expect(f(closed)).to_raise(badarg(1, '?file|string', 'closed file')) + end + } + end + + - it reads content from an existing named file: + expect(f(name)).to_be(content) + - it reads content from an open file handle: + expect(f(io.open(name))).to_be(content) + - it closes file handle upon completion: + h = io.open(name) + expect(io.type(h)).not_to_be 'closed file' + f(h) + expect(io.type(h)).to_be 'closed file' + - it reads from default input stream with no arguments: + io.input(name) + expect(f()).to_be(content) + + +- describe splitdir: + - before: + f = M.splitdir + + - context with bad arguments: + badargs.diagnose(f, 'std.io.splitdir(string)') + + - it returns a filename as a one element list: + expect(f('hello')).to_equal {'hello'} + - it splits root directory in two empty elements: + expect(f(dirsep)).to_equal {'', ''} + - it returns initial empty string for absolute path: + expect(f(dirsep .. 'root')).to_equal {'', 'root'} + - it returns multiple components split at platform directory separator: + expect(f('one' .. dirsep .. 'two')).to_equal {'one', 'two'} + expect(f(table.concat({'1', '2', '3', '4', '5'}, dirsep))). + to_equal {'1', '2', '3', '4', '5'} + + +- describe warn: + - before: + script = [[require 'std.io'.warn 'Ayup!']] + f = M.warn + + - context with bad arguments: + badargs.diagnose(f, 'std.io.warn(string, ?any*)') + + - it outputs a message to stderr: + expect(luaproc(script)).to_output_error 'Ayup!\n' + - it ignores `prog.line` without `prog.file`, `prog.name` or `opts.program`: + script = [[prog = {line=125};]] .. script + expect(luaproc(script)).to_output_error 'Ayup!\n' + - it prefixes `prog.name` if any: | + script = [[prog = {name='name'};]] .. script + expect(luaproc(script)).to_output_error 'name: Ayup!\n' + - it appends `prog.line` if any, to `prog.name`: | + script = [[prog = {line=125, name='name'};]] .. script + expect(luaproc(script)).to_output_error 'name:125: Ayup!\n' + - it prefixes `prog.file` if any: | + script = [[prog = {file='file'};]] .. script + expect(luaproc(script)).to_output_error 'file: Ayup!\n' + - it appends `prog.line` if any, to `prog.name`: | + script = [[prog = {file='file', line=125};]] .. script + expect(luaproc(script)).to_output_error 'file:125: Ayup!\n' + - it prefers `prog.name` to `prog.file` or `opts.program`: | + script = [[ + prog = {file='file', name='name'} + opts = {program='program'} + ]] .. script + expect(luaproc(script)).to_output_error 'name: Ayup!\n' + - it appends `prog.line` if any to `prog.name` over anything else: | + script = [[ + prog = {file='file', line=125, name='name'} + opts = {line=99, program='program'} + ]] .. script + expect(luaproc(script)).to_output_error 'name:125: Ayup!\n' + - it prefers `prog.file` to `opts.program`: | + script = [[ + prog = {file='file'}; opts = {program='program'} + ]] .. script + expect(luaproc(script)).to_output_error 'file: Ayup!\n' + - it appends `prog.line` if any to `prog.file` over using `opts`: | + script = [[ + prog = {file='file', line=125} + opts = {line=99, program='program'} + ]] .. script + expect(luaproc(script)).to_output_error 'file:125: Ayup!\n' + - it prefixes `opts.program` if any: | + script = [[opts = {program='program'};]] .. script + expect(luaproc(script)).to_output_error 'program: Ayup!\n' + - it appends `opts.line` if any, to `opts.program`: | + script = [[opts = {line=99, program='program'};]] .. script + expect(luaproc(script)).to_output_error 'program:99: Ayup!\n' + + +- describe writelines: + - before: | + name = os.tmpname() + h = io.open(name, 'w') + lines = M.readlines(io.open 'Makefile') + + defaultout = io.output() + f, badarg = init(M, this_module, 'writelines') + - after: + if io.type(defaultout) ~= 'closed file' then + io.output(defaultout) + end + h:close() + os.remove(name) + + - context with bad arguments: + - 'it diagnoses argument #1 type not FILE*, string, number or nil': + if have_typecheck then + expect(f(false)).to_raise(badarg(1, '?file|string|number', 'boolean')) + end + - 'it diagnoses argument #2 type not string, number or nil': + if have_typecheck then + expect(f(1, false)).to_raise(badarg(2, 'string|number', 'boolean')) + end + - 'it diagnoses argument #3 type not string, number or nil': + if have_typecheck then + expect(f(1, 2, false)).to_raise(badarg(3, 'string|number', 'boolean')) + end + - it diagnoses closed file argument: | + closed = io.open(name, 'r') closed:close() + if have_typecheck then + expect(f(closed)).to_raise(badarg(1, '?file|string|number', 'closed file')) + end + + - it does not close the file handle upon completion: + expect(io.type(h)).not_to_be 'closed file' + f(h, 'foo') + expect(io.type(h)).not_to_be 'closed file' + - it writes lines to an open file handle: + f(h, unpack(lines)) + h:flush() + expect(M.readlines(io.open(name))).to_equal(lines) + - it accepts number valued arguments: + f(h, 1, 2, 3) + h:flush() + expect(M.readlines(io.open(name))).to_equal {'1', '2', '3'} + - it writes to default output stream with non-file first argument: + io.output(h) + f(unpack(lines)) + h:flush() + expect(M.readlines(io.open(name))).to_equal(lines) diff --git a/Data/BuiltIn/Libraries/lua-stdlib/spec/math_spec.yaml b/Data/BuiltIn/Libraries/lua-stdlib/spec/math_spec.yaml new file mode 100644 index 0000000..ed08753 --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-stdlib/spec/math_spec.yaml @@ -0,0 +1,99 @@ +# General Lua Libraries for Lua 5.1, 5.2 & 5.3 +# Copyright (C) 2011-2018 stdlib authors + +before: + base_module = 'math' + this_module = 'std.math' + global_table = '_G' + + extend_base = {'floor', 'round'} + + M = require(this_module) + + +specify std.math: +- context when required: + - context by name: + - it does not touch the global table: + expect(show_apis {added_to=global_table, by=this_module}). + to_equal {} + - it does not touch the core math table: + expect(show_apis {added_to=base_module, by=this_module}). + to_equal {} + - it contains apis from the core math table: + expect(show_apis {from=base_module, not_in=this_module}). + to_contain.a_permutation_of(extend_base) + + - context via the std module: + - it does not touch the global table: + expect(show_apis {added_to=global_table, by='std'}). + to_equal {} + - it does not touch the core math table: + expect(show_apis {added_to=base_module, by='std'}). + to_equal {} + + +- describe floor: + - before: + f = M.floor + + - context with bad arguments: + badargs.diagnose(f, 'std.math.floor(number, ?int)') + + - it rounds to the nearest smaller integer: + expect(f(1.2)).to_be(1) + expect(f(1.9)).to_be(1) + expect(f(999e-2)).to_be(9) + expect(f(999e-3)).to_be(0) + - it rounds down to specified number of decimal places: + expect(f(1.2345, 0)).to_be(1.0) + expect(f(1.2345, 1)).to_be(1.2) + expect(f(1.2345, 2)).to_be(1.23) + expect(f(9.9999, 2)).to_be(9.99) + expect(f(99999e-3, 3)).to_be(99999e-3) + expect(f(99999e-4, 3)).to_be(9999e-3) + expect(f(99999e-5, 3)).to_be(999e-3) + + +- describe round: + - before: + f = M.round + + - context with bad arguments: + badargs.diagnose(f, 'std.math.round(number, ?int)') + + - it rounds to the nearest integer: + expect(f(1.2)).to_be(1) + expect(f(1.9)).to_be(2) + expect(f(949e-2)).to_be(9) + expect(f(999e-2)).to_be(10) + - it rounds to specified number of decimal places: + expect(f(1.234, 0)).to_be(1.0) + expect(f(5.678, 0)).to_be(6.0) + expect(f(1.234, 1)).to_be(1.2) + expect(f(5.678, 1)).to_be(5.7) + expect(f(1.234, 2)).to_be(1.23) + expect(f(5.678, 2)).to_be(5.68) + expect(f(9.999, 2)).to_be(10) + expect(f(11111e-2, 3)).to_be(11111e-2) + expect(f(99999e-2, 3)).to_be(99999e-2) + expect(f(11111e-3, 3)).to_be(11111e-3) + expect(f(99999e-3, 3)).to_be(99999e-3) + expect(f(11111e-4, 3)).to_be(1111e-3) + expect(f(99999e-4, 3)).to_be(10) + expect(f(99999e-5, 3)).to_be(1) + - it rounds negative values correctly: + expect(f(-1.234, 0)).to_be(-1.0) + expect(f(-5.678, 0)).to_be(-6.0) + expect(f(-1.234, 1)).to_be(-1.2) + expect(f(-5.678, 1)).to_be(-5.7) + expect(f(-1.234, 2)).to_be(-1.23) + expect(f(-5.678, 2)).to_be(-5.68) + expect(f(-9.999, 2)).to_be(-10) + expect(f(-11111e-2, 3)).to_be(-11111e-2) + expect(f(-99999e-2, 3)).to_be(-99999e-2) + expect(f(-11111e-3, 3)).to_be(-11111e-3) + expect(f(-99999e-3, 3)).to_be(-99999e-3) + expect(f(-11111e-4, 3)).to_be(-1111e-3) + expect(f(-99999e-4, 3)).to_be(-10) + expect(f(-99999e-5, 3)).to_be(-1) diff --git a/Data/BuiltIn/Libraries/lua-stdlib/spec/package_spec.yaml b/Data/BuiltIn/Libraries/lua-stdlib/spec/package_spec.yaml new file mode 100644 index 0000000..23ce961 --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-stdlib/spec/package_spec.yaml @@ -0,0 +1,202 @@ +# General Lua Libraries for Lua 5.1, 5.2 & 5.3 +# Copyright (C) 2011-2018 stdlib authors + +before: | + base_module = 'package' + this_module = 'std.package' + global_table = '_G' + + extend_base = {'find', 'insert', 'mappath', 'normalize', 'remove'} + + M = require(this_module) + + path = M.normalize('begin', 'middle', 'end') + + function catfile(...) + return table.concat({...}, M.dirsep) + end + function catpath(...) + return table.concat({...}, M.pathsep) + end + + +specify std.package: +- context when required: + - context by name: + - it does not touch the global table: + expect(show_apis {added_to=global_table, by=this_module}). + to_equal {} + - it does not touch the core package table: + expect(show_apis {added_to=base_module, by=this_module}). + to_equal {} + - it contains apis from the core package table: + expect(show_apis {from=base_module, not_in=this_module}). + to_contain.a_permutation_of(extend_base) + + - context via the std module: + - it does not touch the global table: + expect(show_apis {added_to=global_table, by='std'}). + to_equal {} + - it does not touch the core package table: + expect(show_apis {added_to=base_module, by='std'}). + to_equal {} + + +- describe find: + - before: | + path = table.concat({'begin', 'm%ddl.', 'end'}, M.pathsep) + + f = M.find + + - context with bad arguments: + badargs.diagnose(f, 'std.package.find(string, string, ?int, ?boolean|:plain)') + + - it returns nil for unmatched element: + expect(f(path, 'unmatchable')).to_be(nil) + - it returns the element index for a matched element: + expect(f(path, 'end')).to_be(3) + - it returns the element text for a matched element: + i, element = f(path, 'e.*n') + expect({i, element}).to_equal {1, 'begin'} + - it accepts a search start element argument: + i, element = f(path, 'e.*n', 2) + expect({i, element}).to_equal {3, 'end'} + - it works with plain text search strings: + expect(f(path, 'm%ddl.')).to_be(nil) + i, element = f(path, '%ddl.', 1, ':plain') + expect({i, element}).to_equal {2, 'm%ddl.'} + + +- describe insert: + - before: + f = M.insert + + - context with bad arguments: + badargs.diagnose(f, 'std.package.insert(string, [int], string)') + + - it appends by default: + expect(f(path, 'new')). + to_be(M.normalize('begin', 'middle', 'end', 'new')) + - it prepends with pos set to 1: + expect(f(path, 1, 'new')). + to_be(M.normalize('new', 'begin', 'middle', 'end')) + - it can insert in the middle too: + expect(f(path, 2, 'new')). + to_be(M.normalize('begin', 'new', 'middle', 'end')) + expect(f(path, 3, 'new')). + to_be(M.normalize('begin', 'middle', 'new', 'end')) + - it normalizes the returned path: + path = table.concat({'begin', 'middle', 'end'}, M.pathsep) + expect(f(path, 'new')). + to_be(M.normalize('begin', 'middle', 'end', 'new')) + expect(f(path, 1, './x/../end')). + to_be(M.normalize('end', 'begin', 'middle')) + + +- describe mappath: + - before: | + expected = require 'std.string'.split(path, M.pathsep) + + f = M.mappath + + - context with bad arguments: + badargs.diagnose(f, 'std.package.mappath(string, function, ?any*)') + + - it calls a function with each path element: + t = {} + f(path, function(e) + t[#t + 1] = e + end) + expect(t).to_equal(expected) + - it passes additional arguments through: | + reversed = {} + for i = #expected, 1, -1 do + table.insert(reversed, expected[i]) + end + t = {} + f(path, function(e, pos) + table.insert(t, pos, e) + end, 1) + expect(t).to_equal(reversed) + + +- describe normalize: + - before: + f = M.normalize + + - context with bad arguments: + badargs.diagnose(f, 'std.package.normalize(string*)') + + - context with a single element: + - it strips redundant . directories: + expect(f './x/./y/.').to_be(catfile('.', 'x', 'y')) + - it strips redundant .. directories: + expect(f '../x/../y/z/..').to_be(catfile('..', 'y')) + expect(f '../x/../y/z/..').to_be(catfile('..', 'y')) + expect(f '../../x/../y/z/..').to_be(catfile('..', '..', 'y')) + expect(f '../../x/../y/./..').to_be(catfile('..', '..')) + expect(f '../../w/x/../../y/z/..').to_be(catfile('..', '..', 'y')) + expect(f '../../w/./../.././z/..').to_be(catfile('..', '..', '..')) + - it leaves leading .. directories unmolested: + expect(f '../../1').to_be(catfile('..', '..', '1')) + expect(f './../../1').to_be(catfile('..', '..', '1')) + - it normalizes / to platform dirsep: + expect(f '/foo/bar').to_be(catfile('', 'foo', 'bar')) + - it normalizes ? to platform pathmark: + expect(f '?.lua'). + to_be(catfile('.', M.pathmark .. '.lua')) + - it strips redundant trailing /: + expect(f '/foo/bar/').to_be(catfile('', 'foo', 'bar')) + - it inserts missing ./ for relative paths: + for _, path in ipairs {'x', './x'} do + expect(f(path)).to_be(catfile('.', 'x')) + end + - context with multiple elements: + - it strips redundant . directories: + expect(f('./x/./y/.', 'x')). + to_be(catpath(catfile('.', 'x', 'y'), catfile('.', 'x'))) + - it strips redundant .. directories: + expect(f('../x/../y/z/..', 'x')). + to_be(catpath(catfile('..', 'y'), catfile('.', 'x'))) + - it normalizes / to platform dirsep: + expect(f('/foo/bar', 'x')). + to_be(catpath(catfile('', 'foo', 'bar'), catfile('.', 'x'))) + - it normalizes ? to platform pathmark: + expect(f('?.lua', 'x')). + to_be(catpath(catfile('.', M.pathmark .. '.lua'), catfile('.', 'x'))) + - it strips redundant trailing /: + expect(f('/foo/bar/', 'x')). + to_be(catpath(catfile('', 'foo', 'bar'), catfile('.', 'x'))) + - it inserts missing ./ for relative paths: + for _, path in ipairs {'x', './x'} do + expect(f(path, 'a')). + to_be(catpath(catfile('.', 'x'), catfile('.', 'a'))) + end + - it eliminates all but the first equivalent elements: + expect(f(catpath('1', 'x', '2', './x', './2', './x/../x'))). + to_be(catpath('./1', './x', './2')) + + +- describe remove: + - before: + f = M.remove + + - context with bad arguments: + badargs.diagnose(f, 'std.package.remove(string, ?int)') + + - it removes the last item by default: + expect(f(path)).to_be(M.normalize('begin', 'middle')) + - it pops the first item with pos set to 1: + expect(f(path, 1)).to_be(M.normalize('middle', 'end')) + - it can remove from the middle too: + expect(f(path, 2)).to_be(M.normalize('begin', 'end')) + - it does not normalize the returned path: + path = table.concat({'begin', 'middle', 'end'}, M.pathsep) + expect(f(path)). + to_be(table.concat({'begin', 'middle'}, M.pathsep)) + + +- it splits package.config up: + expect(string.format('%s\n%s\n%s\n%s\n%s\n', + M.dirsep, M.pathsep, M.pathmark, M.execdir, M.igmark) + ).to_contain(package.config) diff --git a/Data/BuiltIn/Libraries/lua-stdlib/spec/spec_helper.lua b/Data/BuiltIn/Libraries/lua-stdlib/spec/spec_helper.lua new file mode 100644 index 0000000..642712f --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-stdlib/spec/spec_helper.lua @@ -0,0 +1,416 @@ +--[[ + General Lua Libraries for Lua 5.1, 5.2 & 5.3 + Copyright (C) 2011-2018 stdlib authors +]] + +local typecheck +have_typecheck, typecheck = pcall(require, 'typecheck') + +local inprocess = require 'specl.inprocess' +local hell = require 'specl.shell' +local std = require 'specl.std' + +badargs = require 'specl.badargs' + + +local top_srcdir = os.getenv 'top_srcdir' or '.' +local top_builddir = os.getenv 'top_builddir' or '.' + +package.path = std.package.normalize( + top_builddir .. '/lib/?.lua', + top_builddir .. '/lib/?/init.lua', + top_srcdir .. '/lib/?.lua', + top_srcdir .. '/lib/?/init.lua', + package.path +) + + +-- Allow user override of LUA binary used by hell.spawn, falling +-- back to environment PATH search for 'lua' if nothing else works. +local LUA = os.getenv 'LUA' or 'lua' + + +-- Simplified version for specifications, does not support functable +-- valued __len metamethod, so don't write examples that need that! +function len(x) + local __len = getmetatable(x) or {} + if type(__len) == 'function' then + return __len(x) + end + if type(x) ~= 'table' then + return #x + end + + local n = #x + for i = 1, n do + if x[i] == nil then + return i -1 + end + end + return n +end + + +-- Make sure we have a maxn even when _VERSION ~= 5.1 +-- @fixme remove this when we get unpack from specl.std +maxn = table.maxn or function(t) + local n = 0 + for k in pairs(t) do + if type(k) == 'number' and k > n then + n = k + end + end + return n +end + + +pack = table.pack or function(...) + return {n=select('#', ...), ...} +end + + +-- Take care to always unpack upto the highest numeric index, for +-- consistency across Lua versions. +local _unpack = table.unpack or unpack + +-- @fixme pick this up from specl.std with the next release +function unpack(t, i, j) + return _unpack(t, tonumber(i) or 1, tonumber(j or t.n or len(t))) +end + + +-- In case we're not using a bleeding edge release of Specl... +_diagnose = badargs.diagnose +badargs.diagnose = function(...) + if have_typecheck then + return _diagnose(...) + end +end + +badargs.result = badargs.result or function(fname, i, want, got) + if want == nil then -- numbers only for narg error + i, want = i - 1, i + end + + if got == nil and type(want) == 'number' then + local s = "bad result #%d from '%s'(no more than %d result%s expected, got %d)" + return s:format(i + 1, fname, i, i == 1 and '' or 's', want) + end + + local function showarg(s) + return('|' .. s .. '|'): + gsub('|%?', '|nil|'): + gsub('|nil|', '|no value|'): + gsub('|any|', '|any value|'): + gsub('|#', '|non-empty '): + gsub('|func|', '|function|'): + gsub('|file|', '|FILE*|'): + gsub('^|', ''): + gsub('|$', ''): + gsub('|([^|]+)$', 'or %1'): + gsub('|', ', ') + end + + return string.format("bad result #%d from '%s'(%s expected, got %s)", + i, fname, showarg(want), got or 'no value') +end + + +-- Wrap up badargs function in a succinct single call. +function init(M, mname, fname) + local name =(mname .. '.' .. fname):gsub('^%.', '') + return M[fname], + function(...) + return badargs.format(name, ...) + end, + function(...) + return badargs.result(name, ...) + end +end + + +-- A copy of base.lua:type, so that an unloadable base.lua doesn't +-- prevent everything else from working. +function objtype(o) + return(getmetatable(o) or {})._type or io.type(o) or type(o) +end + + +function nop() end + + +-- Error message specifications use this to shorten argument lists. +-- Copied from functional.lua to avoid breaking all tests if functional +-- cannot be loaded correctly. +function bind(f, fix) + return function(...) + local arg = {} + for i, v in pairs(fix) do + arg[i] = v + end + local i = 1 + for _, v in pairs {...} do + while arg[i] ~= nil do + i = i + 1 + end + arg[i] = v + end + return f(unpack(arg)) + end +end + + +local function mkscript(code) + local f = os.tmpname() + local h = io.open(f, 'w') + h:write(code) + h:close() + return f +end + + +--- Run some Lua code with the given arguments and input. +-- @string code valid Lua code +-- @tparam[opt={}] string|table arg single argument, or table of +-- arguments for the script invocation. +-- @string[opt] stdin standard input contents for the script process +-- @treturn specl.shell.Process|nil status of resulting process if +-- execution was successful, otherwise nil +function luaproc(code, arg, stdin) + local f = mkscript(code) + if type(arg) ~= 'table' then + arg = {arg} + end + local cmd = {LUA, f, unpack(arg)} + -- inject env and stdin keys separately to avoid truncating `...` in + -- cmd constructor + cmd.env = {LUA_PATH=package.path, LUA_INIT='', LUA_INIT_5_2=''} + cmd.stdin = stdin + local proc = hell.spawn(cmd) + os.remove(f) + return proc +end + + +--- Check deprecation output when calling a named function in the given module. +-- Note that the script fragments passed in *argstr* and *objectinit* +-- can reference the module table as `M`, and(where it would make sense) +-- an object prototype as `P` and instance as `obj`. +-- @param deprecate value of `std._debug.deprecate` +-- @string module dot delimited module path to load +-- @string fname name of a function in the table returned by requiring *module* +-- @param[opt=''] args arguments to pass to *fname* call, must be stringifiable +-- @string[opt=nil] objectinit object initializer to instantiate an +-- object for object method deprecation check +-- @treturn specl.shell.Process|nil status of resulting process if +-- execution was successful, otherwise nil +function deprecation(deprecate, module, fname, args, objectinit) + args = args or '' + local script + if objectinit == nil then + script = string.format([[ + require 'std._debug'.deprecate = %s + M = require '%s' + P = M.prototype + print(M.%s(%s)) + ]], tostring(deprecate), module, fname, tostring(args)) + else + script = string.format([[ + require 'std._debug'.deprecate = %s + local M = require '%s' + local P = M.prototype + local obj = P(%s) + print(obj:%s(%s)) + ]], tostring(deprecate), module, objectinit, fname, tostring(args)) + end + return luaproc(script) +end + + +--- Concatenate the contents of listed existing files. +-- @string ... names of existing files +-- @treturn string concatenated contents of those files +function concat_file_content(...) + local t = {} + for _, name in ipairs {...} do + h = io.open(name) + t[#t + 1] = h:read '*a' + end + return table.concat(t) +end + + +local function tabulate_output(code) + local proc = luaproc(code) + if proc.status ~= 0 then + return error(proc.errout) + end + local r = {} + proc.output:gsub('(%S*)[%s]*', + function(x) + if x ~= '' then + r[x] = true + end + end) + return r +end + + +--- Show changes to tables wrought by a require statement. +-- There are a few modes to this function, controlled by what named +-- arguments are given. Lists new keys in T1 after `require 'import'`: +-- +-- show_apis {added_to=T1, by=import} +-- +-- List keys returned from `require 'import'`, which have the same +-- value in T1: +-- +-- show_apis {from=T1, used_by=import} +-- +-- List keys from `require 'import'`, which are also in T1 but with +-- a different value: +-- +-- show_apis {from=T1, enhanced_by=import} +-- +-- List keys from T2, which are also in T1 but with a different value: +-- +-- show_apis {from=T1, enhanced_in=T2} +-- +-- @tparam table argt one of the combinations above +-- @treturn table a list of keys according to criteria above +function show_apis(argt) + local added_to, from, not_in, enhanced_in, enhanced_after, by = + argt.added_to, argt.from, argt.not_in, argt.enhanced_in, + argt.enhanced_after, argt.by + + if added_to and by then + return tabulate_output([[ + local before, after = {}, {} + for k in pairs(]] .. added_to .. [[) do + before[k] = true + end + + local M = require ']] .. by .. [[' + for k in pairs(]] .. added_to .. [[) do + after[k] = true + end + + for k in pairs(after) do + if not before[k] then + print(k) + end + end + ]]) + + elseif from and not_in then + return tabulate_output([[ + local _ENV = require 'std.normalize' { + from = ']] .. from .. [[', + M = require ']] .. not_in .. [[', + } + + for k in pairs(M) do + -- M[1] is typically the module namespace name, don't match + -- that! + if k ~= 1 and from[k] ~= M[k] then + print(k) + end + end + ]]) + + elseif from and enhanced_in then + return tabulate_output([[ + local _ENV = require 'std.normalize' { + from = ']] .. from .. [[', + M = require ']] .. enhanced_in .. [[', + } + + for k, v in pairs(M) do + if from[k] ~= M[k] and from[k] ~= nil then + print(k) + end + end + ]]) + + elseif from and enhanced_after then + return tabulate_output([[ + local _ENV = require 'std.normalize' { + from = ']] .. from .. [[', + } + local before, after = {}, {} + for k, v in pairs(from) do + before[k] = v + end + ]] .. enhanced_after .. [[ + for k, v in pairs(from) do + after[k] = v + end + + for k, v in pairs(before) do + if after[k] ~= nil and after[k] ~= v then + print(k) + end + end + ]]) + end + + assert(false, 'missing argument to show_apis') +end + + +-- Stub inprocess.capture if necessary; new in Specl 12. +capture = inprocess.capture or function(f, arg) + return nil, nil, f(unpack(arg or {})) +end + + +do + -- Custom matcher for set size and set membership. + + local util = require 'specl.util' + local matchers = require 'specl.matchers' + + local Matcher, matchers, q = + matchers.Matcher, matchers.matchers, matchers.stringify + + matchers.have_size = Matcher { + function(self, actual, expect) + local size = 0 + for _ in pairs(actual) do + size = size + 1 + end + return size == expect + end, + + actual = 'table', + + format_expect = function(self, expect) + return ' a table containing ' .. expect .. ' elements, ' + end, + + format_any_of = function(self, alternatives) + return ' a table with any of ' .. + util.concat(alternatives, util.QUOTED) .. ' elements, ' + end, + } + + matchers.have_member = Matcher { + function(self, actual, expect) + return actual[expect] ~= nil + end, + + actual = 'set', + + format_expect = function(self, expect) + return ' a set containing ' .. q(expect) .. ', ' + end, + + format_any_of = function(self, alternatives) + return ' a set containing any of ' .. + util.concat(alternatives, util.QUOTED) .. ', ' + end, + } + + -- Alias that doesn't tickle sc_error_message_uppercase. + matchers.raise = matchers.error +end diff --git a/Data/BuiltIn/Libraries/lua-stdlib/spec/std_spec.yaml b/Data/BuiltIn/Libraries/lua-stdlib/spec/std_spec.yaml new file mode 100644 index 0000000..b747abf --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-stdlib/spec/std_spec.yaml @@ -0,0 +1,444 @@ +# General Lua Libraries for Lua 5.1, 5.2 & 5.3 +# Copyright (C) 2011-2018 stdlib authors + +before: | + this_module = 'std' + global_table = '_G' + + exported_apis = {'assert', 'elems', 'eval', 'getmetamethod', + 'ielems', 'ipairs', 'npairs', 'pairs', + 'require', 'ripairs', 'rnpairs'} + + -- Tables with iterator metamethods used by various examples. + __pairs = setmetatable({content='a string'}, { + __pairs = function(t) + return function(x, n) + if n < #x.content then + return n+1, string.sub(x.content, n+1, n+1) + end + end, t, 0 + end, + }) + __index = setmetatable({content='a string'}, { + __index = function(t, n) + if n <= #t.content then + return t.content:sub(n, n) + end + end, + __len = function(t) + return #t.content + end, + }) + + M = require(this_module) + M.version = nil -- previous specs may have autoloaded it + + +specify std: +- context when required: + - it does not touch the global table: + expect(show_apis {added_to=global_table, by=this_module}). + to_equal {} + - it exports the documented apis: + t = {} + for k in pairs(M) do + t[#t + 1] = k + end + expect(t).to_contain.a_permutation_of(exported_apis) + +- context when lazy loading: + - it has no submodules on initial load: + for _, v in pairs(M) do + expect(type(v)).not_to_be 'table' + end + - it loads submodules on demand: + lazy = M.math + expect(lazy).to_be(require 'std.math') + - it loads submodule functions on demand: + expect(M.math.round(3.141592)).to_be(3) + +- describe assert: + - before: + f = M.assert + + - context with bad arguments: + badargs.diagnose(f, 'std.assert(?any, ?string, ?any*)') + + - context when it does not trigger: + - it has a truthy initial argument: + expect(f(1)).not_to_raise 'any error' + expect(f(true)).not_to_raise 'any error' + expect(f 'yes').not_to_raise 'any error' + expect(f(false == false)).not_to_raise 'any error' + - it returns the initial argument: + expect(f(1)).to_be(1) + expect(f(true)).to_be(true) + expect(f 'yes').to_be 'yes' + expect(f(false == false)).to_be(true) + - context when it triggers: + - it has a falsey initial argument: + expect(f()).to_raise() + expect(f(false)).to_raise() + expect(f(1 == 0)).to_raise() + - it throws an optional error string: + expect(f(false, 'ah boo')).to_raise 'ah boo' + - it plugs specifiers with string.format: | + expect(f(nil, '%s %d: %q', 'here', 42, 'a string')). + to_raise(string.format('%s %d: %q', 'here', 42, 'a string')) + + +- describe elems: + - before: + f = M.elems + + - context with bad arguments: + badargs.diagnose(f, 'std.elems(table)') + + - it is an iterator over table values: + t = {} + for e in f {'foo', bar='baz', 42} do + t[#t + 1] = e + end + expect(t).to_contain.a_permutation_of {'foo', 'baz', 42} + - it respects __pairs metamethod: | + t = {} + for v in f(__pairs) do + t[#t + 1] = v + end + expect(t). + to_contain.a_permutation_of {'a', ' ', 's', 't', 'r', 'i', 'n', 'g'} + - it works for an empty list: + t = {} + for e in f {} do + t[#t + 1] = e + end + expect(t).to_equal {} + + +- describe eval: + - before: + f = M.eval + + - context with bad arguments: + badargs.diagnose(f, 'std.eval(string)') + + - it diagnoses invalid lua: + # Some internal error when eval tries to call uncompilable '=' code. + expect(f '=').to_raise() + - it evaluates a string of lua code: + expect(f 'math.min(2, 10)').to_be(math.min(2, 10)) + + +- describe getmetamethod: + - before: + f = M.getmetamethod + + - context with bad arguments: + badargs.diagnose(f, 'std.getmetamethod(?any, string)') + + - context with a table: + - before: + method = function() + return 'called' + end + functor = setmetatable({}, {__call=method}) + t = setmetatable({}, { + _type='table', _method=method, _functor=functor, + }) + - it returns nil for missing metamethods: + expect(f(t, 'not a metamethod on t')).to_be(nil) + - it returns nil for non-callable metatable entries: + expect(f(t, '_type')).to_be(nil) + - it returns a method from the metatable: + expect(f(t, '_method')).to_be(method) + expect(f(t, '_method')()).to_be 'called' + - it returns a functor from the metatable: + expect(f(t, '_functor')).to_be(functor) + expect(f(t, '_functor')()).to_be 'called' + + +- describe ielems: + - before: + f = M.ielems + + - context with bad arguments: + badargs.diagnose(f, 'std.ielems(table)') + + - it is an iterator over integer-keyed table values: + t = {} + for e in f {'foo', 42} do + t[#t + 1] = e + end + expect(t).to_equal {'foo', 42} + - it ignores the dictionary part of a table: + t = {} + for e in f {'foo', 42; bar='baz', qux='quux'} do + t[#t + 1] = e + end + expect(t).to_equal {'foo', 42} + - it respects __len metamethod: + t = {} + for v in f(__index) do + t[#t + 1] = v + end + expect(t).to_equal {'a', ' ', 's', 't', 'r', 'i', 'n', 'g'} + - it works for an empty list: + t = {} + for e in f {} do + t[#t + 1] = e + end + expect(t).to_equal {} + + +- describe ipairs: + - before: + f = M.ipairs + + - context with bad arguments: + badargs.diagnose(f, 'std.ipairs(table)') + + - it is an iterator over integer-keyed table values: + t = {} + for i, v in f {'foo', 42} do + t[i] = v + end + expect(t).to_equal {'foo', 42} + - it ignores the dictionary part of a table: + t = {} + for i, v in f {'foo', 42; bar='baz', qux='quux'} do + t[i] = v + end + expect(t).to_equal {'foo', 42} + - it respects __len metamethod: + t = {} + for k, v in f(__index) do + t[k] = v + end + expect(t).to_equal {'a', ' ', 's', 't', 'r', 'i', 'n', 'g'} + - it works for an empty list: + t = {} + for i, v in f {} do + t[i] = v + end + expect(t).to_equal {} + + +- describe npairs: + - before: + f = M.npairs + + - context with bad arguments: + badargs.diagnose(f, 'std.npairs(table)') + + - it is an iterator over integer-keyed table values: + t = {} + for i, v in f {'foo', 42, nil, nil, 'five'} do + t[i] = v + end + expect(t).to_equal {'foo', 42, nil, nil, 'five'} + - it ignores the dictionary part of a table: + t = {} + for i, v in f {'foo', 42, nil, nil, 'five'; bar='baz', qux='quux'} do + t[i] = v + end + expect(t).to_equal {'foo', 42, nil, nil, 'five'} + - it respects __len metamethod: + t = {} + for _, v in f(setmetatable({[2]=false}, {__len=function(self) return 4 end})) do + t[#t + 1] = tostring(v) + end + expect(table.concat(t, ',')).to_be 'nil,false,nil,nil' + - it works for an empty list: + t = {} + for i, v in f {} do + t[i] = v + end + expect(t).to_equal {} + + +- describe pairs: + - before: + f = M.pairs + + - context with bad arguments: + badargs.diagnose(f, 'std.pairs(table)') + + - it is an iterator over all table values: + t = {} + for k, v in f {'foo', bar='baz', 42} do + t[k] = v + end + expect(t).to_equal {'foo', bar='baz', 42} + - it respects __pairs metamethod: | + t = {} + for k, v in f(__pairs) do + t[k] = v + end + expect(t). + to_contain.a_permutation_of {'a', ' ', 's', 't', 'r', 'i', 'n', 'g'} + - it works for an empty list: + t = {} + for k, v in f {} do + t[k] = v + end + expect(t).to_equal {} + + +- describe require: + - before: + f = M.require + + - context with bad arguments: + badargs.diagnose(f, 'std.require(string, ?string, ?string, ?string)') + + - it diagnoses non-existent module: + expect(f('module-not-exists', '', '')).to_raise 'module-not-exists' + - it diagnoses module too old: + expect(f('std', '9999', '9999')). + to_raise "require 'std' with at least version 9999," + - it diagnoses module too new: + expect(f('std', '0', '0')). + to_raise "require 'std' with version less than 0," + - context when the module version is compatible: + - it returns the module table: + expect(f('std', '0', '9999')).to_be(require 'std') + - it places no upper bound by default: + expect(f('std', '0')).to_be(require 'std') + - it places no lower bound by default: + expect(f 'std').to_be(require 'std') + - it uses _VERSION when version field is nil: + expect(luaproc [[ + package.loaded['poop'] = {_VERSION='41.1'} + f = require 'std'.require + print(f('poop', '41', '9999')._VERSION) + ]]).to_succeed_with '41.1\n' + - context with semantic versioning: + - before: + std = require 'std' + ver = std.version + std.version = '1.2.3' + - after: + std.version = ver + - it diagnoses module too old: + expect(f('std', '1.2.4')). + to_raise "require 'std' with at least version 1.2.4," + expect(f('std', '1.3')). + to_raise "require 'std' with at least version 1.3," + expect(f('std', '2.1.2')). + to_raise "require 'std' with at least version 2.1.2," + expect(f('std', '2')). + to_raise "require 'std' with at least version 2," + expect(f('std', '1.2.10')). + to_raise "require 'std' with at least version 1.2.10," + - it diagnoses module too new: + expect(f('std', nil, '1.2.2')). + to_raise "require 'std' with version less than 1.2.2," + expect(f('std', nil, '1.1')). + to_raise "require 'std' with version less than 1.1," + expect(f('std', nil, '1.1.2')). + to_raise "require 'std' with version less than 1.1.2," + expect(f('std', nil, '1')). + to_raise "require 'std' with version less than 1," + - it returns modules with version in range: + expect(f('std')).to_be(std) + expect(f('std', '1')).to_be(std) + expect(f('std', '1.2.3')).to_be(std) + expect(f('std', nil, '2')).to_be(std) + expect(f('std', nil, '1.3')).to_be(std) + expect(f('std', nil, '1.2.10')).to_be(std) + expect(f('std', '1.2.3', '1.2.4')).to_be(std) + - context with several numbers in version string: + - before: + std = require 'std' + ver = std.version + std.version = 'standard library for Lua 5.3 / 41.0.0' + - after: + std.version = ver + - it diagnoses module too old: + expect(f('std', '42')).to_raise() + - it diagnoses module too new: + expect(f('std', nil, '40')).to_raise() + - it returns modules with version in range: + expect(f('std')).to_be(std) + expect(f('std', '1')).to_be(std) + expect(f('std', '41')).to_be(std) + expect(f('std', nil, '42')).to_be(std) + expect(f('std', '41', '42')).to_be(std) + + +- describe ripairs: + - before: + f = M.ripairs + + - context with bad arguments: + badargs.diagnose(f, 'std.ripairs(table)') + + - it returns a function, the table and a number: + fn, t, i = f {1, 2, 3} + expect({type(fn), t, type(i)}).to_equal {'function', {1, 2, 3}, 'number'} + - it iterates over the array part of a table: + t, u = {1, 2, 3; a=4, b=5, c=6}, {} + for i, v in f(t) do + u[i] = v + end + expect(u).to_equal {1, 2, 3} + - it returns elements in reverse order: + t, u = {'one', 'two', 'five'}, {} + for _, v in f(t) do + u[#u + 1] = v + end + expect(u).to_equal {'five', 'two', 'one'} + - it respects __len metamethod: + t = {} + for i, v in f(__index) do + t[i] = v + end + expect(t).to_equal {'a', ' ', 's', 't', 'r', 'i', 'n', 'g'} + t = {} + for _, v in f(__index) do + t[#t + 1] = v + end + expect(t).to_equal {'g', 'n', 'i', 'r', 't', 's', ' ', 'a'} + - it works with the empty list: + t = {} + for k, v in f {} do + t[k] = v + end + expect(t).to_equal {} + + +- describe rnpairs: + - before: + f = M.rnpairs + + - context with bad arguments: + badargs.diagnose(f, 'std.rnpairs(table)') + + - it returns a function, the table and a number: + fn, t, i = f {1, 2, nil, nil, 3} + expect({type(fn), t, type(i)}). + to_equal {'function', {1, 2, nil, nil, 3}, 'number'} + - it iterates over the array part of a table: + t, u = {1, 2, nil, nil, 3; a=4, b=5, c=6}, {} + for i, v in f(t) do + u[i] = v + end + expect(u).to_equal {1, 2, nil, nil, 3} + - it returns elements in reverse order: + t, u, i = {'one', 'two', nil, nil, 'five'}, {}, 1 + for _, v in f(t) do + u[i], i = v, i + 1 + end + expect(u).to_equal {'five', nil, nil, 'two', 'one'} + - it respects __len metamethod: + t = {} + for _, v in f(setmetatable({[2]=false}, {__len=function(self) return 4 end})) do + t[#t + 1] = tostring(v) + end + expect(table.concat(t, ',')).to_be 'nil,nil,false,nil' + - it works with the empty list: + t = {} + for k, v in f {} do + t[k] = v + end + expect(t).to_equal {} diff --git a/Data/BuiltIn/Libraries/lua-stdlib/spec/string_spec.yaml b/Data/BuiltIn/Libraries/lua-stdlib/spec/string_spec.yaml new file mode 100644 index 0000000..2fa47f2 --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-stdlib/spec/string_spec.yaml @@ -0,0 +1,549 @@ +# General Lua Libraries for Lua 5.1, 5.2 & 5.3 +# Copyright (C) 2011-2018 stdlib authors + +before: + base_module = 'string' + this_module = 'std.string' + global_table = '_G' + + extend_base = {'__concat', '__index', + 'caps', 'chomp', 'escape_pattern', 'escape_shell', + 'finds', 'format', 'ltrim', + 'numbertosi', 'ordinal_suffix', 'pad', + 'prettytostring', 'rtrim', 'split', + 'tfind', 'trim', 'wrap'} + + M = require(this_module) + getmetatable('').__concat = M.__concat + getmetatable('').__index = M.__index + +specify std.string: +- before: + subject = 'a string \n\n' + +- context when required: + - context by name: + - it does not touch the global table: + expect(show_apis {added_to=global_table, by=this_module}). + to_equal {} + - it does not touch the core string table: + expect(show_apis {added_to=base_module, by=this_module}). + to_equal {} + - it contains apis from the core string table: + expect(show_apis {from=base_module, not_in=this_module}). + to_contain.a_permutation_of(extend_base) + + - context via the std module: + - it does not touch the global table: + expect(show_apis {added_to=global_table, by='std'}). + to_equal {} + - it does not touch the core string table: + expect(show_apis {added_to=base_module, by='std'}). + to_equal {} + +- describe ..: + - it concatenates string arguments: + target = 'a string \n\n another string' + expect(subject .. ' another string').to_be(target) + - it stringifies non-string arguments: + argument = {'a table'} + expect(subject .. argument).to_be(subject .. '{1="a table"}') + - it stringifies nil arguments: + argument = nil + expect(subject .. argument). + to_be(string.format('%s%s', subject, require 'std.normalize'.str(argument))) + - it does not perturb the original subject: + original = subject + newstring = subject .. ' concatenate something' + expect(subject).to_be(original) + + +- describe caps: + - before: + f = M.caps + + - context with bad arguments: + badargs.diagnose(f, 'std.string.caps(string)') + + - it capitalises words of a string: + target = 'A String \n\n' + expect(f(subject)).to_be(target) + - it changes only the first letter of each word: + expect(f 'a stRiNg').to_be 'A StRiNg' + - it is available as a string metamethod: + expect(('a stRiNg'):caps()).to_be 'A StRiNg' + - it does not perturb the original subject: + original = subject + newstring = f(subject) + expect(subject).to_be(original) + + +- describe chomp: + - before: + target = 'a string \n' + f = M.chomp + + - context with bad arguments: + badargs.diagnose(f, 'std.string.chomp(string)') + + - it removes a single trailing newline from a string: + expect(f(subject)).to_be(target) + - it does not change a string with no trailing newline: + subject = 'a string ' + expect(f(subject)).to_be(subject) + - it is available as a string metamethod: + expect(subject:chomp()).to_be(target) + - it does not perturb the original subject: + original = subject + newstring = f(subject) + expect(subject).to_be(original) + + +- describe escape_pattern: + - before: + magic = {} + meta = '^$()%.[]*+-?' + for i = 1, string.len(meta) do + magic[meta:sub(i, i)] = true + end + f = M.escape_pattern + + - context with bad arguments: + badargs.diagnose(f, 'std.string.escape_pattern(string)') + + - context with each printable ASCII char: + - before: + subject, target = '', '' + for c = 32, 126 do + s = string.char(c) + subject = subject .. s + if magic[s] then + target = target .. '%' + end + target = target .. s + end + - 'it inserts a % before any non-alphanumeric in a string': + expect(f(subject)).to_be(target) + - it is available as a string metamethod: + expect(subject:escape_pattern()).to_be(target) + - it does not perturb the original subject: + original = subject + newstring = f(subject) + expect(subject).to_be(original) + + +- describe escape_shell: + - before: + f = M.escape_shell + + - context with bad arguments: + badargs.diagnose(f, 'std.string.escape_shell(string)') + + - context with each printable ASCII char: + - before: + subject, target = '', '' + for c = 32, 126 do + s = string.char(c) + subject = subject .. s + if s:match('[][ ()\\\'"]') then + target = target .. '\\' + end + target = target .. s + end + - 'it inserts a \\ before any shell metacharacters': + expect(f(subject)).to_be(target) + - it is available as a string metamethod: + expect(subject:escape_shell()).to_be(target) + - it does not perturb the original subject: + original = subject + newstring = f(subject) + expect(subject).to_be(original) + - 'it diagnoses non-string arguments': + if typecheck then + expect(f()).to_raise('string expected') + expect(f {'a table'}).to_raise('string expected') + end + + +- describe finds: + - before: + subject = 'abcd' + f = M.finds + + - context with bad arguments: + badargs.diagnose(f, 'std.string.finds(string, string, ?int, ?boolean|:plain)') + + - context given a complex nested list: + - before: + target = {{1, 2; capt={'a', 'b'}}, {3, 4; capt={'c', 'd'}}} + - it creates a list of pattern captures: + expect({f(subject, '(.)(.)')}).to_equal({target}) + - it is available as a string metamethod: + expect({subject:finds('(.)(.)')}).to_equal({target}) + - it creates an empty list where no captures are matched: + target = {} + expect({f(subject, '(x)')}).to_equal({target}) + - it creates an empty list for a pattern without captures: + target = {{1, 1; capt={}}} + expect({f(subject, 'a')}).to_equal({target}) + - it starts the search at a specified index into the subject: + target = {{8, 9; capt={'a', 'b'}}, {10, 11; capt={'c', 'd'}}} + expect({f('garbage' .. subject, '(.)(.)', 8)}).to_equal({target}) + - it does not perturb the original subject: + original = subject + newstring = f(subject, '...') + expect(subject).to_be(original) + + +- describe format: + - before: + subject = 'string=%s, number=%d' + + f = M.format + + - context with bad arguments: + badargs.diagnose(f, 'std.string.format(string, ?any*)') + + - it returns a single argument without attempting formatting: + expect(f(subject)).to_be(subject) + - it is available as a string metamethod: + expect(subject:format()).to_be(subject) + - it does not perturb the original subject: + original = subject + newstring = f(subject) + expect(subject).to_be(original) + + +- describe ltrim: + - before: + subject = ' \t\r\n a short string \t\r\n ' + + f = M.ltrim + + - context with bad arguments: + badargs.diagnose(f, 'std.string.ltrim(string, ?string)') + + - it removes whitespace from the start of a string: + target = 'a short string \t\r\n ' + expect(f(subject)).to_equal(target) + - it supports custom removal patterns: + target = '\r\n a short string \t\r\n ' + expect(f(subject, '[ \t\n]+')).to_equal(target) + - it is available as a string metamethod: + target = '\r\n a short string \t\r\n ' + expect(subject:ltrim('[ \t\n]+')).to_equal(target) + - it does not perturb the original subject: + original = subject + newstring = f(subject, '%W') + expect(subject).to_be(original) + + +- describe numbertosi: + - before: + f = M.numbertosi + + - context with bad arguments: + badargs.diagnose(f, 'std.string.numbertosi(number|string)') + + - it returns a number using SI suffixes: + target = {'1e-9', '1y', '1z', '1a', '1f', '1p', '1n', '1mu', '1m', '1', + '1k', '1M', '1G', '1T', '1P', '1E', '1Z', '1Y', '1e9'} + subject = {} + for n = -28, 28, 3 do + m = 10 *(10 ^ n) + table.insert(subject, f(m)) + end + expect(subject).to_equal(target) + - it coerces string arguments to a number: + expect(f '1000').to_be '1k' + + +- describe ordinal_suffix: + - before: + f = M.ordinal_suffix + + - context with bad arguments: + badargs.diagnose(f, 'std.string.ordinal_suffix(int|string)') + + - it returns the English suffix for a number: + subject, target = {}, {} + for n = -120, 120 do + suffix = 'th' + m = math.abs(n) % 10 + if m == 1 and math.abs(n) % 100 ~= 11 then + suffix = 'st' + elseif m == 2 and math.abs(n) % 100 ~= 12 then + suffix = 'nd' + elseif m == 3 and math.abs(n) % 100 ~= 13 then + suffix = 'rd' + end + table.insert(target, n .. suffix) + table.insert(subject, n .. f(n)) + end + expect(subject).to_equal(target) + - it coerces string arguments to a number: + expect(f '-91').to_be 'st' + + +- describe pad: + - before: + width = 20 + + f = M.pad + + - context with bad arguments: + badargs.diagnose(f, 'std.string.pad(string, int, ?string)') + + - context when string is shorter than given width: + - before: + subject = 'short string' + - it right pads a string to the given width with spaces: + target = 'short string ' + expect(f(subject, width)).to_be(target) + - it left pads a string to the given negative width with spaces: + width = -width + target = ' short string' + expect(f(subject, width)).to_be(target) + - it is available as a string metamethod: + target = 'short string ' + expect(subject:pad(width)).to_be(target) + + - context when string is longer than given width: + - before: + subject = "a string that's longer than twenty characters" + - it truncates a string to the given width: + target = "a string that's long" + expect(f(subject, width)).to_be(target) + - it left pads a string to given width with spaces: + width = -width + target = 'an twenty characters' + expect(f(subject, width)).to_be(target) + - it is available as a string metamethod: + target = "a string that's long" + expect(subject:pad(width)).to_be(target) + + - it does not perturb the original subject: + original = subject + newstring = f(subject, width) + expect(subject).to_be(original) + + +- describe prettytostring: + - before: + f = M.prettytostring + + - context with bad arguments: + badargs.diagnose(f, 'std.string.prettytostring(?any, ?string, ?string)') + + - it renders nil exactly like system tostring: + expect(f(nil)).to_be(tostring(nil)) + - it renders booleans exactly like system tostring: + expect(f(true)).to_be(tostring(true)) + expect(f(false)).to_be(tostring(false)) + - it renders numbers exactly like system tostring: + n = 8723643 + expect(f(n)).to_be(tostring(n)) + - it renders functions exactly like system tostring: + expect(f(f)).to_be(tostring(f)) + - it renders strings with format '%q' styling: + s = 'a string' + expect(f(s)).to_be(string.format('%q', s)) + - it renders empty tables as a pair of braces: + expect(f {}).to_be('{\n}') + - it renders an array prettily: + a = {'one', 'two', 'three'} + expect(f(a, '')). + to_be '{\n[1] = "one",\n[2] = "two",\n[3] = "three",\n}' + - it renders a table prettily: + t = {one=true, two=2, three={3}} + expect(f(t, '')). + to_be '{\none = true,\nthree =\n{\n[1] = 3,\n},\ntwo = 2,\n}' + - it renders table keys in table.sort order: + t = {one=3, two=5, three=4, four=2, five=1} + expect(f(t, '')). + to_be '{\nfive = 1,\nfour = 2,\none = 3,\nthree = 4,\ntwo = 5,\n}' + - it renders keys with invalid symbol names in long hand: + t = {_=0, word=0, ['?']=1, ['a-key']=1, ['[]']=1} + expect(f(t, '')). + to_be '{\n["?"] = 1,\n["[]"] = 1,\n_ = 0,\n["a-key"] = 1,\nword = 0,\n}' + + +- describe rtrim: + - before: + subject = ' \t\r\n a short string \t\r\n ' + + f = M.rtrim + + - context with bad arguments: + badargs.diagnose(f, 'std.string.rtrim(string, ?string)') + + - it removes whitespace from the end of a string: + target = ' \t\r\n a short string' + expect(f(subject)).to_equal(target) + - it supports custom removal patterns: + target = ' \t\r\n a short string \t\r' + expect(f(subject, '[ \t\n]+')).to_equal(target) + - it is available as a string metamethod: + target = ' \t\r\n a short string \t\r' + expect(subject:rtrim('[ \t\n]+')).to_equal(target) + - it does not perturb the original subject: + original = subject + newstring = f(subject, '%W') + expect(subject).to_be(original) + + +- describe split: + - before: + target = {'first', 'the second one', 'final entry'} + subject = table.concat(target, ', ') + + f = M.split + + - context with bad arguments: + badargs.diagnose(f, 'std.string.split(string, ?string)') + + - it falls back to '%s+' when no pattern is given: + expect(f(subject)). + to_equal {'first,', 'the', 'second', 'one,', 'final', 'entry'} + - it returns a one-element list for an empty string: + expect(f('', ', ')).to_equal {''} + - it makes a table of substrings delimited by a separator: + expect(f(subject, ', ')).to_equal(target) + - it returns n+1 elements for n separators: + expect(f(subject, 'zero')).to_have_size(1) + expect(f(subject, 'c')).to_have_size(2) + expect(f(subject, 's')).to_have_size(3) + expect(f(subject, 't')).to_have_size(4) + expect(f(subject, 'e')).to_have_size(5) + - it returns an empty string element for consecutive separators: + expect(f('xyzyzxy', 'yz')).to_equal {'x', '', 'xy'} + - it returns an empty string element when starting with separator: + expect(f('xyzyzxy', 'xyz')).to_equal {'', 'yzxy'} + - it returns an empty string element when ending with separator: + expect(f('xyzyzxy', 'zxy')).to_equal {'xyzy', ''} + - it returns a table of 1-character strings for '' separator: + expect(f('abcdef', '')).to_equal {'', 'a', 'b', 'c', 'd', 'e', 'f', ''} + - it is available as a string metamethod: + expect(subject:split ', ').to_equal(target) + expect(('/foo/bar/baz.quux'):split '/'). + to_equal {'', 'foo', 'bar', 'baz.quux'} + - it does not perturb the original subject: + original = subject + newstring = f(subject, 'e') + expect(subject).to_be(original) + - it takes a Lua pattern as a separator: + expect(f(subject, '%s+')). + to_equal {'first,', 'the', 'second', 'one,', 'final', 'entry'} + + +- describe tfind: + - before: + subject = 'abc' + + f = M.tfind + + - context with bad arguments: + badargs.diagnose(f, 'std.string.tfind(string, string, ?int, ?boolean|:plain)') + + - it creates a list of pattern captures: + target = {1, 3, {'a', 'b', 'c'}} + expect({f(subject, '(.)(.)(.)')}).to_equal(target) + - it creates an empty list where no captures are matched: + target = {nil, nil, {}} + expect({f(subject, '(x)(y)(z)')}).to_equal(target) + - it creates an empty list for a pattern without captures: + target = {1, 1, {}} + expect({f(subject, 'a')}).to_equal(target) + - it starts the search at a specified index into the subject: + target = {8, 10, {'a', 'b', 'c'}} + expect({f('garbage' .. subject, '(.)(.)(.)', 8)}).to_equal(target) + - it is available as a string metamethod: + target = {8, 10, {'a', 'b', 'c'}} + expect({('garbage' .. subject):tfind('(.)(.)(.)', 8)}).to_equal(target) + - it does not perturb the original subject: + original = subject + newstring = f(subject, '...') + expect(subject).to_be(original) + + +- describe trim: + - before: + subject = ' \t\r\n a short string \t\r\n ' + + f = M.trim + + - context with bad arguments: + badargs.diagnose(f, 'std.string.trim(string, ?string)') + + - it removes whitespace from each end of a string: + target = 'a short string' + expect(f(subject)).to_equal(target) + - it supports custom removal patterns: + target = '\r\n a short string \t\r' + expect(f(subject, '[ \t\n]+')).to_equal(target) + - it is available as a string metamethod: + target = '\r\n a short string \t\r' + expect(subject:trim('[ \t\n]+')).to_equal(target) + - it does not perturb the original subject: + original = subject + newstring = f(subject, '%W') + expect(subject).to_be(original) + + +- describe wrap: + - before: + subject = 'This is a collection of Lua libraries for Lua 5.1 ' .. + 'and 5.2. The libraries are copyright by their authors 2000' .. + '-2015 (see the AUTHORS file for details), and released und' .. + 'er the MIT license (the same license as Lua itself). There' .. + ' is no warranty.' + + f = M.wrap + + - context with bad arguments: + badargs.diagnose(f, 'std.string.wrap(string, ?int, ?int, ?int)') + + - it inserts newlines to wrap a string: + target = 'This is a collection of Lua libraries for Lua 5.1 a' .. + 'nd 5.2. The libraries are\ncopyright by their authors 2000' .. + '-2015 (see the AUTHORS file for details), and\nreleased un' .. + 'der the MIT license (the same license as Lua itself). Ther' .. + 'e is no\nwarranty.' + expect(f(subject)).to_be(target) + - it honours a column width parameter: + target = 'This is a collection of Lua libraries for Lua 5.1 a' .. + 'nd 5.2. The libraries\nare copyright by their authors 2000' .. + '-2015 (see the AUTHORS file for\ndetails), and released un' .. + 'der the MIT license (the same license as Lua\nitself). The' .. + 're is no warranty.' + expect(f(subject, 72)).to_be(target) + - it supports indenting by a fixed number of columns: + target = ' This is a collection of Lua libraries for L' .. + 'ua 5.1 and 5.2. The\n libraries are copyright by th' .. + 'eir authors 2000-2015 (see the\n AUTHORS file for d' .. + 'etails), and released under the MIT license\n (the ' .. + 'same license as Lua itself). There is no warranty.' + expect(f(subject, 72, 8)).to_be(target) + - context given a long unwrapped string: + - before: + target = ' This is a collection of Lua libraries for Lua 5' .. + '.1 and 5.2.\n The libraries are copyright by their author' .. + 's 2000-2015 (see\n the AUTHORS file for details), and rel' .. + 'eased under the MIT\n license (the same license as Lua it' .. + 'self). There is no\n warranty.' + - it can indent the first line differently: + expect(f(subject, 64, 2, 4)).to_be(target) + - it is available as a string metamethod: + expect(subject:wrap(64, 2, 4)).to_be(target) + - it does not perturb the original subject: + original = subject + newstring = f(subject, 55, 5) + expect(subject).to_be(original) + - it diagnoses indent greater than line width: + expect(f(subject, 10, 12)).to_raise('less than the line width') + expect(f(subject, 99, 99)).to_raise('less than the line width') + - it diagnoses non-string arguments: + if have_typecheck then + expect(f()).to_raise('string expected') + expect(f {'a table'}).to_raise('string expected') + end diff --git a/Data/BuiltIn/Libraries/lua-stdlib/spec/table_spec.yaml b/Data/BuiltIn/Libraries/lua-stdlib/spec/table_spec.yaml new file mode 100644 index 0000000..d9fed0c --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-stdlib/spec/table_spec.yaml @@ -0,0 +1,589 @@ +# General Lua Libraries for Lua 5.1, 5.2 & 5.3 +# Copyright (C) 2011-2018 stdlib authors + +before: | + base_module = 'table' + this_module = 'std.table' + global_table = '_G' + + extend_base = {'clone', 'clone_select', 'depair', 'empty', + 'enpair', 'insert', 'invert', 'keys', 'maxn', + 'merge', 'merge_select', 'new', + 'pack', 'project', 'remove', 'size', 'sort', + 'unpack', 'values'} + + M = require(this_module) + + +specify std.table: +- context when required: + - context by name: + - it does not touch the global table: + expect(show_apis {added_to=global_table, by=this_module}). + to_equal {} + - it does not touch the core table table: + expect(show_apis {added_to=base_module, by=this_module}). + to_equal {} + - it contains apis from the core table table: + expect(show_apis {from=base_module, not_in=this_module}). + to_contain.a_permutation_of(extend_base) + + - context via the std module: + - it does not touch the global table: + expect(show_apis {added_to=global_table, by='std'}).to_equal {} + - it does not touch the core table table: + expect(show_apis {added_to=base_module, by='std'}).to_equal {} + + +- describe clone: + - before: + subject = {k1={'v1'}, k2={'v2'}, k3={'v3'}} + withmt = setmetatable(M.clone(subject), {'meta!'}) + + f = M.clone + + - context with bad arguments: + badargs.diagnose(f, 'std.table.clone(table, [table], ?boolean|:nometa)') + + - it does not just return the subject: + expect(f(subject)).not_to_be(subject) + - it does copy the subject: + expect(f(subject)).to_equal(subject) + - it only makes a shallow copy of field values: + expect(f(subject).k1).to_be(subject.k1) + - it does not perturb the original subject: + target = {k1=subject.k1, k2=subject.k2, k3=subject.k3} + copy = f(subject) + expect(subject).to_equal(target) + expect(subject).to_be(subject) + + - context with metatables: + - it copies the metatable by default: + expect(getmetatable(f(withmt))).to_be(getmetatable(withmt)) + - it treats non-table arg2 as nometa parameter: + expect(getmetatable(f(withmt, ':nometa'))).to_be(nil) + - it treats table arg2 as a map parameter: + expect(getmetatable(f(withmt, {}))).to_be(getmetatable(withmt)) + - it supports 3 arguments with nometa as arg3: + expect(getmetatable(f(withmt, {}, ':nometa'))).to_be(nil) + + - context when renaming some keys: + - it renames during cloning: + target = {newkey=subject.k1, k2=subject.k2, k3=subject.k3} + expect(f(subject, {k1 = 'newkey'})).to_equal(target) + - it does not perturb the value in the renamed key field: + expect(f(subject, {k1 = 'newkey'}).newkey).to_be(subject.k1) + + +- describe clone_select: + - before: + subject = {k1={'v1'}, k2={'v2'}, k3={'v3'}} + withmt = setmetatable(M.clone(subject), {'meta!'}) + + f = M.clone_select + + - context with bad arguments: + badargs.diagnose(f, 'std.table.clone_select(table, [table], ?boolean|:nometa)') + + - it does not just return the subject: + expect(f(subject)).not_to_be(subject) + - it copies the keys selected: + expect(f(subject, {'k1', 'k2'})).to_equal({k1={'v1'}, k2={'v2'}}) + - it does copy the subject when supplied with a full list of keys: + expect(f(subject, {'k1', 'k2', 'k3'})).to_equal(subject) + - it only makes a shallow copy: + expect(f(subject, {'k1'}).k1).to_be(subject.k1) + - it does not perturb the original subject: + target = {k1=subject.k1, k2=subject.k2, k3=subject.k3} + copy = f(subject, {'k1', 'k2', 'k3'}) + expect(subject).to_equal(target) + expect(subject).to_be(subject) + + - context with metatables: + - it treats non-table arg2 as nometa parameter: + expect(getmetatable(f(withmt, ':nometa'))).to_be(nil) + - it treats table arg2 as a map parameter: + expect(getmetatable(f(withmt, {}))).to_be(getmetatable(withmt)) + expect(getmetatable(f(withmt, {'k1'}))).to_be(getmetatable(withmt)) + - it supports 3 arguments with nometa as arg3: + expect(getmetatable(f(withmt, {}, ':nometa'))).to_be(nil) + expect(getmetatable(f(withmt, {'k1'}, ':nometa'))).to_be(nil) + + +- describe depair: + - before: + t = {'first', 'second', third = 4} + l = M.enpair(t) + + f = M.depair + + - context with bad arguments: + badargs.diagnose(f, 'std.table.depair(list of lists)') + + - it returns a primitive table: + expect(objtype(f(l))).to_be 'table' + - it works with an empty table: + expect(f {}).to_equal {} + - it is the inverse of enpair: + expect(f(l)).to_equal(t) + + +- describe empty: + - before: + f = M.empty + + - context with bad arguments: + badargs.diagnose(f, 'std.table.empty(table)') + + - it returns true for an empty table: + expect(f {}).to_be(true) + expect(f {nil}).to_be(true) + - it returns false for a non-empty table: + expect(f {'stuff'}).to_be(false) + expect(f {{}}).to_be(false) + expect(f {false}).to_be(false) + + +- describe enpair: + - before: + t = {'first', 'second', third = 4} + l = M.enpair(t) + + f = M.enpair + + - context with bad arguments: + badargs.diagnose(f, 'std.table.enpair(table)') + + - it returns a table: + expect(objtype(f(t))).to_be 'table' + - it works for an empty table: + expect(f {}).to_equal {} + - it turns a table into a table of pairs: + expect(f(t)).to_equal {{1, 'first'}, {2, 'second'}, {'third', 4}} + - it is the inverse of depair: + expect(f(t)).to_equal(l) + + +- describe insert: + - before: + f, badarg = init(M, this_module, 'insert') + + - context with bad arguments: + badargs.diagnose(f, 'std.table.insert(table, [int], any)') + + examples { + ['it diagnoses more than 2 arguments with no pos'] = function() + pending '#issue 76' + expect(f({}, false, false)).to_raise(badarg(3)) + end + } + examples { + ['it diagnoses out of bounds pos arguments'] = function() + expect(f({}, 0, 'x')).to_raise 'position 0 out of bounds' + expect(f({}, 2, 'x')).to_raise 'position 2 out of bounds' + expect(f({1}, 5, 'x')).to_raise 'position 5 out of bounds' + end + } + + - it returns the modified table: + t = {} + expect(f(t, 1)).to_be(t) + - it append a new element at the end by default: + expect(f({1, 2}, 'x')).to_equal {1, 2, 'x'} + - it fills holes by default: + expect(f({1, 2, [5]=3}, 'x')).to_equal {1, 2, 'x', [5]=3} + - it respects __len when appending: + t = setmetatable({1, 2, [5]=3}, {__len = function() return 42 end}) + expect(f(t, 'x')).to_equal {1, 2, [5]=3, [43]='x'} + - it moves other elements up if necessary: + expect(f({1, 2}, 1, 'x')).to_equal {'x', 1, 2} + expect(f({1, 2}, 2, 'x')).to_equal {1, 'x', 2} + expect(f({1, 2}, 3, 'x')).to_equal {1, 2, 'x'} + - it inserts a new element according to pos argument: + expect(f({}, 1, 'x')).to_equal {'x'} + + +- describe invert: + - before: + subject = {k1=1, k2=2, k3=3} + + f = M.invert + + - context with bad arguments: + badargs.diagnose(f, 'std.table.invert(table)') + + - it returns a new table: + expect(f(subject)).not_to_be(subject) + - it inverts keys and values in the returned table: + expect(f(subject)).to_equal {'k1', 'k2', 'k3'} + - it is reversible: + expect(f(f(subject))).to_equal(subject) + - it seems to copy a list of 1..n numbers: + subject = {1, 2, 3} + expect(f(subject)).to_copy(subject) + + +- describe keys: + - before: + subject = {k1=1, k2=2, k3=3} + + f = M.keys + + - context with bad arguments: + badargs.diagnose(f, 'std.table.keys(table)') + + - it returns an empty list when subject is empty: + expect(f {}).to_equal {} + - it makes a list of table keys: + cmp = function(a, b) + return a < b + end + expect(M.sort(f(subject), cmp)).to_equal {'k1', 'k2', 'k3'} + - it does not guarantee stable ordering: + subject = {} + -- is this a good test? there is a vanishingly small possibility the + -- returned table will have all 10000 keys in the same order... + for i = 10000, 1, -1 do + table.insert(subject, i) + end + expect(f(subject)).not_to_equal(subject) + + +- describe maxn: + - before: + f = M.maxn + + - context with bad arguments: + badargs.diagnose(f, 'std.table.maxn(table)') + + - it returns the largest numeric key of a table: + expect(f {'a', 'b', 'c'}).to_be(3) + expect(f {1, 2, 5, a=10, 3}).to_be(4) + - it works with an empty table: + expect(f {}).to_be(0) + - it ignores holes: + expect(f {1, 2, [5]=3}).to_be(5) + - it ignores __len metamethod: + t = setmetatable({1, 2, [5]=3}, {__len = function() return 42 end}) + expect(f(t)).to_be(5) + + +- describe merge: + - before: | + -- Additional merge keys which are moderately unusual + t1 = {k1={'v1'}, k2='if', k3={'?'}} + t2 = {['if']=true, [{'?'}]=false, _='underscore', k3=t1.k1} + t1mt = setmetatable(M.clone(t1), {'meta!'}) + target = {} + for k, v in pairs(t1) do + target[k] = v + end + for k, v in pairs(t2) do + target[k] = v + end + + f, badarg = init(M, this_module, 'merge') + + - context with bad arguments: + badargs.diagnose(f, 'std.table.merge(table, table, [table], ?boolean|:nometa)') + + examples { + ['it diagnoses more than 2 arguments with no pos'] = function() + pending '#issue 76' + expect(f({}, {}, ':nometa', false)).to_raise(badarg(4)) + end + } + + - it does not create a whole new table: + expect(f(t1, t2)).to_be(t1) + - it does not change t1 when t2 is empty: + expect(f(t1, {})).to_be(t1) + - it copies t2 when t1 is empty: + expect(f({}, t1)).to_copy(t1) + - it merges keys from t2 into t1: + expect(f(t1, t2)).to_equal(target) + - it gives precedence to values from t2: + original = M.clone(t1) + m = f(t1, t2) -- Merge is destructive, do it once only. + expect(m.k3).to_be(t2.k3) + expect(m.k3).not_to_be(original.k3) + - it only makes a shallow copy of field values: + expect(f({}, t1).k1).to_be(t1.k1) + + - context with metatables: + - it copies the metatable by default: + expect(getmetatable(f({}, t1mt))).to_be(getmetatable(t1mt)) + expect(getmetatable(f({}, t1mt, {'k1'}))).to_be(getmetatable(t1mt)) + - it treats non-table arg3 as nometa parameter: + expect(getmetatable(f({}, t1mt, ':nometa'))).to_be(nil) + - it treats table arg3 as a map parameter: + expect(getmetatable(f({}, t1mt, {}))).to_be(getmetatable(t1mt)) + expect(getmetatable(f({}, t1mt, {'k1'}))).to_be(getmetatable(t1mt)) + - it supports 4 arguments with nometa as arg4: + expect(getmetatable(f({}, t1mt, {}, ':nometa'))).to_be(nil) + expect(getmetatable(f({}, t1mt, {'k1'}, ':nometa'))).to_be(nil) + + - context when renaming some keys: + - it renames during merging: + target = {newkey=t1.k1, k2=t1.k2, k3=t1.k3} + expect(f({}, t1, {k1 = 'newkey'})).to_equal(target) + - it does not perturb the value in the renamed key field: + expect(f({}, t1, {k1 = 'newkey'}).newkey).to_be(t1.k1) + + +- describe merge_select: + - before: | + -- Additional merge keys which are moderately unusual + tablekey = {'?'} + t1 = {k1={'v1'}, k2='if', k3={'?'}} + t1mt = setmetatable(M.clone(t1), {'meta!'}) + t2 = {['if']=true, [tablekey]=false, _='underscore', k3=t1.k1} + t2keys = {'if', tablekey, '_', 'k3'} + target = {} + for k, v in pairs(t1) do + target[k] = v + end + for k, v in pairs(t2) do + target[k] = v + end + + f, badarg = init(M, this_module, 'merge_select') + + - context with bad arguments: + badargs.diagnose(f, 'std.table.merge_select(table, table, [table], ?boolean|:nometa)') + + examples { + ['it diagnoses more than 2 arguments with no pos'] = function() + pending '#issue 76' + expect(f({}, {}, ':nometa', false)).to_raise(badarg(4)) + end + } + + - it does not create a whole new table: + expect(f(t1, t2)).to_be(t1) + - it does not change t1 when t2 is empty: + expect(f(t1, {})).to_be(t1) + - it does not change t1 when key list is empty: + expect(f(t1, t2, {})).to_be(t1) + - it copies the named fields: + expect(f({}, t2, t2keys)).to_equal(t2) + - it makes a shallow copy: + expect(f({}, t1, {'k1'}).k1).to_be(t1.k1) + - it copies exactly named fields of t2 when t1 is empty: + expect(f({}, t1, {'k1', 'k2', 'k3'})).to_copy(t1) + - it merges keys from t2 into t1: + expect(f(t1, t2, t2keys)).to_equal(target) + - it gives precedence to values from t2: + original = M.clone(t1) + m = f(t1, t2, t2keys) -- Merge is destructive, do it once only. + expect(m.k3).to_be(t2.k3) + expect(m.k3).not_to_be(original.k3) + + - context with metatables: + - it copies the metatable by default: + expect(getmetatable(f({}, t1mt))).to_be(getmetatable(t1mt)) + expect(getmetatable(f({}, t1mt, {'k1'}))).to_be(getmetatable(t1mt)) + - it treats non-table arg3 as nometa parameter: + expect(getmetatable(f({}, t1mt, ':nometa'))).to_be(nil) + - it treats table arg3 as a map parameter: + expect(getmetatable(f({}, t1mt, {}))).to_be(getmetatable(t1mt)) + expect(getmetatable(f({}, t1mt, {'k1'}))).to_be(getmetatable(t1mt)) + - it supports 4 arguments with nometa as arg4: + expect(getmetatable(f({}, t1mt, {}, ':nometa'))).to_be(nil) + expect(getmetatable(f({}, t1mt, {'k1'}, ':nometa'))).to_be(nil) + + +- describe new: + - before: + f = M.new + + - context with bad arguments: + badargs.diagnose(f, 'std.table.new(?any, ?table)') + + - context when not setting a default: + - before: default = nil + - it returns a new table when nil is passed: + expect(f(default, nil)).to_equal {} + - it returns any table passed in: + t = {'unique table'} + expect(f(default, t)).to_be(t) + + - context when setting a default: + - before: + default = 'default' + - it returns a new table when nil is passed: + expect(f(default, nil)).to_equal {} + - it returns any table passed in: + t = {'unique table'} + expect(f(default, t)).to_be(t) + + - it returns the stored value for existing keys: + t = f('default') + v = {'unique value'} + t[1] = v + expect(t[1]).to_be(v) + - it returns the constructor default for unset keys: + t = f('default') + expect(t[1]).to_be 'default' + - it returns the actual default object: + default = {'unique object'} + t = f(default) + expect(t[1]).to_be(default) + + +- describe pack: + - before: + unpack = unpack or table.unpack + t = {'one', 'two', 'five', n=3} + f = M.pack + - it creates an empty table with no arguments: + expect(f()).to_equal {n=0} + - it creates a table with arguments as elements: + expect(f('one', 'two', 'five')).to_equal(t) + - it is the inverse operation to unpack: + expect(f(unpack(t))).to_equal(t) + - it saves the tuple length in field n: + expect(f(1, 2, 5).n).to_be(3) + expect(f('', false, nil).n).to_be(3) + expect(f(nil, nil, nil).n).to_be(3) + + +- describe project: + - before: + l = { + {first = false, second = true, third = true}, + {first = 1, second = 2, third = 3}, + {first = '1st', second = '2nd', third = '3rd'}, + } + + f = M.project + + - context with bad arguments: + badargs.diagnose(f, 'std.table.project(any, list of tables)') + + - it returns a table: + expect(objtype(f('third', l))).to_be 'table' + - it works with an empty table: + expect(f('third', {})).to_equal {} + - it projects a table of fields from a table of tables: + expect(f('third', l)).to_equal {true, 3, '3rd'} + - it projects fields with a falsey value correctly: + expect(f('first', l)).to_equal {false, 1, '1st'} + + +- describe remove: + - before: + f = M.remove + + - context with bad arguments: + badargs.diagnose(f, 'std.table.remove(table, ?int)') + + examples { + ['it diagnoses out of bounds pos arguments'] = function() + expect(f({1}, 0)).to_raise 'position 0 out of bounds' + expect(f({1}, 3)).to_raise 'position 3 out of bounds' + expect(f({1}, 5)).to_raise 'position 5 out of bounds' + end + } + + - it returns the removed element: + t = {'one', 'two', 'five'} + expect(f({'one', 2, 5}, 1)).to_be 'one' + - it removes an element from the end by default: + expect(f {1, 2, 'five'}).to_be 'five' + - it ignores holes: + t = {'second', 'first', [5]='invisible'} + expect(f(t)).to_be 'first' + expect(f(t)).to_be 'second' + - it respects __len when defaulting pos: + t = setmetatable({1, 2, [43]='invisible'}, {__len = function() return 42 end}) + expect(f(t)).to_be(nil) + expect(f(t)).to_be(nil) + expect(t).to_equal {1, 2, [43]='invisible'} + - it moves other elements down if necessary: + t = {1, 2, 5, 'third', 'first', 'second', 42} + expect(f(t, 5)).to_be 'first' + expect(t).to_equal {1, 2, 5, 'third', 'second', 42} + expect(f(t, 5)).to_be 'second' + expect(t).to_equal {1, 2, 5, 'third', 42} + expect(f(t, 4)).to_be 'third' + expect(t).to_equal {1, 2, 5, 42} + + +- describe size: + - before: | + -- - 1 - ------- 2 ------- -- 3 -- + subject = {'one', {{'two'}, 'three'}, four=5} + + f = M.size + + - context with bad arguments: + badargs.diagnose(f, 'std.table.size(table)') + + - it counts the number of keys in a table: + expect(f(subject)).to_be(3) + - it counts no keys in an empty table: + expect(f {}).to_be(0) + + +- describe sort: + - before: + subject = {5, 2, 4, 1, 0, 3} + target = {0, 1, 2, 3, 4, 5} + cmp = function(a, b) return a < b end + + f = M.sort + + - context with bad arguments: + badargs.diagnose(f, 'std.table.sort(table, ?function)') + + - it sorts elements in place: + f(subject, cmp) + expect(subject).to_equal(target) + - it returns the sorted table: + expect(f(subject, cmp)).to_equal(target) + + +- describe unpack: + - before: + t = {'one', 'two', 'five'} + f = M.unpack + - it returns nil for an empty table: + expect(f {}).to_be(nil) + - it returns numeric indexed table elements: + expect({f(t)}).to_equal(t) + - it respects __len metamethod: + function two(t) + return setmetatable(t, {__len=function() return 2 end}) + end + expect(pack(f(two {})).n).to_be(2) + expect(pack(f(two(t))).n).to_be(2) + - it returns holes in numeric indices as nil: + expect({f {nil, 2}}).to_equal {[2] = 2} + expect({f {nil, nil, 3}}).to_equal {[3] = 3} + expect({f {1, nil, nil, 4}}).to_equal {1, [4] = 4} + - it is the inverse operation to pack: + expect({f(M.pack('one', 'two', 'five'))}).to_equal(t) + + +- describe values: + - before: + subject = {k1={1}, k2={2}, k3={3}} + + f = M.values + + - context with bad arguments: + badargs.diagnose(f, 'std.table.values(table)') + + - it returns an empty list when subject is empty: + expect(f {}).to_equal {} + - it makes a list of table values: + cmp = function(a, b) return a[1] < b[1] end + expect(M.sort(f(subject), cmp)).to_equal {{1}, {2}, {3}} + - it does guarantee stable ordering: + subject = {} + -- is this a good test? or just requiring an implementation quirk? + for i = 10000, 1, -1 do + table.insert(subject, i) + end + expect(f(subject)).to_equal(subject) diff --git a/Data/BuiltIn/Libraries/lua-stdlib/stdlib-git-1.rockspec b/Data/BuiltIn/Libraries/lua-stdlib/stdlib-git-1.rockspec new file mode 100644 index 0000000..504aee8 --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-stdlib/stdlib-git-1.rockspec @@ -0,0 +1,51 @@ +local _MODREV, _SPECREV = 'git', '-1' + +package = 'stdlib' +version = _MODREV .. _SPECREV + +description = { + summary = 'General Lua Libraries', + detailed = [[ + stdlib is a library of modules for common programming tasks, + including list and table operations, and pretty-printing. + ]], + homepage = 'http://lua-stdlib.github.io/lua-stdlib', + license = 'MIT/X11', +} + +source = (function(gitp) + if gitp then + return { + url = 'git://github.com/lua-stdlib/lua-stdlib.git', + } + else + return { + url = 'http://github.com/lua-stdlib/lua-stdlib/archive/v' .. _MODREV .. '.zip', + dir = 'lua-stdlib-' .. _MODREV, + } + end +end)(_MODREV == 'git') + +dependencies = { + 'lua >= 5.1, < 5.4', + 'std._debug', + 'std.normalize >= 2.0', +} + +if _MODREV == 'git' then + dependencies[#dependencies + 1] = 'ldoc' +end + +build = { + type = 'builtin', + modules = { + std = 'lib/std/init.lua', + ['std._base'] = 'lib/std/_base.lua', + ['std.debug'] = 'lib/std/debug.lua', + ['std.io'] = 'lib/std/io.lua', + ['std.math'] = 'lib/std/math.lua', + ['std.package'] = 'lib/std/package.lua', + ['std.string'] = 'lib/std/string.lua', + ['std.table'] = 'lib/std/table.lua', + }, +} diff --git a/Data/BuiltIn/Libraries/lua-stdlib/string.lua b/Data/BuiltIn/Libraries/lua-stdlib/string.lua new file mode 100644 index 0000000..6ad9014 --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-stdlib/string.lua @@ -0,0 +1,505 @@ +--[[ + General Lua Libraries for Lua 5.1, 5.2 & 5.3 + Copyright (C) 2002-2018 stdlib authors +]] +--[[-- + Additions to the core string module. + + The module table returned by `std.string` also contains all of the entries + from the core string table. An hygienic way to import this module, then, is + simply to override the core `string` locally: + + local string = require 'std.string' + + @corelibrary std.string +]] + + +local _ = require 'std._base' + +local argscheck = _.typecheck and _.std.typecheck.argscheck +local escape_pattern = _.string.escape_pattern +local split = _.string.split + +_ = nil + + +local _ENV = require 'std.normalize' { + 'string', + abs = 'math.abs', + concat = 'table.concat', + find = 'string.find', + floor = 'math.floor', + format = 'string.format', + gsub = 'string.gsub', + insert = 'table.insert', + match = 'string.match', + merge = 'table.merge', + render = 'string.render', + sort = 'table.sort', + sub = 'string.sub', + upper = 'string.upper', +} + + + +--[[ =============== ]]-- +--[[ Implementation. ]]-- +--[[ =============== ]]-- + + +local M + + +local function toqstring(x, xstr) + if type(x) ~= 'string' then + return xstr + end + return format('%q', x) +end + + +local concatvfns = { + elem = tostring, + term = function(x) + return type(x) ~= 'table' or getmetamethod(x, '__tostring') + end, + sort = function(keys) + return keys + end, + open = function(x) return '{' end, + close = function(x) return '}' end, + pair = function(x, kp, vp, k, v, kstr, vstr, seqp) + return toqstring(k, kstr) .. '=' .. toqstring(v, vstr) + end, + sep = function(x, kp, vp, kn, vn, seqp) + return kp ~= nil and kn ~= nil and ',' or '' + end, +} + + +local function __concat(s, o) + -- Don't use normalize.str here, because we don't want ASCII escape rendering. + return render(s, concatvfns) .. render(o, concatvfns) +end + + +local function __index(s, i) + if type(i) == 'number' then + return sub(s, i, i) + else + -- Fall back to module metamethods + return M[i] + end +end + + +local _format = string.format + +local function format(f, arg1, ...) + return(arg1 ~= nil) and _format(f, arg1, ...) or f +end + + +local function tpack(from, to, ...) + return from, to, {...} +end + +local function tfind(s, ...) + return tpack(find(s, ...)) +end + + +local function finds(s, p, i, ...) + i = i or 1 + local l = {} + local from, to, r + repeat + from, to, r = tfind(s, p, i, ...) + if from ~= nil then + insert(l, {from, to, capt=r}) + i = to + 1 + end + until not from + return l +end + + +local function caps(s) + return(gsub(s, '(%w)([%w]*)', function(l, ls) + return upper(l) .. ls + end)) +end + + +local function escape_shell(s) + return(gsub(s, '([ %(%)%\\%[%]\'"])', '\\%1')) +end + + +local function ordinal_suffix(n) + n = abs(n) % 100 + local d = n % 10 + if d == 1 and n ~= 11 then + return 'st' + elseif d == 2 and n ~= 12 then + return 'nd' + elseif d == 3 and n ~= 13 then + return 'rd' + else + return 'th' + end +end + + +local function pad(s, w, p) + p = string.rep(p or ' ', abs(w)) + if w < 0 then + return string.sub(p .. s, w) + end + return string.sub(s .. p, 1, w) +end + + +local function wrap(s, w, ind, ind1) + w = w or 78 + ind = ind or 0 + ind1 = ind1 or ind + assert(ind1 < w and ind < w, + 'the indents must be less than the line width') + local r = {string.rep(' ', ind1)} + local i, lstart, lens = 1, ind1, len(s) + while i <= lens do + local j = i + w - lstart + while len(s[j]) > 0 and s[j] ~= ' ' and j > i do + j = j - 1 + end + local ni = j + 1 + while s[j] == ' ' do + j = j - 1 + end + insert(r, sub(s, i, j)) + i = ni + if i < lens then + insert(r, '\n' .. string.rep(' ', ind)) + lstart = ind + end + end + return concat(r) +end + + +local function numbertosi(n) + local SIprefix = { + [-8]='y', [-7]='z', [-6]='a', [-5]='f', + [-4]='p', [-3]='n', [-2]='mu', [-1]='m', + [0]='', [1]='k', [2]='M', [3]='G', + [4]='T', [5]='P', [6]='E', [7]='Z', + [8]='Y' + } + local t = _format('% #.2e', n) + local _, _, m, e = find(t, '.(.%...)e(.+)') + local man, exp = tonumber(m), tonumber(e) + local siexp = floor(exp / 3) + local shift = exp - siexp * 3 + local s = SIprefix[siexp] or 'e' .. tostring(siexp) + man = man *(10 ^ shift) + return _format('%0.f', man) .. s +end + + +-- Ordor numbers first then asciibetically. +local function keycmp(a, b) + if type(a) == 'number' then + return type(b) ~= 'number' or a < b + end + return type(b) ~= 'number' and tostring(a) < tostring(b) +end + + +local render_fallbacks = { + __index = concatvfns, +} + + +local function prettytostring(x, indent, spacing) + indent = indent or '\t' + spacing = spacing or '' + return render(x, setmetatable({ + elem = function(x) + if type(x) ~= 'string' then + return tostring(x) + end + return format('%q', x) + end, + + sort = function(keylist) + sort(keylist, keycmp) + return keylist + end, + + open = function() + local s = spacing .. '{' + spacing = spacing .. indent + return s + end, + + close = function() + spacing = string.gsub(spacing, indent .. '$', '') + return spacing .. '}' + end, + + pair = function(x, _, _, k, v, kstr, vstr) + local type_k = type(k) + local s = spacing + if type_k ~= 'string' or match(k, '[^%w_]') then + s = s .. '[' + if type_k == 'table' then + s = s .. '\n' + end + s = s .. kstr + if type_k == 'table' then + s = s .. '\n' + end + s = s .. ']' + else + s = s .. k + end + s = s .. ' =' + if type(v) == 'table' then + s = s .. '\n' + else + s = s .. ' ' + end + s = s .. vstr + return s + end, + + sep = function(_, k) + local s = '\n' + if k then + s = ',' .. s + end + return s + end, + }, render_fallbacks)) +end + + +local function trim(s, r) + r = r or '%s+' + return (gsub(gsub(s, '^' .. r, ''), r .. '$', '')) +end + + + +--[[ ================= ]]-- +--[[ Public Interface. ]]-- +--[[ ================= ]]-- + + +local function X(decl, fn) + return argscheck and argscheck('std.string.' .. decl, fn) or fn +end + +M = { + --- Metamethods + -- @section metamethods + + --- String concatenation operation. + -- @function __concat + -- @string s initial string + -- @param o object to stringify and concatenate + -- @return s .. tostring(o) + -- @usage + -- local string = setmetatable('', require 'std.string') + -- concatenated = 'foo' .. {'bar'} + __concat = __concat, + + --- String subscript operation. + -- @function __index + -- @string s string + -- @tparam int|string i index or method name + -- @return `sub(s, i, i)` if i is a number, otherwise + -- fall back to a `std.string` metamethod(if any). + -- @usage + -- getmetatable('').__index = require 'std.string'.__index + -- third =('12345')[3] + __index = __index, + + + --- Core Functions + -- @section corefuncs + + --- Capitalise each word in a string. + -- @function caps + -- @string s any string + -- @treturn string *s* with each word capitalized + -- @usage + -- userfullname = caps(input_string) + caps = X('caps(string)', caps), + + --- Remove any final newline from a string. + -- @function chomp + -- @string s any string + -- @treturn string *s* with any single trailing newline removed + -- @usage + -- line = chomp(line) + chomp = X('chomp(string)', function(s) + return(gsub(s, '\n$', '')) + end), + + --- Escape a string to be used as a pattern. + -- @function escape_pattern + -- @string s any string + -- @treturn string *s* with active pattern characters escaped + -- @usage + -- substr = match(inputstr, escape_pattern(literal)) + escape_pattern = X('escape_pattern(string)', escape_pattern), + + --- Escape a string to be used as a shell token. + -- Quotes spaces, parentheses, brackets, quotes, apostrophes and + -- whitespace. + -- @function escape_shell + -- @string s any string + -- @treturn string *s* with active shell characters escaped + -- @usage + -- os.execute('echo ' .. escape_shell(outputstr)) + escape_shell = X('escape_shell(string)', escape_shell), + + --- Repeatedly `string.find` until target string is exhausted. + -- @function finds + -- @string s target string + -- @string pattern pattern to match in *s* + -- @int[opt=1] init start position + -- @bool[opt] plain inhibit magic characters + -- @return list of `{from, to; capt={captures}}` + -- @see std.string.tfind + -- @usage + -- for t in std.elems(finds('the target string', '%S+')) do + -- print(tostring(t.capt)) + -- end + finds = X('finds(string, string, ?int, ?boolean|:plain)', finds), + + --- Extend to work better with one argument. + -- If only one argument is passed, no formatting is attempted. + -- @function format + -- @string f format string + -- @param[opt] ... arguments to format + -- @return formatted string + -- @usage + -- print(format '100% stdlib!') + format = X('format(string, [any...])', format), + + --- Remove leading matter from a string. + -- @function ltrim + -- @string s any string + -- @string[opt='%s+'] r leading pattern + -- @treturn string *s* with leading *r* stripped + -- @usage + -- print('got: ' .. ltrim(userinput)) + ltrim = X('ltrim(string, ?string)', function(s, r) + return (gsub(s, '^' ..(r or '%s+'), '')) + end), + + --- Write a number using SI suffixes. + -- The number is always written to 3 s.f. + -- @function numbertosi + -- @tparam number|string n any numeric value + -- @treturn string *n* simplifed using largest available SI suffix. + -- @usage + -- print(numbertosi(bitspersecond) .. 'bps') + numbertosi = X('numbertosi(number|string)', numbertosi), + + --- Return the English suffix for an ordinal. + -- @function ordinal_suffix + -- @tparam int|string n any integer value + -- @treturn string English suffix for *n* + -- @usage + -- local now = os.date '*t' + -- print('%d%s day of the week', now.day, ordinal_suffix(now.day)) + ordinal_suffix = X('ordinal_suffix(int|string)', ordinal_suffix), + + --- Justify a string. + -- When the string is longer than w, it is truncated(left or right + -- according to the sign of w). + -- @function pad + -- @string s a string to justify + -- @int w width to justify to(-ve means right-justify; +ve means + -- left-justify) + -- @string[opt=' '] p string to pad with + -- @treturn string *s* justified to *w* characters wide + -- @usage + -- print(pad(trim(outputstr, 78)) .. '\n') + pad = X('pad(string, int, ?string)', pad), + + --- Pretty-print a table, or other object. + -- @function prettytostring + -- @param x object to convert to string + -- @string[opt='\t'] indent indent between levels + -- @string[opt=''] spacing space before every line + -- @treturn string pretty string rendering of *x* + -- @usage + -- print(prettytostring(std, ' ')) + prettytostring = X('prettytostring(?any, ?string, ?string)', prettytostring), + + --- Remove trailing matter from a string. + -- @function rtrim + -- @string s any string + -- @string[opt='%s+'] r trailing pattern + -- @treturn string *s* with trailing *r* stripped + -- @usage + -- print('got: ' .. rtrim(userinput)) + rtrim = X('rtrim(string, ?string)', function(s, r) + return (gsub(s, (r or '%s+') .. '$', '')) + end), + + --- Split a string at a given separator. + -- Separator is a Lua pattern, so you have to escape active characters, + -- `^$()%.[]*+-?` with a `%` prefix to match a literal character in *s*. + -- @function split + -- @string s to split + -- @string[opt='%s+'] sep separator pattern + -- @return list of strings + -- @usage + -- words = split 'a very short sentence' + split = X('split(string, ?string)', split), + + --- Do `string.find`, returning a table of captures. + -- @function tfind + -- @string s target string + -- @string pattern pattern to match in *s* + -- @int[opt=1] init start position + -- @bool[opt] plain inhibit magic characters + -- @treturn int start of match + -- @treturn int end of match + -- @treturn table list of captured strings + -- @see std.string.finds + -- @usage + -- b, e, captures = tfind('the target string', '%s', 10) + tfind = X('tfind(string, string, ?int, ?boolean|:plain)', tfind), + + --- Remove leading and trailing matter from a string. + -- @function trim + -- @string s any string + -- @string[opt='%s+'] r trailing pattern + -- @treturn string *s* with leading and trailing *r* stripped + -- @usage + -- print('got: ' .. trim(userinput)) + trim = X('trim(string, ?string)', trim), + + --- Wrap a string into a paragraph. + -- @function wrap + -- @string s a paragraph of text + -- @int[opt=78] w width to wrap to + -- @int[opt=0] ind indent + -- @int[opt=ind] ind1 indent of first line + -- @treturn string *s* wrapped to *w* columns + -- @usage + -- print(wrap(copyright, 72, 4)) + wrap = X('wrap(string, ?int, ?int, ?int)', wrap), +} + + +return merge(string, M) + diff --git a/Data/BuiltIn/Libraries/lua-stdlib/table.lua b/Data/BuiltIn/Libraries/lua-stdlib/table.lua new file mode 100644 index 0000000..7bda608 --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-stdlib/table.lua @@ -0,0 +1,439 @@ +--[[ + General Lua Libraries for Lua 5.1, 5.2 & 5.3 + Copyright (C) 2002-2018 stdlib authors +]] +--[[-- + Extensions to the core table module. + + The module table returned by `std.table` also contains all of the entries from + the core table module. An hygienic way to import this module, then, is simply + to override the core `table` locally: + + local table = require 'std.table' + + @corelibrary std.table +]] + + +local _ = require 'std._base' + +local argscheck = _.typecheck and _.typecheck.argscheck +local invert = _.table.invert +local maxn = _.table.maxn + +_ = nil + +local _ENV = require 'std.normalize' { + 'table', + merge = 'table.merge', + min = 'math.min', +} + + + +--[[ =============== ]]-- +--[[ Implementation. ]]-- +--[[ =============== ]]-- + + +local M + + +local function merge_allfields(t, u, map, nometa) + if type(map) ~= 'table' then + map, nometa = nil, map + end + + if not nometa then + setmetatable(t, getmetatable(u)) + end + if map then + for k, v in pairs(u) do + t[map[k] or k] = v + end + else + for k, v in pairs(u) do + t[k] = v + end + end + return t +end + + +local function merge_namedfields(t, u, keys, nometa) + if type(keys) ~= 'table' then + keys, nometa = nil, keys + end + + if not nometa then + setmetatable(t, getmetatable(u)) + end + for _, k in pairs(keys or {}) do + t[k] = u[k] + end + return t +end + + +local function depair(ls) + local t = {} + for _, v in ipairs(ls) do + t[v[1]] = v[2] + end + return t +end + + +local function enpair(t) + local tt = {} + for i, v in pairs(t) do + tt[#tt + 1] = {i, v} + end + return tt +end + + +local _insert = table.insert + +local function insert(t, pos, v) + if v == nil then + pos, v = len(t) + 1, pos + end + if pos < 1 or pos > len(t) + 1 then + argerror('std.table.insert', 2, 'position ' .. pos .. ' out of bounds', 2) + end + _insert(t, pos, v) + return t +end + + +local function keys(t) + local l = {} + for k in pairs(t) do + l[#l + 1] = k + end + return l +end + + +local function new(x, t) + return setmetatable(t or {}, {__index = function(t, i) + return x + end}) +end + + +local function project(fkey, tt) + local r = {} + for _, t in ipairs(tt) do + r[#r + 1] = t[fkey] + end + return r +end + + +local function size(t) + local n = 0 + for _ in pairs(t) do + n = n + 1 + end + return n +end + + +-- Preserve core table sort function. +local _sort = table.sort + +local function sort(t, c) + _sort(t, c) + return t +end + + +local _remove = table.remove + +local function remove(t, pos) + local lent = len(t) + pos = pos or lent + if pos < min(1, lent) or pos > lent + 1 then -- +1? whu? that's what 5.2.3 does!?! + argerror('std.table.remove', 2, 'position ' .. pos .. ' out of bounds', 2) + end + return _remove(t, pos) +end + + +local _unpack = unpack + +local function unpack(t, i, j) + if j == nil then + -- if j was not given, respect __len, otherwise use maxn + local m = getmetamethod(t, '__len') + j = m and m(t) or maxn(t) + end + return _unpack(t, tonumber(i) or 1, tonumber(j)) +end + + +local function values(t) + local l = {} + for _, v in pairs(t) do + l[#l + 1] = v + end + return l +end + + + +--[[ ================= ]]-- +--[[ Public Interface. ]]-- +--[[ ================= ]]-- + + +local function X(decl, fn) + return argscheck and argscheck('std.table.' .. decl, fn) or fn +end + +M = { + --- Core Functions + -- @section corefuncs + + --- Enhance core *table.insert* to return its result. + -- If *pos* is not given, respect `__len` metamethod when calculating + -- default append. Also, diagnose out of bounds *pos* arguments + -- consistently on any supported version of Lua. + -- @function insert + -- @tparam table t a table + -- @int[opt=len(t)] pos index at which to insert new element + -- @param v value to insert into *t* + -- @treturn table *t* + -- @usage + -- --> {1, 'x', 2, 3, 'y'} + -- insert(insert({1, 2, 3}, 2, 'x'), 'y') + insert = X('insert(table, [int], any)', insert), + + --- Largest integer key in a table. + -- @function maxn + -- @tparam table t a table + -- @treturn int largest integer key in *t* + -- @usage + -- --> 42 + -- maxn {'a', b='c', 99, [42]='x', 'x', [5]=67} + maxn = X('maxn(table)', maxn), + + --- Turn a tuple into a list, with tuple-size in field `n` + -- @function pack + -- @param ... tuple + -- @return list-like table, with tuple-size in field `n` + -- @usage + -- --> {1, 2, 'ax', n=3} + -- pack(find('ax1', '(%D+)')) + pack = pack, + + --- Enhance core *table.remove* to respect `__len` when *pos* is omitted. + -- Also, diagnose out of bounds *pos* arguments consistently on any supported + -- version of Lua. + -- @function remove + -- @tparam table t a table + -- @int[opt=len(t)] pos index from which to remove an element + -- @return removed value, or else `nil` + -- @usage + -- --> {1, 2, 5} + -- t = {1, 2, 'x', 5} + -- remove(t, 3) == 'x' and t + remove = X('remove(table, ?int)', remove), + + --- Enhance core *table.sort* to return its result. + -- @function sort + -- @tparam table t unsorted table + -- @tparam[opt=std.operator.lt] comparator c ordering function callback + -- @return *t* with keys sorted according to *c* + -- @usage + -- table.concat(sort(object)) + sort = X('sort(table, ?function)', sort), + + --- Enhance core *table.unpack* to always unpack up to __len or maxn. + -- @function unpack + -- @tparam table t table to act on + -- @int[opt=1] i first index to unpack + -- @int[opt=table.maxn(t)] j last index to unpack + -- @return ... values of numeric indices of *t* + -- @usage + -- return unpack(results_table) + unpack = X('unpack(table, ?int, ?int)', unpack), + + + --- Accessor Functions + -- @section accessorfuncs + + --- Make a shallow copy of a table, including any metatable. + -- @function clone + -- @tparam table t source table + -- @tparam[opt={}] table map table of `{old_key=new_key, ...}` + -- @bool[opt] nometa if non-nil don't copy metatable + -- @return copy of *t*, also sharing *t*'s metatable unless *nometa* + -- is true, and with keys renamed according to *map* + -- @see merge + -- @see clone_select + -- @usage + -- shallowcopy = clone(original, {rename_this='to_this'}, ':nometa') + clone = X('clone(table, [table], ?boolean|:nometa)', function(...) + return merge_allfields({}, ...) + end), + + --- Make a partial clone of a table. + -- + -- Like `clone`, but does not copy any fields by default. + -- @function clone_select + -- @tparam table t source table + -- @tparam[opt={}] table keys list of keys to copy + -- @bool[opt] nometa if non-nil don't copy metatable + -- @treturn table copy of fields in *selection* from *t*, also sharing *t*'s + -- metatable unless *nometa* + -- @see clone + -- @see merge_select + -- @usage + -- partialcopy = clone_select(original, {'this', 'and_this'}, true) + clone_select = X('clone_select(table, [table], ?boolean|:nometa)', function(...) + return merge_namedfields({}, ...) + end), + + --- Turn a list of pairs into a table. + -- @todo Find a better name. + -- @function depair + -- @tparam table ls list of lists + -- @treturn table a flat table with keys and values from *ls* + -- @see enpair + -- @usage + -- --> {a=1, b=2, c=3} + -- depair {{'a', 1}, {'b', 2}, {'c', 3}} + depair = X('depair(list of lists)', depair), + + --- Turn a table into a list of pairs. + -- @todo Find a better name. + -- @function enpair + -- @tparam table t a table `{i1=v1, ..., in=vn}` + -- @treturn table a new list of pairs containing `{{i1, v1}, ..., {in, vn}}` + -- @see depair + -- @usage + -- --> {{1, 'a'}, {2, 'b'}, {3, 'c'}} + -- enpair {'a', 'b', 'c'} + enpair = X('enpair(table)', enpair), + + --- Return whether table is empty. + -- @function empty + -- @tparam table t any table + -- @treturn boolean `true` if *t* is empty, otherwise `false` + -- @usage + -- if empty(t) then error 'ohnoes' end + empty = X('empty(table)', function(t) + return not next(t) + end), + + --- Make a table with a default value for unset keys. + -- @function new + -- @param[opt=nil] x default entry value + -- @tparam[opt={}] table t initial table + -- @treturn table table whose unset elements are *x* + -- @usage + -- t = new(0) + new = X('new(?any, ?table)', new), + + --- Project a list of fields from a list of tables. + -- @function project + -- @param fkey field to project + -- @tparam table tt a list of tables + -- @treturn table list of *fkey* fields from *tt* + -- @usage + -- --> {1, 3, 'yy'} + -- project('xx', {{'a', xx=1, yy='z'}, {'b', yy=2}, {'c', xx=3}, {xx='yy'}) + project = X('project(any, list of tables)', project), + + --- Find the number of elements in a table. + -- @function size + -- @tparam table t any table + -- @treturn int number of non-nil values in *t* + -- @usage + -- --> 3 + -- size {foo=true, bar=true, baz=false} + size = X('size(table)', size), + + --- Make the list of values of a table. + -- @function values + -- @tparam table t any table + -- @treturn table list of values in *t* + -- @see keys + -- @usage + -- --> {'a', 'c', 42} + -- values {'a', b='c', [-1]=42} + values = X('values(table)', values), + + + --- Mutator Functions + -- @section mutatorfuncs + + --- Invert a table. + -- @function invert + -- @tparam table t a table with `{k=v, ...}` + -- @treturn table inverted table `{v=k, ...}` + -- @usage + -- --> {a=1, b=2, c=3} + -- invert {'a', 'b', 'c'} + invert = X('invert(table)', invert), + + --- Make the list of keys in table. + -- @function keys + -- @tparam table t a table + -- @treturn table list of keys from *t* + -- @see values + -- @usage + -- globals = keys(_G) + keys = X('keys(table)', keys), + + --- Destructively merge one table's fields into another. + -- @function merge + -- @tparam table t destination table + -- @tparam table u table with fields to merge + -- @tparam[opt={}] table map table of `{old_key=new_key, ...}` + -- @bool[opt] nometa if `true` or ':nometa' don't copy metatable + -- @treturn table *t* with fields from *u* merged in + -- @see clone + -- @see merge_select + -- @usage + -- merge(_G, require 'std.debug', {say='log'}, ':nometa') + merge = X('merge(table, table, [table], ?boolean|:nometa)', merge_allfields), + + --- Destructively merge another table's named fields into *table*. + -- + -- Like `merge`, but does not merge any fields by default. + -- @function merge_select + -- @tparam table t destination table + -- @tparam table u table with fields to merge + -- @tparam[opt={}] table keys list of keys to copy + -- @bool[opt] nometa if `true` or ':nometa' don't copy metatable + -- @treturn table copy of fields in *selection* from *t*, also sharing *t*'s + -- metatable unless *nometa* + -- @see merge + -- @see clone_select + -- @usage + -- merge_select(_G, require 'std.debug', {'say'}, false) + merge_select = X('merge_select(table, table, [table], ?boolean|:nometa)', + merge_namedfields), +} + + +return merge(table, M) + + + +--- Types +-- @section Types + +--- Signature of a @{sort} comparator function. +-- @function comparator +-- @param a any object +-- @param b any object +-- @treturn boolean `true` if *a* sorts before *b*, otherwise `false` +-- @see sort +-- @usage +-- local reversor = function(a, b) return a > b end +-- sort(t, reversor) diff --git a/Data/Libraries/GameLab/Editor/Window/SplitWindow.lua b/Data/Libraries/GameLab/Editor/Window/SplitWindow.lua index 1419d34..acdaa9d 100644 --- a/Data/Libraries/GameLab/Editor/Window/SplitWindow.lua +++ b/Data/Libraries/GameLab/Editor/Window/SplitWindow.lua @@ -15,7 +15,8 @@ Splitter.Ctor = function(self, value) self.value = value -- [0-1] 位置 end -local ESplitMode = GameLab.GlobalEnum("GameLab.Editor.Window.ESplitMode", { +local ESplitMode = GameLab.GlobalEnum( "GameLab.Editor.Window.ESplitMode", +{ "Horizontal", -- 水平划分 "Vertical", -- 垂直划分 }) diff --git a/Data/Scripts/EditorApplication.lua b/Data/Scripts/EditorApplication.lua index 5614e2a..76c4f3d 100644 --- a/Data/Scripts/EditorApplication.lua +++ b/Data/Scripts/EditorApplication.lua @@ -11,6 +11,7 @@ local Debug = GameLab.Debug local Window = GameLab.Editor.Window local GL = GameLab.Engine.GL local delegate = GameLab.Delegate +local tuple = GameLab.Containers.Tuple local app = GameLab.Editor.EditorApplication.New() @@ -65,6 +66,8 @@ _G["default_font"] = font local v = GameLab.Engine.Math.Vector2(10.002, 2.334) +local t = tuple(1,2,3) + while true do app:OnStep() diff --git a/Data/boot.lua b/Data/boot.lua index 566468a..ff8e93f 100644 --- a/Data/boot.lua +++ b/Data/boot.lua @@ -24,6 +24,7 @@ end -- load gamelab modules
require "GameLab"
+require "GameLab.Containers"
require "GameLab.Utils"
require "GameLab.Events"
require "GameLab.Engine"
|